From 287a72cfe607035dacfc1aeb26e68a20ee0996d1 Mon Sep 17 00:00:00 2001 From: xuhcc Date: Sun, 1 Sep 2024 12:58:50 +0400 Subject: [PATCH] Deployed 1fb6212 with MkDocs version: 1.2.4 --- api_reference/beancount.core.html | 10 +- api_reference/beancount.parser.html | 8 +- api_reference/beancount.tools.html | 2 +- api_reference/beancount.utils.html | 2 +- beancount_scripting_plugins.html | 2 +- ...bdc0339d6207eab9d578bc1fc954db96ed97d.png} | Bin 20169 -> 20170 bytes beancount_v3.html | 2 +- ...38066dfe69a152d18f2d828ca1663cc0b29a93.png | Bin 0 -> 9668 bytes ...e76e1f74eeaba6ecb79f612719f105efac6b88.png | Bin 9679 -> 0 bytes external_contributions.html | 6 ++ index.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 96 +++++++++--------- sitemap.xml.gz | Bin 768 -> 768 bytes 14 files changed, 69 insertions(+), 63 deletions(-) rename beancount_scripting_plugins/media/{4117596158f0642dae7becf17be7a89f169bf4a0.png => 6bbbdc0339d6207eab9d578bc1fc954db96ed97d.png} (76%) create mode 100644 beancount_v3/media/b738066dfe69a152d18f2d828ca1663cc0b29a93.png delete mode 100644 beancount_v3/media/c8e76e1f74eeaba6ecb79f612719f105efac6b88.png diff --git a/api_reference/beancount.core.html b/api_reference/beancount.core.html index 8f17d1b7..d2245cf7 100644 --- a/api_reference/beancount.core.html +++ b/api_reference/beancount.core.html @@ -3225,7 +3225,7 @@

-beancount.core.amount.Amount.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x7a32089c1e20>) +beancount.core.amount.Amount.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x749446ea74d0>)

@@ -7622,7 +7622,7 @@

Returns: @@ -14009,7 +14009,7 @@

-beancount.core.inventory.Inventory.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x7a32089c1e20>, parens=True) +beancount.core.inventory.Inventory.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x749446ea74d0>, parens=True)

@@ -15847,7 +15847,7 @@

-beancount.core.position.Position.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x7a32089c1e20>, detail=True) +beancount.core.position.Position.to_string(self, dformat=<beancount.core.display_context.DisplayFormatter object at 0x749446ea74d0>, detail=True)

@@ -16254,7 +16254,7 @@

-beancount.core.position.to_string(pos, dformat=<beancount.core.display_context.DisplayFormatter object at 0x7a32089c1e20>, detail=True) +beancount.core.position.to_string(pos, dformat=<beancount.core.display_context.DisplayFormatter object at 0x749446ea74d0>, detail=True)

diff --git a/api_reference/beancount.parser.html b/api_reference/beancount.parser.html index 8a84bccd..fa631af1 100644 --- a/api_reference/beancount.parser.html +++ b/api_reference/beancount.parser.html @@ -3785,7 +3785,7 @@

-beancount.parser.cmptest.assertEqualEntries(expected_entries, actual_entries, failfunc=<function fail at 0x7a3207e94680>, allow_incomplete=False) +beancount.parser.cmptest.assertEqualEntries(expected_entries, actual_entries, failfunc=<function fail at 0x74944608c540>, allow_incomplete=False)

@@ -3886,7 +3886,7 @@

-beancount.parser.cmptest.assertExcludesEntries(subset_entries, entries, failfunc=<function fail at 0x7a3207e94680>, allow_incomplete=False) +beancount.parser.cmptest.assertExcludesEntries(subset_entries, entries, failfunc=<function fail at 0x74944608c540>, allow_incomplete=False)

@@ -3978,7 +3978,7 @@

-beancount.parser.cmptest.assertIncludesEntries(subset_entries, entries, failfunc=<function fail at 0x7a3207e94680>, allow_incomplete=False) +beancount.parser.cmptest.assertIncludesEntries(subset_entries, entries, failfunc=<function fail at 0x74944608c540>, allow_incomplete=False)

@@ -9662,7 +9662,7 @@

-beancount.parser.options.Opt(name, default_value, example_value=<object object at 0x7a320938d4f0>, converter=None, deprecated=False, alias=None) +beancount.parser.options.Opt(name, default_value, example_value=<object object at 0x7494475894f0>, converter=None, deprecated=False, alias=None)

diff --git a/api_reference/beancount.tools.html b/api_reference/beancount.tools.html index 379b9901..f33a3934 100644 --- a/api_reference/beancount.tools.html +++ b/api_reference/beancount.tools.html @@ -480,7 +480,7 @@

-beancount.tools.treeify.dump_tree(node, file=<_io.StringIO object at 0x7a3206d45540>, prefix='') +beancount.tools.treeify.dump_tree(node, file=<_io.StringIO object at 0x749444f21780>, prefix='')

diff --git a/api_reference/beancount.utils.html b/api_reference/beancount.utils.html index b0c0fcd7..2b442642 100644 --- a/api_reference/beancount.utils.html +++ b/api_reference/beancount.utils.html @@ -4422,7 +4422,7 @@

-beancount.utils.misc_utils.is_sorted(iterable, key=<function <lambda> at 0x7a3208a21bc0>, cmp=<function <lambda> at 0x7a3208a21c60>) +beancount.utils.misc_utils.is_sorted(iterable, key=<function <lambda> at 0x749446c30cc0>, cmp=<function <lambda> at 0x749446c30d60>)

diff --git a/beancount_scripting_plugins.html b/beancount_scripting_plugins.html index 6ad867dc..db32af2e 100644 --- a/beancount_scripting_plugins.html +++ b/beancount_scripting_plugins.html @@ -256,7 +256,7 @@

IntroductionThis document provides examples and guidelines on how to write scripts that use the contents of your ledger. It also provides information on how to write your own “plugins,” which are just Python functions that you can configure to transform your transactions or synthesize ones programmatically. These are the main two methods for extending Beancount’s features and for writing your own custom reports. You simply use Python to do this.

Load Pipeline

You need to know a little bit about how Beancount processes its input files. Internally, the single point of entry to load an input file is the beancount.loader.load_file() function, which accepts an input file and carries out a list of transformation steps, as in this diagram:

-

+

The stages of loading are as follows:

  1. diff --git a/beancount_scripting_plugins/media/4117596158f0642dae7becf17be7a89f169bf4a0.png b/beancount_scripting_plugins/media/6bbbdc0339d6207eab9d578bc1fc954db96ed97d.png similarity index 76% rename from beancount_scripting_plugins/media/4117596158f0642dae7becf17be7a89f169bf4a0.png rename to beancount_scripting_plugins/media/6bbbdc0339d6207eab9d578bc1fc954db96ed97d.png index b8df41d9a4ba03c20ed022bbb021e63423f89db1..6e85fed0cbeb78e8a3258c478a05439ebe5d9f9e 100644 GIT binary patch delta 4303 zcmai02{hF0zo#iGOZ8Hets?tY${0jhGimH0dkhIPV=rZXks_7CXtK-LX2!m6gDC4D zTgotYVPZ-KV=}I{d;jO&_n!Yf_c`Zz&Uv=)^Zk6j+tZcK*`3b$T8cBi?3|nyklvis zWS&wGRBMTt9*Ov3^fs{mdelijN={Icr61iWY5zV2TUcg>$J3bt8#T{btw&Db48=oY z(O6s4$z{6ith|j7_{4Gc>@(Rr0^7$V*mfT+&a4qBTLQ0iGKc-!Z3=jn=Z2L82K-Ki zvbWs@jFT{C@Pj%UOmY*z;^RpI3Imn3MZoGUZGwik_{IyE$WAntDo>-Q?TS!s=a+6U z!kSdUDb|ObFbJV%4Ojf%Pe60ElEM^%%?Zw>!EUJQztGT8*~^mTYa)F+I>ev^)lrh+ zfrqxtEz+iR@;i-Xy#MUD*cHONS?fg&Z3rzR^S2n>@d@*$g-kx;LnUyC`8)B6Oxi1= z-kfdCPZM`9so*sKD14Lt%a%B?!F)$=5vg{B+^rV<_5Fd#LZR%$?u=JJ=T3hY`Y|G2Nh@TB1RWtb zBzZqJ4l`KdnDnyI)c6S8j~EPcLtH?CKyKT-n5(OpQ^jcWFv}&&W8>!5f;Iu=6#&V9 zszpjo(0D@YUMaaaPX)t!H-RV{4$Nl_>HZAG_>cJMb9EOM5_BQKl7t&`a~{ZK)w?Lm z!Wr4Qnp;(hY1y)V=wOlNds8;RmR4Ww^5FC0uZ5cY-@UGjfB=elL&Nu8@Pw+OoVx!o z99ytcv;4|nfO%1}Q-%Bo>`|EyJ0zwpOLy6y3+!nUp_Gyil?M!s z)zLQCsnbW6hFn$y?J{+51&^_gRITP0-uRwC`&0l(F3?y;Bf{PgB7S5d}IMJ6-K42V&kS? zOk3uTVyGc++&-7`gC)P`S=;Q&;H~TBx{QrpT)SZ4KL`jZCDcJ64uKUF+)M>KXppFQ zCVEqX-FE?z0``ew%^If1=ueDMT#{2$D;UIAC(eCTgvsmg*+C+dURlKMQ$tt=AKc@g zSl~?BhC}W)*W6ulFgYKa2#q&8Rm*YMZR(mM-X7PU{ggpNGW%Pfn6%3(YqGXx;joYm zm26)jSRbWnp;{NsoNmp5sVTRHPI`CL?^+YzFfh#l2E*oDtD}ManmTd_|6^+uRDM{h z&1F@E>%t2o#iTYQ@l9j$@UwQqEnqb`q~N&^Vu7iEjTr8{$s#p{!>-3#IYvEHFK8Zm z<5&H40Rqfxe`+~!l+V%A0!VFaED7-$^7IkCF`ko(2oRxpvttkBG!1qrrCeg`#7Xl{ z)&=I@ObrYmOvAi}pGZAcKbwD}QBDqRf$c)llpyz(SF`_soxU zhbmu|*9gHz_o~zHq#pC2)FxUrD`Dn%@R`gNO2~K$78{MvitUl%bL1Fkfs6YQzb&Aj z#Mj!lMav}T$Sy}$`OF7{xTf?xwKEO`e#irnBg@J{ic)m@(;?b;ftQ3IPUqM1$Rdb5 zd_l7IO!uA2WQSrcfHnzC7Tt)23(hZqN3~C76*B#Zyr1&5hKqk`Ny}}95-g54`Alds z*knQqgQ46;8{;?Q&h`Q#<2^GNfq3nS#qv=>k{)cSE7yrp1igp!IGQY$yC zXxE*1IKkYVD&s3DPLtC``=F(w=#lgw8XpMWpx(Q_zs26({_Oy+7H5|*#Z{RuCNj)q zY!myKUhmy;XHzFP{8HV~`s2Z|Lo-X=$g3j#{m7rRti70z?a&~nl4@PgZkL9!m#Wsv zj2CEt?QZ?)*3kE%_3JY}@S)!)woh91g~Dukef4X+;Q>J1RAUdjT|3XB*Nb?%l<%;)Da)!zYHx((F~P~g$z zU#>t48a|9xg@!5;ZjQV<{6762UYY$;Dp3Y7L#X=Cd=UA$>|z^{wPG4HW@+i$COg6G z?H+7?zeYxmQSRZ4UgmTc{Vf((Tm2JycEwL~f=MP2BmjYnnHN!<1|VZcR1n7H1cN@= zEEB?UZ|@{NTYTM)ih=N(r?^|BNRD2+;MN)iivc(WmbB4z~HtTcbakstO1N4A6da6-poog znW=1QZ~Vw%%*&n9NKj9_Vk`*ceQZ$z!pj`DyIUihGwqoRppiicNBpBQ)i1y)ZU@@&{p~Hv zXA}A>g*AlBK>Sn}m9`gEcP1PWTX$Y6YwZpO&Ii{xD~YI&nWSorwve{QcmirBw3c@v z>>T&`XZehHkcz}<+UF7?pU?|iVztuzjkc$Y@%4!4?<%&9Wp*J{T%z_`tz@P5xc4_P z#_lAl!?muT^FbF4(Eu1#dr#id+1lD!t~k-fr8R;L-xgEhc0PYa-7mg`FL$?r`xu|7 ziO8|9oCf>aTU~ubeT!_53WFMAuIUWjp32%tA6uePJjt2TuT}$d)B~)<)3ZkR^2|H+ zz(miu>laYO$@>=SK3^-R!hbZYqEvl;VJ{aOQ(cd`eJ^Ey=jj7pyNiqFVx2(TxQ#X7 z^CJ93_ev32l-Kds&ECV*(9wx8+U7byKb;*R=`DFy>(R9qJ7M3KkBb;B+LnNED5v=1mcUhQpVV!yUU2sIE4@o)bk1=<{bZg!__zUhe3wChv` zuynGHK}%TJp9_^PKjhZSgmLf-Tkzw$9$c~cTz?u;BI8H4?%`bf3S>QS-} z+;%DgSYs60JR8)H!AaB_t29WWgx^1&Zo5F9m^PL$0vTuDNQ^TO0v1bg{3DWg?Ar1D zaZZ|@xH!7p5&DR}_`OBWwf2VMJtn_w#0}Kkt%x2fT+yr?;o=`HtMZ-D+t6X!h2ILs z{%+sCxHRF8#A<;NvfmWbY+R?-tNm^*iirM5z9)EyPxRTve`~|LQ?Y~fo3=VqNyl?X zbtkfr4}ZKN?J$W4Myr!@LYwa0D1I1dVtHTlA71-Eoi+fBQh%0+G-?L^B;fzUS11Zg zT{AnLPoO7eqV7TbC8f0goi;StB?$EYIR(`rTI1K=cX@5>P>_2c2!Wi1yM&c8ym-!Vz9c8qV`McX+45Q?}g!i1H$y5 zvL7kep$}I5S^$GdX&#OR;BzzO9aJX|#`68i)6Q&;zy5lAaD_u&>>uG^rvo&2z~MWb z0v++St$Nz7R07p^ZLTj z>!s$<1WpvUtLwKnr)^SExBX)PR_$wob27%oX7V_+Ic#|sHz;G-EzkBdS-%QUt=Z@w zRZIQJW^Aen*VK28rj1K$5^Db3ssllY#ZTiJzBXeR?=sZb2kCtFDvU1kp`+O^4Yka8 zUQz_an=qATt{h0;z06*@_Rt)XKG*dvlbjzBF4A?-8Yhmg{5sSr@dKH7NN}KTCmye* zKTN>`SG8xfw->UP&r_`On}G}Go8@2mSd(f@gLpNKrDk3NInS7;ssp1n_%Gx1$)~UF z+QhCznj}gWS)bpM8FHWY5s2xK-&OZFm=B#aMbcIyVq-t{3*6tnxS=6=V^Y&^TWvv& zlcj$9av%Q`Pkua**`>wNFDq&!tJ6Ddr{OnaHPS`H*@_rVKC)6t;|Gl5)K=GPpA%RV z81Dtd{y`&aak{%sqCcAMqFKgN-eceJyeIjsrbZS6n7z$>>oss=Ta~8$^sS$>M?%qb(fCr1(7#c%57oS6l=OH=YrL{ezEjM~&{Gp~mp027 zZ}XlbO&A+_l)?bGV*587JLd7WTjsKhkXId}d;K?|Q3sFSysviWp;ThFY;1M`c}N~UcZ*5d_w2M)KCCGBuaMW1xn6|f0TUm< zUb_3UMro0i^+HVZrbn)D1S_O9>JSdH&-mb(sH1;f9S3}-F!pT~r)54>9@E=fGZ3Xd zf&RVs_vBv#Ui6f#6Fw-yGR`U_a?ly7j~UZ|lP<$o(#qSRwsKC{l_aZ>!iot#s91Wr zSdy}By@eF@)t}o@St)}KP$7k0>y$+d(mKrk&=Dc@h#bp&=~C_h6e$n*<*z^awK)V; z(V&pdkRQxkos)pT@2UI_&mKg;PX5v15T{Wm+BSu-o;KJ3V;bc?jyh?h^;Pb1gqErB z>}Rt>LqgfS7j4GQk`IdSm8$7#3(YorU56#BTnc7c3;qX%)c$k~`erN0|5s8-NH_&-f+P%Z{Mfh#X9+UD5kR$BYui>)vK= zuy^$rtqH44iV`iaGO;A>U(B9Kya$8CXYvZ;h1o~CqBea&b*Azhd(FfUW>Bo>8u=eg CZ=opw delta 4280 zcmaJ?2{_bU+fPcV5D{f5l&wO^SR-EhW63fj#$HI)!5I5Liagn3Otu*cV=NgVW-McD zC1VRQvW+|>#*%#-Wt-uXf9KYs%2VatM*lZjy~P{F_Gh^ylx7FRp~KPu#$!)P-S5h1?K{xxyZ2iuP^BB4~Jz;eTN@U7utZJA+ zpU6AfSQ^fl2t#(=6WaJfge`Gi9{dcG2^7jf!+Xt`Nw@GV;iW}LOC^^P;8{4cqvc#r zJ6LqJbacC*LLpVPhjh{IY_>P~TB@kt*knHMrDU%!$k zl>MAiFTBWK5>~(OW<9`f#HQE*PHQClf1bID%#u~~&pi~_ui#_c!1XZr}e;>}i` zul(%`6S|?F_ai)udz#B-bxe|41i= z3T@GyB{^O`U!RlPQhD2ZsZ4co|GDj!C8XL5o%3NlpU8XWsTI-LWwZ^dpKWUtCWFZf zjOkAl5xp!4oK(=}KV4Jiu-}(dX6wC?{YJS2kt|ZFg;Dk0rr$RzR2-{Vn*9~B`Ol3X zXhleJYNl}{GIn`uaa{Zmq7{zwhLXFh>z#%22kge4V?`ZX%p?W;<1Ctxu$dlVMObiQ zJ*c-e+2nb6V&;e_9Us!Dde1@3idqtD7o`e0e|{WIhzRh?ypTQ9U? zjmo7-i`QG zfH^62qtpLOr0-xqA=QOf5+#;<+mD5}r74AIQKgrt%R#5oR&!%cBNNQ+0ykIMJJAaZ zjyhju(P%Q98ZH+O+xY<2hZX8I09~47w(4~QyxS}^_#>!`N;NccyQ)1JEZ>Afd z(oiSJEBUTc`>^LU$c+$%;&X}dKgR+3!aaWKSl61hENgK}*=AV89{Lq8j(Bp%>mGo+ zG3r&-9l)f7ypB43`696^j|mD1aa>g6iA{r`}_fj%jwVr3E8T7D*d|uCDsS5H`b%p>hi><>UEo0NUQ)_DzlO z`0HkG3Ai*ex^DYg`kNo12&(ti)eqO^wKYZwi$p~^IW9{rzI3yT#3Ol8o$4cy-tPTV z;;^b%F0)&P=3lrxsT4ZfnWmVoVD;1A<4jIu9Dnd&(N+=2Q40vRkh}=~R_`)xlBqOi zY-z3#(uV4Lq(HDkFo(!w0GiF)DMjh_#fRL!YqK^Ax$xQyQDi@hFt%7)w#t_Yr`Eye z_cOtYbcHX?j>Tv633FQf4y2hyYtYpXvM;nX=+seIfRB%C7u0a4;4hX6B7KrH6vams zeVJP?qgXe@^&q`yX|+WQ#5#l&y;{`$sHE9LX`VfqzUnmHb+sey{t z=o{9UwchAd!9;M@l)rEurQx%#k+j-TbQb9;K)5a$Xmv-Jyu*;rM%nS3p^mfN3qt!0A2noNmD8Xl8J*?lFQyu&*34UP)b&Hn#4c!gbc+{gOxRCTYfS zMt82y-Tev*g?ltY8c{b=DPM)ObCV^pHomHD}Y(CpWwVC7T7)feFz1^nT z-gLo+JEyXJ5+=ykqPBEYf1LAryJmQ zs$ng&b#N6U=a|mb8*w)&!_T@ol-d-cKRL0t%Rw4%T`N3)%TGRJ%S5=CV^lEL`(-%; zQKmkX(K`LQRIa~sf}1!C(Y&f}c0n|qwA;WGTqZkxu&ed!KDr-C*SwEfCp~?^mQ;`! zw{S#^<$?1%K#{X%=-SDID0`hNxgIzDVD2I4MDcP|ced6$O&aaO6 z;0F~84f>KH(=h!<1H^$IwfO_`x1?tOT-ME0>UfZNqSruUb@(Jhqz za1ugZ%kvtl^l09j1e_p}zVg({S z+P{HcW8i@F^e}z@!~H*~SMVbS@W!$OIC%EPwhm*v@lxKqLl2b=h?@a1v?nQKPnK%EwCd-6t@ z;@+C|X}tF}!{zXmEW4fWsRgTn|47*X?fi_-q!$l|kf9(_OP}}HukL`f(jx;k*8Rv2 zd)=o{*W1`v{HOE`g8wh`L+1SfX}kZw6e=82Y%%i`*}JeupuN43?dub&(m}lEYt-hNOsfAhKe*PY)xco6az&ndgvCnrAKX=h2X#;=yQ~sP z;;@*Q;HCy}Cw`NEYMM((^iA9_59ezO=j&%`J~j`99*?^DF8<_qWSNQxP^3BaGCRBe zs+?JJLtjJ6I(M`1-T&%=-IAtUoIO1q6H;B{uTXheik=h)Qs|uZLEVeATfVuc0=fmw zvQyW2`VKY!f&AttcY7>@a_@k-A0`G0-Y74<`-b@}jqBzxi)3b_mt@=ViLmheT-X-&5&PN%%N zvCnU6VZ$Zwr3YNTz5m_BbblN(VHTGRj4FToLg#mc9*7}Vp>0v8R1d-!qbb|3{Z%`V zy_%V3tb3qT?jpTA79Lf&6wOJR#5t+FfIV?bL~Iek@*q06Eo8l0ckn?C&J+M#T_V9E z)S^(|%pU@QNd|3#2L|WXGM)0ueJv|$X=D!3ideNL$B+^xw#6<#%?jIFs1j$B!A$R| zILdNk=ETrAD<5BcuH@>v#{x_*s}Vl3?%w5obkn;n6fNAuZz_JEfkYh1F;`Bw7oJsI z;WyHTD?w8A%Rvc^EI0>cVIV=seQXZK58fhR1Rjo^EJ!R;pJ)%4_=7P)4s=XQ>bIbi_G{e)iqeeM$*tr~-5 z*&cs7YHkM{5@E5wl)c+oyv-hBRFUY|D6>lJ+YtE}{ zo{0lfK2@)V9p}+1G{xBdc6H%0`L|xH;(X)~v(h-N4CVyI6?Jg~%N<5Hk8UkKV?U=C za4hMp_DRtz(L{jSBwZEju4Ve#L;Ts{+3(5?r%}J|er^6B|>3U5)T;}M2ixbI#sn5L@gswQKP56m}cxb&wsyOtuBkR}VsD!$z( zO)FZjGw3sM@?7(Bc^kwXP0p(2fv!Ie`Rd7J^iHVPx z2cDw|FZc1c!E-8D1ZFkS=mfh$Nl>LF8!IA1{$p7#PobR7g-0dtbu+lt1(Xw7yz>KN zt0HUqaYh8`K?LPb6b2_%rT5=HJ2CbB+Tuc*uhj_6CMe2=zO)1EIbq9VE_|Sutmj*D zvQh`eKd}~?L*K4v0LT9V@G;!km@3|4m(K=zk;-9L)31sp#HgTN(0|-DXY+X@pC2Ba zd@Y>*KW0W`;N~UY7~=-gN)@(G`)bu4ZC>@~w4k-&9z|B;YXszdLd`40TO)@Y+t%{|3Bho2vi- diff --git a/beancount_v3.html b/beancount_v3.html index a210eff0..f4da15d4 100644 --- a/beancount_v3.html +++ b/beancount_v3.html @@ -395,7 +395,7 @@

    Restructuring the Code

    +

    Here is a detailed breakdown of the various parts of the codebase today and what I think will happen to them:

  2. @@ -270,6 +272,8 @@

    Books and ArticlesScaled Estimates of Mutual Fund NAVs (RedStreet) : Problem: Mutual fund NAVs (at least in the US) are updated exactly once per day, at the end of the day. When needing to make financial decisions when the trading window is still open (eg: when tax loss harvesting), and the end-of-day NAV is not yet available, it is sometimes useful to make a trivial estimate of that NAV, especially on days when there are huge changes in the market.

    A Shortcut to Scrape Trade History from Fidelity (David Avraamides) I wrote up a description of how I use a Shortcut to scrape trade history from Fidelity’s website, run it through a Python script to convert to Beancount’s ledger format, and then save it in the clipboard so I can paste it into a ledger file.

    Lazy Beancount (Vasily M) / Evernight/lazy-beancount : Opinionated guide on how to start (and continue) tracking personal finances using the open-source Beancount accounting system. It comes together with some code. The primary goal of this guide is to provide you a way to start managing your own finances using plain-text accounting gradually and incrementally. Also with various useful tools already included and set up.

    +

    The Zen of Balance — https://academy.beanhub.io/ (Fang-Pen Lin) : An explanation of double-entry accounting using visualizations and diagrams.

    +

    Multiperiod hledger-Style Reports in beancount: Pivoting a Table | Altynbek Isabekov : An article showing how to produce pivot table summaries of account balances, e.g. by year, with associated code (github).

    Plugins

    split_transactions: Johann Klähn wrote a plugin that can split a single transaction into many against a limbo account, as would be done for depreciation.

    zerosum: Red S wrote a plugin to match up transactions that when taken together should sum up to zero and move them to a separate account.

    @@ -322,6 +326,8 @@

    Using Tree-sitterIn Rust

    jcornaz/beancount-parser (Jonathan Cornaz): A beancount file parser library for Rust. Uses nom.

    beancount_parser_lima (Simon Guest): A zero-copy parser for Beancount in Rust. It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive). Uses Logos, Chumsky, and Ariadne.

    +

    Emacs Lisp

    +

    trs-80/beancount-txn-elisp/ : beancount-txn-elisp: A library to read/parse and write/insert individual Beancount transactions, implemented in Emacs Lisp.

    Importers

    reds importers: Simple importers and tools for several US based institutions, and various file types. Emphasizes ease of writing your own importers by providing well maintained common libraries for banks, credit cards, and investment houses, and for various file types, which minimizes the institution specific code you need to write. This is a reference implementation of the principles expressed in The Five Minute Ledger Update. Contributions welcome. By RedStreet

    plaid2text: An importer from Plaid which stores the transactions to a Mongo DB and is able to render it to Beancount syntax. By Micah Duke.

    diff --git a/index.html b/index.html index f7d931e9..dcf16b38 100644 --- a/index.html +++ b/index.html @@ -333,5 +333,5 @@

    About this Documentation wrote: Regarding the last piece on which there seems to be a misunderstanding between you and I, let me provide an standalone example, outside the context of the plugin. Since Vanguard only makes three decimal places of precision available me, I need to have the actual share count calculated from the price and transaction amount. For example, I have a transaction on my activities page with these details: Shares Transacted: 0.000 Share Price: 48.62 Amount: 0.02 The only way to enter this transaction and have it balance would be to manually calculate the Shares Transacted with four decimal places of precision. My preferred way of entering this would be to enter the Share Price and Amount and have Beancount calculate Shares Transacted to a precision associated with my Vanguard account. Additionally, as metadata, I'd record \"Shares Transacted: 0.000\" as history of \"what the statement said\". Maybe you can give me your thoughts on how such a case would be addressed with planned Beancount features or as a plugin I could right? What does the downloadable file contain? 0.000 or 0.0004 or even 0.00041? What this is, most likely, is the quarterly fee that they take, which they price in terms of shares. If this is your Vanguard account, and you sum all the similar such transactions around it, you should observe that it sums to 5$ per quarter. I would log it like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX -0.00041 VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD The \"*\" will be required to merge all the lots into a single one (average cost booking) because the 48.62 USD they declare is _not_ one of your lots, but rather just the price of the asset that happened to be there on the day they took the fee. In other words, they take their fee in terms of units of each asset, priced at the current price, deducting against the cost basis of all your lots together (it doesn't matter which because this is a pre-tax account, so no capital gains are reported). Now you're asking, how could I avoid calculating 0.00041 myself? My first answer would be: let's first see if the OFX download from Vanguard includes the precision. That would solve it. I believe it does (I checked my own history of downloads and I have some numbers in there with 4 fractional digits). My second answer would be that - if you don't have the number of shares from the file - after I implement the much needed new inventory booking proposal ( http://furius.ca/beancount/doc/proposal-booking ), the interpolation capabilities will be vastly extended, such that eliding the number of shares would even be possible, like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD Now this by itself still would not solve the problem: this would store 0.000411353353 which is limited at 12 fractional digits because of the default context. So that's incorrect. What would need to be done to deal with this is to infer the most common number of digits used on units of VIIPX and to quantize the result to that number of digits (I think this could be safe enough). The number of digits appearing in the file for each currency is already tracked in a DisplayContext object that is stored in the options_map from the parser. I'll have to take that into account in the inventory booking proposal. I'm adding this to the proposal. Requirements \uf0c1 The previous sections introduce the general problem of booking, and point out important shortcomings in both the Beancount and Ledger implementations. This section will present a set of desires for a new and improved booking mechanism for command-line bookkeeping software implementations. Here are reasonable requirements for a new method: Explicit inventory lot selection needs to be supported. The user should be able to indicate precisely which of the lots to reduce a position against, per-transaction. An account or commodity should not have to have a consistent method applied across all its trades. Explicit inventory lot selection should support partial matching , that is, in cases where the lots to match against are ambiguous, the user should be able to supply only minimal information to disambiguate the lots, e.g., the date, or a label, or the cost, or combinations of these. For instance, if only a single lot is available to match against, the user should be able to specify the cost as \u201c {} \u201d, telling the system to book at cost, but essentially saying: \u201cbook against any lot.\u201d This should trigger an error only if there are multiple lots. A variety of informations should be supported to specify an existing lot, and they should be combinable: The cost of acquisition of the position; The date the position was acquired at; A user-provided label. Cases where insufficient information is provided for explicit lot selection is available should admit for a default booking method to be invoked. The user should be able to specify globally and per-account what the default booking method should be. This paves the way for implicit lot selection . A degenerate method should be available that kicks off an error and requires the user to be explicit. Average cost booking needs to be supported, as it is quite common in retirement or tax-sheltered accounts. This is the default method used in Canadian investment accounts. Cost basis adjustments need to be supported as well. The problem should be able to be specified by providing a specific lot (or all of a commodity\u2019s lots at average cost) and a dollar amount to adjust the position by. Stock splits need to be able to maintain some of the original attributes of the position , specifically, the original trade date and the user-specified label, if one has been provided. This should allow a common syntax to specify an original trading lot when reducing a position after a split. Reducing a position needs to always book against an existing lot . I\u2019m preserving the Beancount behavior here, which I\u2019ve argued for previously, as it works quite well and is a great method to avoid data entry mistakes. This naturally defines an invariant for inventories: all positions of a particular commodity held at cost should be of the same sign in one inventory (this can be used as a sanity check). Bookings that change the sign of a number of units should raise an error , unless an explicit exception is requested (and I\u2019m not even convinced that we need it). Note again that bookings only occur for units held at cost, so currency conversions are unaffected by this requirement. Beancount has had this feature for a while, and it has proved useful to detect errors. For example, if an inventory has a position of 8 HOOL {500 USD} you attempt to post a change of -10 units to this lot, the resulting number of units is now negative: -2. This should indicate user error. The only use case I can think for allowing this is the trading of futures spreads and currencies, which would naturally be reported in the same account (a short position in currencies is not regarded as a different instrument in the same way that a short position would); this is the only reason to provide an exception, and I suspect that 99% of users will not need it.] Debugging Tools \uf0c1 Since this is a non-trivial but greatly important part of the process of entering trading data, we should provide tools that list in detail the running balance for an inventory, including all of the detail of its lots. Booking errors being reported should include sufficient context, that is: The transaction with offending posting The offending posting The current state of the inventory before the posting is applied, with all its lots The implicit booking method that is in effect to disambiguate between lots A detailed reason why the booking failed Explicit vs. Implicit Booking Reduction \uf0c1 Another question that may come up in the design of a new booking method is whether we require the user to be explicit about whether he thinks this is an addition to a position, or a reduction. This could be done, for instance, by requiring a slightly different syntax for the cost, for example \u201c {} \u201d would indicate an addition and \u201c [] \u201d a reduction. I\u2019m not convinced that it is necessary to make that distinction, maybe the extra burden on the user is not worth it, but it might be a way to cross-check an expectation against the actual calculations that occur. I\u2019ll drop the idea for now. Design Proposal \uf0c1 This section presents a concrete description of a design that fulfills the previously introduced requirements. We hope to keep this as simple as possible. Inventory \uf0c1 An inventory is simply a list of lot descriptors. Each inventory lot will be defined by: UNITS, (CURRENCY, COST-UNITS, COST-CURRENCY, DATE, LABEL) Fields: UNITS: the number of units of that lot that are held CURRENCY: the type of commodity held, a string COST-UNITS: the number in the price of that lot COST-CURRENCY: the pricing commodity of the cost DATE: the acquisition date of the lot LABEL: an arbitrary user-specified string used to identify this lot If this represents a lot held at cost, after this processing, only the LABEL field should be optionally with a NULL value. Anyone writing code against this inventory could expect that all other values are filled in with non-NULL values (or are otherwise all NULL). Input Syntax & Filtering \uf0c1 The input syntax should allow the specification of cost as any combination of the following informations: The cost per unit , as an amount, such as \u201c 500 USD \u201d A total cost , to be automatically divided by the number of units, like this: {... +9.95 USD} . You should be able to use either cost per unit, total cost, or even combine the two, like this: {500 + 9.95 USD} . This is useful to enter commissions. This syntax also replaces the previous {{ ... }} syntax. The lot-date , as a YYYY-MM-DD date, such as \u201c 2014-06-20 \u201d A label , which is any non-numerical identifier, such as first-apple or some random uuid like \u201c aa2ba9695cc7 \u201d A special marker \u201c * \u201d that indicates to book at the average cost of the inventory balance These following postings should all be valid syntax: ... Assets:Investments:Stock 10 HOOL {500 USD} ; cost Assets:Investments:Stock 10 HOOL {339999615d7a} ; label Assets:Investments:Stock 10 HOOL {2014-05-01} ; lot-date Assets:Investments:Stock 10 HOOL {} ; no information ... Combinations Assets:Investments:Stock 10 HOOL {500 USD, 2014-05-01} Assets:Investments:Stock 10 HOOL {2014-05-01, 339999615d7a} Algorithm \uf0c1 All postings should be processed in a separate step after parsing, in the order of their date, against a running inventory balance for each account. The cost amount should become fully determined at this stage, and if we fail to resolve a cost, an error should be raised and the transaction deleted from the flow of directives (after the program loudly complaining about it). When processing an entry, we should match and filter all inventory lots against all the filters that are provided by the user. In general, after filtering the lots with the user restrictions: If the set of lots is empty, an error should be raised; If there is a single lot, this lot should be selected (success case); If there are multiple lots, the default implicitly booking method for the corresponding account should be invoked. Implicit Booking Methods \uf0c1 If there are multiple matching lots to choose from during booking, the following implicit booking methods could be invoked: STRICT: Select the only lot of the inventory. If there are more than one lots, raise an error. FIFO: Select the lot with the earliest date. LIFO: Select the lot with the latest date. AVERAGE: Book this transaction at average cost. AVERAGE_ONLY: Like AVERAGE but also trigger an aggregation on an addition to a position. This can be used to enforce that the only booking method for all lots is at the average cost. NONE: Don\u2019t perform any inventory booking on this account. Allow a mix of lots for the same commodity or positive and negative numbers in the inventory. (This essentially devolves to the Ledger method of booking.) This method would only get invoked if disambiguation between multiple lots is required, after filtering the lots against the expression provided by the user. The STRICT method is basically the degenerate disambiguation which issues an error if there is any ambiguity and should be the default. There should be a default method that applies to all accounts. The default value should be overridable. In Beancount, we would add a new \u201coption\u201d to allow the user to change this: option \"booking_method\" \"FIFO\" The default value used in a particular account should be specifiable as well, because it is a common case that the method varies by account, and is fixed within the account. In Beancount, this would probably become part of the open directive, something like this: 2003-02-17 open Assets:Ameritrade:HOOL HOOL booking:FIFO (I\u2019m not sure about the syntax.) Resolving Same Date Ambiguity \uf0c1 If the automatic booking method gets invoked to resolve an ambiguous lot reduction, e.g. FIFO, if there are multiple lots at the same date, something needs to be done to resolve which of the lots is to be chosen. The line number at which the transaction appears should be selected. For example, in the following case, a WIDGET Of 8 GBP would be selected: 2014-10-15 * \"buy widgets\" Assets:Inventory 10 WIDGET {} ;; Price inferred to 8 GBP/widget Assets:Cash -80 GBP 2014-10-15 * \"buy another widget\" Assets:Inventory 1 WIDGET {} ;; Price inferred to 9 GBP/widget Assets:Cash -9 GBP 2014-10-16 * \"sell a widget\" Assets:Cash 11 GBP Assets:Inventory -1 WIDGET {} ;; Ambiguous lot Dates Inserted by Default \uf0c1 By default, if an explicit date is not specified in a cost, the date of the transaction that holds the posting should be attached to the trading lot automatically. This date is overridable so that stock splits may be implemented by a transformation to a transaction like this: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD, 2014-01-04} ; reducing Assets:Investments:Stock 10 HOOL {500.00 USD, 2014-01-04} ; augment Assets:Investments:Stock 10 HOOLL {500.00 USD, 2014-01-04} ; augment Matching with No Information \uf0c1 Supplying no information for the cost should be supported and is sometimes useful: in the case of augmenting a position, if all the other legs have values specified, we should be able to automatically infer the cost of the units being traded: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {} Assets:Investments:Cash -5009.95 USD Expenses:Commissions 9.95 USD In the case of reducing a position\u2014and we can figure that out whether that is the case by looking at the inventory at the point of applying the transaction to its account balance\u2014an empty specification should trigger the default booking method. If the method is STRICT, for instance, this would select a lot only if there is a single lot available (the choice is unambiguous), which is probably a common case if one trades infrequently. Other methods will apply as they are defined. Note that the user still has to specify a cost of \u201c {} \u201d in order to inform us that this posting has to be considered \u201cheld at cost.\u201d This is important to disambiguate from a price conversion with no associated cost. Reducing Multiple Lots \uf0c1 If a single trade needs to close multiple existing lots of an inventory, this can dealt with trivially by inserting one posting for each lot. I think this is a totally reasonable requirement. This could represent a single trade, for instance, if your brokerage allows it: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -10 HOOL {500 USD} Assets:Investments:Stock -12 HOOL {510 USD} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains Note: If the cost basis specification for the lot matches multiple lots of the inventory and the result is unambiguous, the lots will be correctly selected. For example, if the ante-inventory contains just those two lots (22 HOOL in total), you should be able to have a single reducing posting like this: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -22 HOOL {} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains and they will both be included. On the other hand, if the result is ambiguous (for example, if you have more than these two lots) the booking strategy for that account would be invoked. By default, this strategy will be \"STRICT\" which will generate an error, but if this account's strategy is set to \"FIFO\" (or is not set and the default global strategy is \"FIFO\"), the FIFO lots would be automatically selected for you, as per your wish. Lot Basis Modification \uf0c1 Another idea is to support the modification of a specific lot\u2019s cost basis in a single leg. The way this could work, is by specifying the number of units to modify, and the \u201c+ number currency\u201d syntax would be used to adjust the cost basis by a specific number, like this: 2012-05-01 * \"Adjust cost basis by 250 USD for 5 of the 500 USD units\" Assets:Investments:Stock ~5 HOOL {500 + 250 USD} If the number of units is smaller than the total number of units in the lot, split out the lot before applying the cost basis adjustment. I\u2019m not certain I like this. Not specifying the size could also adjust the entire position (I\u2019m not sure if this makes sense as it departs from the current syntax significantly): 2012-05-01 * \"Adjust cost basis by 250 USD for the 500 USD units\" Assets:Investments:Stock HOOL {500 + 250 USD} In any case, all the fields other than the total cost adjustment would be used to select which lot to adjust. Tag Reuse \uf0c1 We will have to be careful in the inventory booking to warn on reuse of lot labels. Labels should be unique. Examples \uf0c1 Nothing speaks more clearly than concrete examples. If otherwise unspecified, we are assuming that the booking method on the Assets:Investments:Stock account is STRICT . No Conflict \uf0c1 Given the following inventory lots: 21, (HOOL, 500, USD, 2012-05-01, null) 22, (AAPL, 380, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {} ... This booking should fail (because the amount is not one of the valid lots): 2013-05-01 * Assets:Investments:Stock -10 HOOL {520 USD} ... This one too should fail (no currency matching lot): 2013-05-01 * Assets:Investments:Stock -10 MSFT {80 USD} ... So should this one (invalid date when a date is specified): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2010-01-01} ... Explicit Selection By Cost \uf0c1 Given the following inventory: 21, (HOOL, 500, USD, 2012-05-01, null) 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 25, (HOOL, 510, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {510 USD} ... This booking should fail if the stock account\u2019s method is STRICT (ambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... This booking should succeed if the stock account\u2019s method is FIFO, booking against the lot at 2012-05-01 (earliest): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... Explicit Selection By Date \uf0c1 Given the same inventory as previously, this booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-05-01} ... This booking should fail if the method is STRICT (ambiguous) 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-06-01} ... Explicit Selection By Label \uf0c1 This booking should succeed, because there is a single lot with the \u201cabc\u201c label: 2013-05-01 * Assets:Investments:Stock -10 HOOL {abc} ... If multiple lots have the same label, ambiguous cases may occur; with this inventory, for example: 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 31, (HOOL, 510, USD, 2012-07-01, \u201cabc\u201d) The same booking should fail. Explicit Selection By Combination \uf0c1 With the initial inventory, this booking should succeed (unambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} ... There is only one lot at a cost of 500 USD and at an acquisition date of 2012-06-01. Not Enough Units \uf0c1 The following booking would be unambiguous, but would fail, because there aren\u2019t enough units of the lot in the inventory to subtract from: 2013-05-01 * Assets:Investments:Stock -33 HOOL {500 USD, 2012-06-01} \u2026 Redundant Selection of Same Lot \uf0c1 If two separate postings select the same inventory lot in one transaction, it should be able to work: 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -10 HOOL {abc} ... Of course, if there are not enough shares, it should fail: 2013-05-01 * Assets:Investments:Stock -20 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -20 HOOL {abc} ... Automatic Price Extrapolation \uf0c1 If all the postings of a transaction can have their balance computed, we allow a single price to be automatically calculated - this should work: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD And result in the equivalent of: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {534.51 USD} Income:US:Invest:Gains -340.51 USD Of course, preserving the original date of the lot should work too, so this should work as well: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {2014-02-04} Income:US:Invest:Gains -340.51 USD Average Cost Booking \uf0c1 Augmenting lots with the average cost syntax should fail: 2014-03-15 * \"Buying at average cost, what does this mean?\" Assets:US:Invest:Stock 10.00 HOOL {*} Income:US:Invest:Gains -5000.00 USD Reducing should work, this is the main use case: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {510.00 USD} Assets:US:Invest:Cash -5100.00 USD 2014-04-28 * \"Obtaining a dividend in stock\" Assets:US:Invest:Stock 1.00 HOOL {520.00 USD} Income:US:Invest:Dividends -520.00 USD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains ; Automatically calculated: -194.29 USD The effect would be to aggregate the 10 HOOL {500.00 USD} lot, the 10 HOOL {510.00 USD} lot, and the 1.00 HOOL {520.00 USD} lot, together into a single lot of 21 HOOL {505.714285 USD}, and to remove 8 HOOL from this lot, which is 8 HOOL x 505.714285 USD/HOOL = 4045.71 USD. We receive 4240.00 USD, which allows us to automatically compute the capital gain: 4240.00 - 4045.71 = 194.29 USD. After the reduction, a single lot of 13 HOOL {505.714285 USD} remains. It should also work if we have multiple currencies in the account, that is adding the following transaction to the above should not make it faile: 2014-04-15 * \"Buying another stock\" Assets:US:Invest:Stock 15.00 AAPL {300.00 USD} Assets:US:Invest:Cash -4500.00 USD However it should fail if we have the same currency priced in distinct cost currencies in the same account: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {623.00 CAD} Assets:US:Invest:Cash -62300.00 CAD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} ; Which HOOL, USD or CAD? Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains But of course, this never happens in practice, so I\u2019m not too concerned. We could potentially support specifying the cost-currency to resolve this case, that is, replacing the sell leg with this: 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {* USD} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains I\u2019m quite sure it wouldn\u2019t be useful, but we could go the extra mile and be as general as we can possibly be. Future Work \uf0c1 This proposal does not yet deal with cost basis re-adjustments! We need to way to be able to add or remove a fixed dollar amount to a position\u2019s cost basis, that is, from (1) a lot identifier and (2) a cost-currency amount, we should be able to update the cost of that lot. The transaction still has to balance. Here are some ideas what this might look like: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD + 230.00 USD} Income:US:Invest:Gains -230.00 USD The resulting inventory would be 10 HOOL at 523.00 USD per share, and the rest of the original lots, less 10 HOOL at 500.00 USD per share (there might be some of that remaining, that\u2019s fine). This is the equivalent of 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500.00 USD} Assets:US:Invest:Stock 10.00 HOOL {523.00 USD} Income:US:Invest:Gains -230.00 USD except you did not have to carry out the calculation. This should therefore be implemented as a transformation. However, this is not super useful, because this is already supported... adjustments are usually performed on lots at average cost, and this is where the problem lie: you\u2019d have to make the calculation yourself! Here\u2019s a relevant use case: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD This would first average all the lots of HOOL together and recompute the cost basis with the new amount. This should work: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 5.00 HOOL {500.00 USD} Assert:US:Invest:Cash -2500.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 5.00 HOOL {520.00 USD} Assets:US:Invest:Cash -2600.00 USD 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Notice how we still have to specify an amount of HOOL shares to be repriced. I like this, but I\u2019m wondering if we should allow specifying \u201call the units\u201d like this (this would be a more radical change to the syntax): 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock * HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Perhaps the best way to auto-compute cost basis adjustments would be via powerful interpolation, like this: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500 USD} ; reduce Assets:US:Invest:Stock 10.00 HOOL {} ; augment, interpolated Income:US:Invest:Gains -230.00 USD See the Smarter Elision document for more details on a proposal. Inter-Account Booking \uf0c1 Some tax laws require a user to book according to a specific method, and this might apply between all accounts. This means that some sort of transfer needs to be applied in order to handle this correctly. See the separate document for detail. TODO - complete this with more tests for average cost booking! Implementation Notes \uf0c1 Separate Parsing & Interpolation \uf0c1 In order to implement a syntax for reduction that does not specify the cost, we need to make changes to the parser. Because there may be no costs on the posting, it becomes impossible to perform balance checks at parsing time. Therefore, we will need to postpone balance checks to a stage after parsing. This is reasonable and in a way nice: it is best if the Beancount parser does not output many complex errors at that stage. A new \u201cpost-parse\u201d stage will be added, right before running the plugins. Conclusion \uf0c1 This document is work-in-progress. I\u2019d really love to get some feedback in order to improve the suggested method, which is why I put this down in words instead of just writing the code. I think a better semantic can be reached by iterating on this design to produce something that works better in all systems, and this can be used as documentation later on for developers to understand why it is designed this way. Your feedback is much appreciated.","title":"A Proposal for an Improvement on Inventory Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inventory-booking","text":"A Proposal for an Improvement on Command-Line Bookkeeping Martin Blais, June 2014 http://furius.ca/beancount/doc/proposal-booking Motivation Problem Description Lot Date Average Cost Basis Capital Gains Sans Commissions Cost Basis Adjustments Dealing with Stock Splits Previous Solutions Shortcomings in Ledger Shortcomings in Beancount Requirements Debugging Tools Explicit vs. Implicit Booking Reduction Design Proposal Inventory Input Syntax & Filtering Algorithm Implicit Booking Methods Dates Inserted by Default Matching with No Information Reducing Multiple Lots Examples No Conflict Explicit Selection By Cost Explicit Selection By Date Explicit Selection By Label Explicit Selection By Combination Not Enough Units Redundant Selection of Same Lot Automatic Price Extrapolation Average Cost Booking Future Work Implementation Note Conclusion","title":"Inventory Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#motivation","text":"The problem of \u201cinventory booking,\u201d that is, selecting which of an inventory\u2019s trade lots to reduce when closing a position, is a tricky one. So far, in the command-line accounting community, relatively little progress has been made in supporting the flexibility to deal with many common real-life cases. This document offers a discussion of the current state of affairs, describes the common use cases we would like to be able to solve, identifies a set of requirements for a better booking method, and proposes a syntax and implementation design to support these requirements, along with clearly defined semantics for the booking method.","title":"Motivation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#problem-description","text":"The problem under consideration is the problem of deciding, for a double-entry transaction that intends to reduce the size of a position at a particular point in time in a particular account, which of the account inventory lots contained at that point should be reduced. This should be specified using a simple data entry syntax that minimizes the burden on the user. For example, one could enter a position in HOOL stock by buying two lots of it at different points in time: 2014-02-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade\" Assets:Investments:Stock 8 HOOL {510 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD We will call the two sets of shares \u201ctrade lots\u201d and assume that the underlying system that counts them is keeping track of each of their cost and date of acquisition separately. The question here is, if we were to sell some of this stock, which of the shares should we select? Those from the first trade, or those from the second? This is an important decision, because it has an impact on capital gains, and thus on taxes. (The account of capital gains is described in more detail in the \u201cStock Trading\u201d section of the Beancount cookbook if you need an explanation of this.) Depending on our financial situation, this year\u2019s trading history, and the unrealized gains that a trade would realize, sometimes we may want to select one lot over another. By now, most discount brokers even let you select your specific lot when you place a trade that reduces or closes a position. It is important to emphasize here the two directions of inventory booking: Augmenting a position. This is the process of creating a new trading lot, or adding to an existing trading lot (if the cost and other attributes are identical). This is easy, and amounts to adding a record to some sort of mapping that describes an account\u2019s balance (I call this an \"inventory\"). Reducing a position. This is generally where booking complications take place, and the problem is essentially to figure out which lot of an existing position to remove units from. Most of this document is dedicated to the second direction.","title":"Problem Description"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#lot-date","text":"Even if the cost of each lot is identical, the acquisition date of the position you\u2019re intending to close matters, because different taxation rules may apply, for example, in the US, positions held for more than 12 months are subject to a significantly lower tax rate (\u201cthe long-term capital gains rate\u201d). For example, if you entered your position like this: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade at same price\" Assets:Investments:Stock 8 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD and we are booking a trade for 2014-03-01, selecting the first lot would result in a long-term (low) gain, because two years have passed since acquisition (position held from 2012-05-01 to 2014-03-01) while booking against the second would result in a short-term (high) capital gains tax. So it\u2019s not just a matter of the cost of the lots. Our systems don't aim to file taxes automatically at this point but we would like to enter our data in a way that eventually allows this kind of reporting to take place. Note that this discussion assumes that you were able to decide which of the lots you could trade against. There are several taxation rules that may apply, and in some cases, depending on which country you live in, you may not have a choice, the government may dictate how you are meant to report your gains. In some cases you may even need to be able to apply multiple booking methods to the same account (e.g., if an account is subject to a taxation claim from multiple countries). We will ignore this exotic case in this document, as it is quite rare.","title":"Lot Date"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#average-cost-basis","text":"Things get more complicated for tax-sheltered accounts. Because there are no tax consequences to closing positions in these accounts, brokers will normally choose to account for the book value of your positions as if they were a single lot. That is, the cost of each share is computed using the weighted average of the cost of the position you are holding. This can equivalently be calculated by summing the total cost of each position and then dividing by the total number of shares. To continue with our first example: 10 HOOL x 500 USD/HOOL = 5000 USD 8 HOOL x 510 USD/HOOL = 4080 USD Cost basis = 5000 USD + 4080 USD = 9080 USD Total number of shares = 10 HOOL + 8 HOOL = 18 HOOL Cost basis per share = 9080 USD / 18 HOOL = 504.44 USD/HOOL So if you were to close some of your position and sell some shares, you would do so at a cost of 504.44 USD/HOOL. The gain you would realize would be relative to this cost; for example if you sold 5 shares at 520 USD/HOOL, your gain would be calculated like this: (520 USD/HOOL - 504.44 USD/HOOL) x 5 HOOL = 77.78 USD This type of booking method is made evident by two kinds of financial events that you might witness occur in these types of accounts: Fees may be withdrawn by selling some shares reported at their current market price. You will see these if you hold a retirement account at Vanguard in the USA. The broker takes a fixed amount of dollars per quarter (5$, with my employer's plan) that is backed out in terms of a small, fractional number of shares of each fund to sell. That transaction is oblivious to capital gains: they simply figure out how many shares to sell to cover their fee and don\u2019t report a gain to you. You have to assume it doesn\u2019t matter. Most people don't track their gains in non-taxable accounts but freaks like us may want to compute the return nonetheless. Cost basis readjustments may occur spontaneously. This is rare, but I have seen this once or twice in the case of mutual funds in a Canadian tax-deferred account: based on some undisclosed details of their trading activity, the fund management has had to issue an adjustment to the cost basis, which you are meant to apply to your positions. You get an email telling you how much the adjustment was for. 99% of people probably don\u2019t blink, don't understand and ignore this message\u2026 perhaps rightly so, as it has no impact on their taxes, but if you want to account for your trading returns in that account, you do need to count it. So we need to be able to add or remove to the cost basis of existing positions. Note that the average cost of your positions changes on every trade and needs to get recalculated every time that you add to an existing position. A problem with this is that if you track the cost per share, these multiple recalculations may result in some errors if you store the amounts using a fixed numerical representation (typically decimal numbers). An alternative method to track the cost in our data structures, one that is more stable numerically, is to simply keep track of the total cost instead of the cost-per-share. Also note that using fractional numbers does not provide a sufficient answer to this problem: in practice, the broker may report a specific cost, one that is rounded to specific number of decimals, and we may want to be able to book our reducing trade using that reported number rather than maintain the idealized amount that a fractional representation would. Both using a fixed decimal or a fractional representation pose problems. We will largely ignore those problems here. In summary: we need to be able to support book against lots at their average cost, and we need to be able to adjust the cost basis of an existing position. I\u2019ve been thinking about a solution to implement this that would not force an account to be marked with a special state. I think we can do this using only inventory manipulations. All we need is a small change to the syntax that allows the user to indicate to the system that it should book against the average cost of all positions. Using the first example above, acquiring the shares would take the same form as previously, that is after buying two lots of 10 and 8 HOOL units, we end up with an inventory that holds two lots, one of 10 HOOL {500 USD} and one of 8 HOOL {510 USD}. However, when it\u2019s time to sell, the following syntax would be used (this is an old idea I\u2019ve been meaning to implement for a while): 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 HOOL {*} Assets:Investments:Cash 2759.95 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains When the \u201c * \u201d is encountered in lieu of the cost, like this, it would: Merge all the lots together and recalculate the average price per share (504.44 USD) Book against this merged inventory, reducing the resulting lot. After the transaction, we would end up with a single position of 13 HOOL {504.44 USD). Adding to this position at a different price would create a new lot, and those would get merged again the next time a reduction occurs at average cost. We do not need to merge lots until there is a reduction. Apart from concerns of accumulating rounding error, this solution is correct mathematically, and maintains the property that accounts aren\u2019t coerced into any specific booking method\u2014a mix of methods could be used on every reduction. This is a nice property, even if not strictly necessary, and even if we want to be able to just specify a \u201cdefault\u201d booking method to use per account and just stick with it throughout. It\u2019s always nice to have the flexibility to support exceptions, because in real life, they sometimes occur.","title":"Average Cost Basis"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#capital-gains-sans-commissions","text":"We would like to be able to support the automatic calculation of capital gains without the commissions. This problem is described in much detail in the Commissions section of the trading documentation and in a thread on the mailing-list . The essence of the complication that occurs is that one cannot simply subtract the commissions incurred during the reporting period from the gains that include commissions, because the commissions that were incurred to acquire the position must be pro-rated to the shares of the position that are sold. The simplest and most common way to implement this is to include the costs of acquiring the position into the cost basis of the position itself, and deduct the selling costs from the market value when a position is reduced. Whatever new design we come up with must allow us to count these adjusted gains as this is essential to various individuals' situations. In Beancount, I have figured out a solution for this problem, which luckily enough involves only an automated transformation of a transaction flagged by the presence of a special flag on its postings... if and only if I can find a way to specify which lot to book against without having to specify its cost, because once the cost of commissions gets folded into the cost of the position, the adjusted cost does not show up anywhere in the input file, the user would have to calculate it manually, which is unreasonable. In the solution I\u2019m proposing, the following transactions would be transformed automatically, triggered by the presence of the \u201cC\u201d flag: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD C Expenses:Commissions 9.95 USD Assets:US:Invest:HOOL 10.00 HOOL {500 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains 2014-05-10 * \"Sell #2\" Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains They would be automatically transformed by a plugin into the following and replace the original transactions above: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD X Assets:US:Invest:HOOL 10.00 HOOL {500.995 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" X Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains ; Should be (530-500)*4 - 9.95*(4/10) - 9.95 = ; 106.07 USD 2014-05-10 * \"Sell #2\" X Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains ; Should be (540-500)*6 - 9.95*(6/10) - 9.95 = ; 224.08 USD The \u201cX\u201d flag would just mark the postings that have been transformed, so maybe they can be rendered with a special color or attribute later on. The rebates account is included as a separate counter just to make the transaction balance. The reason I need to relax the disambiguation of trading lots is that the user needs to be able to specify the matching leg without having to specify the cost of the position, because at the point where the position is reduced (2014-04-10), there is no way to figure out what the cost of the original lot was. Just to be clear, in the example above, this means that if all the information have is 4 HOOL and 500 USD, there is no way to back out a cost of 500.995 USD that could specify a match with the opening trade lot, because that happened with 10 HOOLs. So we need to be more explicit about booking. In the example above, I\u2019m selecting the matching lot \u201cby label,\u201d that is, the user has the option to provide a unique lot identifier in the cost specification, and that can later on be used to disambiguate which lot we want to book a reduction/sale against. The example above uses the string \u201c aa2ba9695cc7 \u201d in this way. (An alternative solution to this problem would involve keep track of both the original cost (without commissions), and the actual cost (with commissions), and then finding the lot against the former, but using the latter to balance the transaction. This idea is to allow the user to keep using the cost of a position to select the lot, but I\u2019m not even sure if that is possible, in the presence of various changes to the inventory. More thought is required on this matter.)","title":"Capital Gains Sans Commissions"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#cost-basis-adjustments","text":"In order to adjust the cost basis, one can replace the contents of an account explicitly like this: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {510 USD} Income:US:Invest:Gains This method works well and lets the system automatically compute the gains. But it would be nice to support making adjustments in price units against the total cost of the position, for example, \u201cadd 340.51 USD to the cost basis of this position\u201d. The problem is that the adjusted cost per share now needs to be computed by the user\u2026 it\u2019s inconvenient. Here\u2019s one way we could support this well, however: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD If all the legs are fully specified - they have a calculatable balance - we allow a single price to be elided. This solves this problem well.","title":"Cost Basis Adjustments"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#dealing-with-stock-splits","text":"Accounting for stock splits creates some complications in terms of booking. In general, the problem is that we need to deal with changes in the meaning of a commodity over time. For example, you could do this in Beancount right now and it works: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD} Assets:Investments:Stock 10 HOOL {500.00 USD} Assets:Investments:Stock 10 HOOLL {500.00 USD} One problem with splitting lots this way is that the price and meaning of a HOOL unit before and after the split differs, but let\u2019s just assume we\u2019re okay with that for now (this can be solved by relabeling the commodity by using another name, ideally in a way that is not visible to the user - we have a pending discussion elsewhere ). The issue that concerns booking is, when you sell that position, which cost do you use? The inventory will contain positions at 500 USD, so that\u2019s what you should be using, but is it obvious to the user? We can assume this can be learned with a recipe . Now, what if we automatically attach the transaction date? Does it get reset on the split and now you would have to use 2014-04-17? If so, we could not automatically inspect the list of trades to determine whether this is a long-term vs. short-term trade. We need to somehow preserve some of the attributes of the original event that augmented this position, which includes the original trade date (not the split date) and the original user-specified label on the position, if one was provided.","title":"Dealing with Stock Splits"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#forcing-a-single-method-per-account","text":"For accounts that will use the average booking method, it may be useful to allow specifying that an account should only use this booking method . The idea is to avoid data entry errors when we know that an account is only able to use this method. One way to ensure this is to automatically aggregate inventory lots when augmenting a position. This ensures that at any point in time, there is only a single lot for each commodity. If we associated an inventory booking type to each account, we could define a special one that also triggers aggregation upon the addition of a position. Another approach would be to not enforce these aggregations, but to provide a plugin that would check that for those accounts that are marked as using the average cost inventory booking method by default, that only such bookings actually take place.","title":"Forcing a Single Method per Account"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#previous-solutions","text":"This section reviews existing implementations of booking methods in command-line bookkeeping systems and highlights specific shortcomings that motivate why we need to define a new method.","title":"Previous Solutions"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#shortcomings-in-ledger","text":"(Please note: I\u2019m not deeply familiar with the finer details of the inner workings of Ledger; I inferred its behavior from building test examples and reading the docs. If I got any of this wrong, please do let me know by leaving a comment.) Ledger\u2019s approach to booking is quite liberal. Internally, Ledger does not distinguish between conversions held at cost and regular price conversions : all conversions are assumed held at cost, and the only place trading lots appear is at reporting time (using its --lots option). Its \u201c {} \u201d cost syntax is meant to be used to disambiguate lots on a position reduction , not to be used when acquiring a position. The cost of acquiring a position is specified using the \u201c @ \u201d price notation: 2014/05/01 * Buy some stock (Ledger syntax) Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash This will result in an inventory of 10 HOOL {500 USD}, that is, the cost is always stored in the inventory that holds the position: $ ledger -f l1.lgr bal --lots 10 HOOL {USD500} [2014/05/01] USD-5000 Assets:Investments USD-5000 Cash 10 HOOL {USD500} [2014/05/01] Stock -------------------- 10 HOOL {USD500} [2014/05/01] USD-5000 There is no distinction between conversions at cost and without cost\u2014all conversions are tracked as if \u201cat cost.\u201d As we will see in the next section, this behaviour is distinct from Beancount\u2019s semantics, which requires a conversion held at cost to be specified using the \u201c {} \u201d notation for both its augmenting and reducing postings, and distinguishes between positions held at cost and price conversions. The advantage of the Ledger method is a simpler input mechanism, but it leads to confusing outcomes if you accumulate many conversions of many types of currencies in the same account. An inventory can easily become fragmented in its lot composition. Consider what happens, for instance, if you convert in various directions between 5 different currencies\u2026 you may easily end up with USD held in CAD, JPY, EUR and GBP and vice-versa\u2026 any possible combinations of those are possible. For instance, the following currency conversions will maintain their original cost against their converted currencies: 2014/05/01 Transfer from Canada Assets:CA:Checking -500 CAD Assets:US:Checking 400 USD @ 1.25 CAD 2014/05/15 Transfers from Europe Assets:UK:Checking -200 GBP Assets:US:Checking 500 USD @ 0.4 GBP 2014/05/21 Transfers from Europe Assets:DE:Checking -100 EUR Assets:US:Checking 133.33 USD @ 0.75 EUR This results in the following inventory in the Assets:US:Checking account: $ ledger -f l2.lgr bal --lots US:Checking 500.00 USD {0.4 GBP} [2014/05/15] 133.33 USD {0.75 EUR} [2014/05/21] 400.00 USD {1.25 CAD} [2014/05/01] Assets:US:Checking The currencies are treated like stock. But nobody thinks of their currency balances like this , one normally thinks of currencies held in an account as units in and of themselves, not in terms of their price related to some other currency. In my view, this is confusing. After these conversions, I just think of the balance of that account as 1033.33 USD. A possible rebuttal to this observation is that most of the time a user does not care about inventory lots for accounts with just currencies, so they just happen not print them out. Out of sight, out of mind. But there is no way to distinguish when the rendering routine should render lots or not. If instructed to produce a report, a balance routine should ideally only render lots only for some accounts (e.g., investing accounts, where the cost of units matters) and not for others (e.g. accounts with currencies that were deposited sometimes as a result of conversions). Moreover, when I render a balance sheet, for units held at cost we need to report the book values rather than the number of shares. But for currencies, the currency units themselves need to get reported, always. If you don\u2019t distinguish between the two cases, how do you know which to render? I think the answer is to select which view you want to render using the options. The problem is that neither provides a single unified view that does the correct thing for both types of commodities. The user should not have to specify options to view partially incorrect views in one style or the other, this should be automatic and depend on whether we care about the cost of those units. This gets handled correctly if you distinguish between the two kinds of conversions. But perhaps most importantly, this method does not particularly address the conversion problem : it is still possible to create money out of thin air by making conversions back and forth at different rates: 2014/05/01 Transfer to Canada Assets:US:Checking -40000 USD Assets:CA:Checking 50000 CAD @ 0.80 USD 2014/05/15 Transfer from Canada Assets:CA:Checking -50000 CAD @ 0.85 USD Assets:US:Checking 42500 USD This results in a trial balance with just 2500 USD: $ ledger -f l3.lgr bal 2500 USD Assets:US:Checking This is not a desirable outcome. We should require that the trial balance always sum to zero (except for virtual transactions). After all, this is the aggregate realization of the elegance of the double-entry method: because all transactions sum to zero, the total sum of any subgroup of transactions also sums to zero\u2026 except it doesn\u2019t. In my view, any balance sheet that gets rendered should comply with the accounting equation, precisely. In my explorations with Ledger, I was hoping that by virtue of its inventory tracking method it would somehow naturally deal with these conversions automatically and thus provide a good justification for keeping track of all units at cost, but I witness the same problem showing up. (The conversions issue has been a real bitch of a problem to figure out a solution for in Beancount, but it is working, and I think that the same solution could be applied to Ledger to adjust these sums, without changing its current booking method: automatically insert a special conversion entry at strategic points - when a balance sheet is rendered.) In the end, I have not found that tracking costs for every transaction would appear to have an advantage over forgetting the costs for price conversions, the conversions problem is independent of this. I\u2019d love to hear more support in favour of this design choice. Now, because all conversions maintain their cost, Ledger needs to be quite liberal about booking, because it would be inconvenient to force the user to specify the original rate of conversion for each currency, in the case of commonly occurring currency conversions. This has an unfortunate impact on those conversions which we do want to hold at cost: arbitrary lot reductions are allowed, that is, reductions against lots that do not exist. For example, the following trade does not trigger an error in Ledger: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of an impossible lot Assets:Investments:Stock -10 HOOL {505 USD} @ 510 USD Assets:Investments:Cash This results in an inventory with a long position of 10 HOOL and a short position of 10 HOOL (at a different cost): $ ledger -f l4.lgr bal --lots stock 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD505} Assets:Investments:Stock I think that the reduction of a 505 USD position of HOOL should not have been allowed; Beancount treats this as an error by choice. Ledger clearly appears to support booking against an existing inventory, so surely the intention was to be able to reduce against existing positions. The following transactions do result in an empty inventory, for instance: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot Assets:Investments:Stock -10 HOOL {500 USD} [2014/05/01] @ 510 USD Assets:Investments:Cash With output: $ ledger -f l5.lgr bal --lots USD100 Equity:Capital Gains As you see, the HOOL position has been eliminated. (Don\u2019t mind the auto-inserted equity entry in the output, I believe this will be fixed in a pending patch .) However, both the date and the cost must be specified for this to work. The following transactions results in a similar problematic long + short position inventory as mentioned above: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot? Assets:Investments:Stock -10 HOOL {500 USD} @ 510 USD Assets:Investments:Cash With output: $ ledger -f l6.lgr bal --lots 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} Assets:Investments:Stock USD100 Equity:Capital Gains -------------------- 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} USD100 This is a bit surprising, I expected the lots to book against each other. I suspect this may be an unreported bug, and not intended behaviour. Finally, the \u201c {} \u201d cost syntax is allowed be used on augmenting legs as well. The documentation points to these methods being equivalent . It results in an inventory lot that does not have a date associated with it, but the other leg is not converted to cost: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash With output: $ ledger -f l7.lgr bal --lots 0 Assets:Investments -10 HOOL {USD500} Cash 10 HOOL {USD500} Stock -------------------- 0 I probably just don\u2019t understand how this is meant to be used; in Beancount the automatically computed leg would have posted a -5000 USD amount to the Cash account, not shares. What I think is going on, is that Ledger (probably) accumulates all the lots without attempting to match them together to try to make reductions, and then at reporting time - and only then - it matches the lots together based on some reporting options: Group lots by both cost/price and date, using --lots Group lots by cost/price only, using --lot-prices Group lots by date only, using --lot-dates It is not entirely obvious from the documentation how that works, but the behavior is consistent with this. (If this is correct, I believe that to be a problem with its design: the mechanism by which an inventory reduction is booked to an existing set of lots should definitely not be a reporting feature. It needs to occur before processing reports, so that a single and definitive history of inventory bookings can be realized. If variants in bookings are supported - and I don\u2019t think this is a good idea - they should be supported before the reporting phase. In my view, altering inventory booking strategies from command-line options is a bad idea, the strategies in place should be fully defined by the language itself.)","title":"Shortcomings in Ledger"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#shortcomings-in-beancount","text":"Beancount also has various shortcomings, though different ones. In contrast to Ledger, Beancount disambiguates between currency conversions and conversions \u201cheld at cost\u201d: 2014-05-01 * \"Convert some Loonies to Franklins\" Assets:CA:Checking -6000 CAD Assets:Investments:Cash 5000 USD @ 1.2 CAD ; Currency conversion 2014-05-01 * \"Buy some stock\" Assets:Investments:Stock 10 HOOL {500 USD} ; Held at cost Assets:Investments:Cash In the first transaction, the Assets:Investment:Cash account results in an inventory of 5000 USD, with no cost associated to it. However, after the second transaction the Assets:Investment:Stock account has an inventory of 10 HOOL with a cost of 500 USD associated to it. This is perhaps a little bit more complex, and requires more knowledge from the user: there are two kinds of conversions and he has to understand and distinguish between these two cases, and this is not obvious for newcomers to the system. Some user education is required (I\u2019ll admit it would help if I wrote more documentation). Beancount\u2019s method is also less liberal, it requires a strict application of lot reduction. That is, if you enter a transaction that reduces a lot that does not exist, it will output an error. The motivation for this is to make it difficult for the user to make a mistake in data entry. Any reduction of a position in an inventory has to match against exactly one lot. There are a few downsides that result from this choice: It requires the users to always find the cost of the lot to match against. This means that automating the import of transactions requires manual intervention from the user, as he has to go search in the ledger to insert the matching lot. (Note that theoretically the import code could load up the ledger contents to list its holdings, and if unambiguous, could look for the cost itself and insert it in the output. But this makes the import code dependent on the user\u2019s ledger, which most import codes aren't doing). This is an important step, as finding the correct cost is required in order to correctly compute capital gains, but a more flexible method is desired, one that allows the user to be a bit more lazy and not have to put the cost of the existing position when the choice is obvious, e.g., when there is a single lot of that unit to match against. I\u2019d like to relax this method somewhat. The strict requirement to reduce against an existing lot also means that both long and short positions of the same commodities \u201cheld at cost\u201d in the same account cannot exist. This is not much of a problem in practice, however, because short positions are quite rare\u2014few individuals engage in them\u2014and the user could always create separate accounts to hold short positions if needed. In fact, it is already customary to create a dedicated subaccount for each commodity in an investment account, as it naturally organizes trading activity nicely (reports a bit of a mess otherwise, it\u2019s nice to see the total position value grouped by stock, since they offer exposure to different market characteristics). Different accounts should work well, as long as we treat the signs correctly in reductions, i.e., buying stock against an existing short position is seen as a position reduction (the signs are just reversed). Another problem with the Beancount booking method is in how it matches against lots with dates. For example, if you were to try to disambiguate between two lots at the same price, the most obvious input would not work: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash This would report a booking error. This is confusing. In this case Beancount, in its desire to be strict, applies strict matching against the inventory lots, and attempting to match units of a lot of (HOOL, 500 USD, 2012-05-01) with units of (HOOL, 500 USD, None ) simply fails. Note that the lot-date syntax is accepted by Beancount on both augmenting and reducing legs, so the \u201csolution\u201d is that the user is forced to specifically provide the lot-date on acquiring the position. This would work, for instance: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash The inconvenience here is that when the user entered the first trade, he could not predict that he would enter another trade at the same price in the future and typically would not have inserted the lot-date. More likely than not, the user had to go back and revisit that transaction in order to disambiguate this. We can do better. This is a shortcoming of its implementation, which reflects the fact that position reduction was initially regarded as an exact matching problem rather than a fuzzy matching one\u2014it wasn\u2019t clear to me how to handle this safely at the time. This needs to be fixed after this proposal, and what I\u2019m doing with this document is very much a process of clarifying for myself what I want this fuzzy matching to mean, and to ensure that I come up with an unambiguous design that is easy to enter data for. Ledger always automatically attaches the transaction date to the inventory lot, and in my view this is the correct thing to do (below we propose attaching more information as well).","title":"Shortcomings in Beancount"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#context","text":"[Updated on Nov\u20192014] It is important to distinguish between two types of booking: Booking. The strict type of booking that occurs when we are considering the entire list of transactions. We will call this the \u201cbooking\u201d process, and it should occur just once. This process should be strict: the failure of correctly book a reduction to an existing lot should trigger a fatal error. Inventory Aggregation. When we are considering a subset of entries, it is possible that entries that add some lots are removed, and that other entries that reduce or remove those lots are kept. This creates a situation in which it is necessary to support aggregations over time of changes to an inventory that may not admit a matching lot. If we are to support arbitrary subsets of transactions being aggregated, we must take this into account. This aggregation process should never lead to an error. Let\u2019s take an example to justify why we need the non-booking summarization. Let\u2019s say we have a stock purchase and sale: 2013-11-03 * \"Buy\" Assets:Investments:VEA 10 AAPL {37.45 USD} ;; (A) Assets:Investments:Cash -370.45 USD 2014-08-09 * \"Sell\" Assets:Investments:VEA -5 AAPL {37.45 USD} Assets:Investments:Cash 194.40 USD Income:Investments:PnL If we filter \u201cby year\u201d and only want to view transactions that occurred in 2014, AND we don\u2019t \u201cclose\u201d the previous year, that is, we do not create opening balances entries that would deposit the lots in the account at the beginning of the year - this could happen if we filter by tag, or by some other combination of conditions - then the (A) lot is not present in the inventory to be debited from. We must somehow accommodate a -5 AAPL at that point. When reporting, the user could decide how to summarize and \u201cmatch up as best it can\u201d those legs, but converting them to units or cost, or convert them to a single lot at average-cost. The booking process, however, should admit no such laxity. It should be strict and trigger an error if a lot cannot be matched. This would only be applied on the total set of transactions, and only once, never on any subset. The purpose of the booking stage should be threefold: Match against existing lots and issue errors when that is impossible. Do this, using the method specified by the user. Replace all partially specified lots by a full lot specification, the lot that was matched during the booking process. Insert links on transactions that form a trade: a buy and corresponding sells should be linked together. This essentially identifies trades, and can then be used for reporting later on. (Trades at average cost will require special consideration.) We can view Ledger\u2019s method as having an aggregation method only, lacking a booking stage. The particular method for aggregation is chosen using --lots or --lot-dates or --lot-prices. This would indicate that a \u201cbooking\u201d stage could be tacked on to Ledger without changing much of its general structure: it could be inserted as a pre- or post- process, but it would require Ledger to sort the transactions by date, something it does not currently do (it runs a single pass over its data). Beancount has had both methods for a while, but the separate nature of these two operations has not been explicitly stated so far\u2014instead, the inventory supported an optional flag to raise an error when booking was impossible. Also, inventory aggregation has not been thought as requiring much customization, it was meant to be unified to the booking process. It is now clear that they are two distinct algorithms, and that a few different methods for aggregating can be relevant (though they do not function the same as Ledger\u2019s do. Note: you can view the inventory aggregation method as a GROUP BY within an Inventory object\u2019s lots: which columns do you group over? This requires use cases). In any case, fully specified lots should match against each other by default: we need to support this as the degenerate case to use for simple currencies (not held at cost)... we would not want to accumulate all changes in the same currency as separate lots in an inventory, they need to cross each other as soon as they are applied. We want to change this in order to make the code clearer: there should be a separate \u201cbooking\u201d stage, provided by a plugin , which resolves the partially specific lot reduction using the algorithm outlined in this document, and a separate method for inventory aggregation should not bother with any of those details. In fact, the inventory aggregation could potentially simply become an accumulated list of lots, and the summarization of them could take place a posteriori, with some conceptual similarly to when Ledger applies its aggregation. Just using a simple aggregation becomes more relevant once we begin entering more specific data about lots, such as always having an acquisition date, a label, and possibly even a link to the entry that created the lot. In order to trigger summarization sensibly, functions to convert and summarize accumulated inventories could be provided in the shell, such as \u201cUNITS(inventory)\u201d.","title":"Context"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#precision-for-interpolation","text":"The interpolation capabilities will be extended to cover eliding a single number, any number, in any subset of postings whose \u201cweights\u201d resolve to a particular currency (e.g., \u201call postings with weights in USD\u201d). The interpolation needs to occur at a particular precision. The interpolated number should be quantized automatically, and the number of fractional digits to quantize it to should be automatically inferred from the DisplayContext which is itself derived from the input file. Either the most common or the maximum number of digits witnessed in the file should be used. For a use case, see this thread : On Mon, Nov 24, 2014 at 12:33 PM, ELI wrote: Regarding the last piece on which there seems to be a misunderstanding between you and I, let me provide an standalone example, outside the context of the plugin. Since Vanguard only makes three decimal places of precision available me, I need to have the actual share count calculated from the price and transaction amount. For example, I have a transaction on my activities page with these details: Shares Transacted: 0.000 Share Price: 48.62 Amount: 0.02 The only way to enter this transaction and have it balance would be to manually calculate the Shares Transacted with four decimal places of precision. My preferred way of entering this would be to enter the Share Price and Amount and have Beancount calculate Shares Transacted to a precision associated with my Vanguard account. Additionally, as metadata, I'd record \"Shares Transacted: 0.000\" as history of \"what the statement said\". Maybe you can give me your thoughts on how such a case would be addressed with planned Beancount features or as a plugin I could right? What does the downloadable file contain? 0.000 or 0.0004 or even 0.00041? What this is, most likely, is the quarterly fee that they take, which they price in terms of shares. If this is your Vanguard account, and you sum all the similar such transactions around it, you should observe that it sums to 5$ per quarter. I would log it like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX -0.00041 VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD The \"*\" will be required to merge all the lots into a single one (average cost booking) because the 48.62 USD they declare is _not_ one of your lots, but rather just the price of the asset that happened to be there on the day they took the fee. In other words, they take their fee in terms of units of each asset, priced at the current price, deducting against the cost basis of all your lots together (it doesn't matter which because this is a pre-tax account, so no capital gains are reported). Now you're asking, how could I avoid calculating 0.00041 myself? My first answer would be: let's first see if the OFX download from Vanguard includes the precision. That would solve it. I believe it does (I checked my own history of downloads and I have some numbers in there with 4 fractional digits). My second answer would be that - if you don't have the number of shares from the file - after I implement the much needed new inventory booking proposal ( http://furius.ca/beancount/doc/proposal-booking ), the interpolation capabilities will be vastly extended, such that eliding the number of shares would even be possible, like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD Now this by itself still would not solve the problem: this would store 0.000411353353 which is limited at 12 fractional digits because of the default context. So that's incorrect. What would need to be done to deal with this is to infer the most common number of digits used on units of VIIPX and to quantize the result to that number of digits (I think this could be safe enough). The number of digits appearing in the file for each currency is already tracked in a DisplayContext object that is stored in the options_map from the parser. I'll have to take that into account in the inventory booking proposal. I'm adding this to the proposal.","title":"Precision for Interpolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#requirements","text":"The previous sections introduce the general problem of booking, and point out important shortcomings in both the Beancount and Ledger implementations. This section will present a set of desires for a new and improved booking mechanism for command-line bookkeeping software implementations. Here are reasonable requirements for a new method: Explicit inventory lot selection needs to be supported. The user should be able to indicate precisely which of the lots to reduce a position against, per-transaction. An account or commodity should not have to have a consistent method applied across all its trades. Explicit inventory lot selection should support partial matching , that is, in cases where the lots to match against are ambiguous, the user should be able to supply only minimal information to disambiguate the lots, e.g., the date, or a label, or the cost, or combinations of these. For instance, if only a single lot is available to match against, the user should be able to specify the cost as \u201c {} \u201d, telling the system to book at cost, but essentially saying: \u201cbook against any lot.\u201d This should trigger an error only if there are multiple lots. A variety of informations should be supported to specify an existing lot, and they should be combinable: The cost of acquisition of the position; The date the position was acquired at; A user-provided label. Cases where insufficient information is provided for explicit lot selection is available should admit for a default booking method to be invoked. The user should be able to specify globally and per-account what the default booking method should be. This paves the way for implicit lot selection . A degenerate method should be available that kicks off an error and requires the user to be explicit. Average cost booking needs to be supported, as it is quite common in retirement or tax-sheltered accounts. This is the default method used in Canadian investment accounts. Cost basis adjustments need to be supported as well. The problem should be able to be specified by providing a specific lot (or all of a commodity\u2019s lots at average cost) and a dollar amount to adjust the position by. Stock splits need to be able to maintain some of the original attributes of the position , specifically, the original trade date and the user-specified label, if one has been provided. This should allow a common syntax to specify an original trading lot when reducing a position after a split. Reducing a position needs to always book against an existing lot . I\u2019m preserving the Beancount behavior here, which I\u2019ve argued for previously, as it works quite well and is a great method to avoid data entry mistakes. This naturally defines an invariant for inventories: all positions of a particular commodity held at cost should be of the same sign in one inventory (this can be used as a sanity check). Bookings that change the sign of a number of units should raise an error , unless an explicit exception is requested (and I\u2019m not even convinced that we need it). Note again that bookings only occur for units held at cost, so currency conversions are unaffected by this requirement. Beancount has had this feature for a while, and it has proved useful to detect errors. For example, if an inventory has a position of 8 HOOL {500 USD} you attempt to post a change of -10 units to this lot, the resulting number of units is now negative: -2. This should indicate user error. The only use case I can think for allowing this is the trading of futures spreads and currencies, which would naturally be reported in the same account (a short position in currencies is not regarded as a different instrument in the same way that a short position would); this is the only reason to provide an exception, and I suspect that 99% of users will not need it.]","title":"Requirements"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#debugging-tools","text":"Since this is a non-trivial but greatly important part of the process of entering trading data, we should provide tools that list in detail the running balance for an inventory, including all of the detail of its lots. Booking errors being reported should include sufficient context, that is: The transaction with offending posting The offending posting The current state of the inventory before the posting is applied, with all its lots The implicit booking method that is in effect to disambiguate between lots A detailed reason why the booking failed","title":"Debugging Tools"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-vs-implicit-booking-reduction","text":"Another question that may come up in the design of a new booking method is whether we require the user to be explicit about whether he thinks this is an addition to a position, or a reduction. This could be done, for instance, by requiring a slightly different syntax for the cost, for example \u201c {} \u201d would indicate an addition and \u201c [] \u201d a reduction. I\u2019m not convinced that it is necessary to make that distinction, maybe the extra burden on the user is not worth it, but it might be a way to cross-check an expectation against the actual calculations that occur. I\u2019ll drop the idea for now.","title":"Explicit vs. Implicit Booking Reduction"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#design-proposal","text":"This section presents a concrete description of a design that fulfills the previously introduced requirements. We hope to keep this as simple as possible.","title":"Design Proposal"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inventory","text":"An inventory is simply a list of lot descriptors. Each inventory lot will be defined by: UNITS, (CURRENCY, COST-UNITS, COST-CURRENCY, DATE, LABEL) Fields: UNITS: the number of units of that lot that are held CURRENCY: the type of commodity held, a string COST-UNITS: the number in the price of that lot COST-CURRENCY: the pricing commodity of the cost DATE: the acquisition date of the lot LABEL: an arbitrary user-specified string used to identify this lot If this represents a lot held at cost, after this processing, only the LABEL field should be optionally with a NULL value. Anyone writing code against this inventory could expect that all other values are filled in with non-NULL values (or are otherwise all NULL).","title":"Inventory"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#input-syntax-filtering","text":"The input syntax should allow the specification of cost as any combination of the following informations: The cost per unit , as an amount, such as \u201c 500 USD \u201d A total cost , to be automatically divided by the number of units, like this: {... +9.95 USD} . You should be able to use either cost per unit, total cost, or even combine the two, like this: {500 + 9.95 USD} . This is useful to enter commissions. This syntax also replaces the previous {{ ... }} syntax. The lot-date , as a YYYY-MM-DD date, such as \u201c 2014-06-20 \u201d A label , which is any non-numerical identifier, such as first-apple or some random uuid like \u201c aa2ba9695cc7 \u201d A special marker \u201c * \u201d that indicates to book at the average cost of the inventory balance These following postings should all be valid syntax: ... Assets:Investments:Stock 10 HOOL {500 USD} ; cost Assets:Investments:Stock 10 HOOL {339999615d7a} ; label Assets:Investments:Stock 10 HOOL {2014-05-01} ; lot-date Assets:Investments:Stock 10 HOOL {} ; no information ... Combinations Assets:Investments:Stock 10 HOOL {500 USD, 2014-05-01} Assets:Investments:Stock 10 HOOL {2014-05-01, 339999615d7a}","title":"Input Syntax & Filtering"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#algorithm","text":"All postings should be processed in a separate step after parsing, in the order of their date, against a running inventory balance for each account. The cost amount should become fully determined at this stage, and if we fail to resolve a cost, an error should be raised and the transaction deleted from the flow of directives (after the program loudly complaining about it). When processing an entry, we should match and filter all inventory lots against all the filters that are provided by the user. In general, after filtering the lots with the user restrictions: If the set of lots is empty, an error should be raised; If there is a single lot, this lot should be selected (success case); If there are multiple lots, the default implicitly booking method for the corresponding account should be invoked.","title":"Algorithm"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#implicit-booking-methods","text":"If there are multiple matching lots to choose from during booking, the following implicit booking methods could be invoked: STRICT: Select the only lot of the inventory. If there are more than one lots, raise an error. FIFO: Select the lot with the earliest date. LIFO: Select the lot with the latest date. AVERAGE: Book this transaction at average cost. AVERAGE_ONLY: Like AVERAGE but also trigger an aggregation on an addition to a position. This can be used to enforce that the only booking method for all lots is at the average cost. NONE: Don\u2019t perform any inventory booking on this account. Allow a mix of lots for the same commodity or positive and negative numbers in the inventory. (This essentially devolves to the Ledger method of booking.) This method would only get invoked if disambiguation between multiple lots is required, after filtering the lots against the expression provided by the user. The STRICT method is basically the degenerate disambiguation which issues an error if there is any ambiguity and should be the default. There should be a default method that applies to all accounts. The default value should be overridable. In Beancount, we would add a new \u201coption\u201d to allow the user to change this: option \"booking_method\" \"FIFO\" The default value used in a particular account should be specifiable as well, because it is a common case that the method varies by account, and is fixed within the account. In Beancount, this would probably become part of the open directive, something like this: 2003-02-17 open Assets:Ameritrade:HOOL HOOL booking:FIFO (I\u2019m not sure about the syntax.)","title":"Implicit Booking Methods"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#resolving-same-date-ambiguity","text":"If the automatic booking method gets invoked to resolve an ambiguous lot reduction, e.g. FIFO, if there are multiple lots at the same date, something needs to be done to resolve which of the lots is to be chosen. The line number at which the transaction appears should be selected. For example, in the following case, a WIDGET Of 8 GBP would be selected: 2014-10-15 * \"buy widgets\" Assets:Inventory 10 WIDGET {} ;; Price inferred to 8 GBP/widget Assets:Cash -80 GBP 2014-10-15 * \"buy another widget\" Assets:Inventory 1 WIDGET {} ;; Price inferred to 9 GBP/widget Assets:Cash -9 GBP 2014-10-16 * \"sell a widget\" Assets:Cash 11 GBP Assets:Inventory -1 WIDGET {} ;; Ambiguous lot","title":"Resolving Same Date Ambiguity"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#dates-inserted-by-default","text":"By default, if an explicit date is not specified in a cost, the date of the transaction that holds the posting should be attached to the trading lot automatically. This date is overridable so that stock splits may be implemented by a transformation to a transaction like this: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD, 2014-01-04} ; reducing Assets:Investments:Stock 10 HOOL {500.00 USD, 2014-01-04} ; augment Assets:Investments:Stock 10 HOOLL {500.00 USD, 2014-01-04} ; augment","title":"Dates Inserted by Default"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#matching-with-no-information","text":"Supplying no information for the cost should be supported and is sometimes useful: in the case of augmenting a position, if all the other legs have values specified, we should be able to automatically infer the cost of the units being traded: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {} Assets:Investments:Cash -5009.95 USD Expenses:Commissions 9.95 USD In the case of reducing a position\u2014and we can figure that out whether that is the case by looking at the inventory at the point of applying the transaction to its account balance\u2014an empty specification should trigger the default booking method. If the method is STRICT, for instance, this would select a lot only if there is a single lot available (the choice is unambiguous), which is probably a common case if one trades infrequently. Other methods will apply as they are defined. Note that the user still has to specify a cost of \u201c {} \u201d in order to inform us that this posting has to be considered \u201cheld at cost.\u201d This is important to disambiguate from a price conversion with no associated cost.","title":"Matching with No Information"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#reducing-multiple-lots","text":"If a single trade needs to close multiple existing lots of an inventory, this can dealt with trivially by inserting one posting for each lot. I think this is a totally reasonable requirement. This could represent a single trade, for instance, if your brokerage allows it: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -10 HOOL {500 USD} Assets:Investments:Stock -12 HOOL {510 USD} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains Note: If the cost basis specification for the lot matches multiple lots of the inventory and the result is unambiguous, the lots will be correctly selected. For example, if the ante-inventory contains just those two lots (22 HOOL in total), you should be able to have a single reducing posting like this: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -22 HOOL {} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains and they will both be included. On the other hand, if the result is ambiguous (for example, if you have more than these two lots) the booking strategy for that account would be invoked. By default, this strategy will be \"STRICT\" which will generate an error, but if this account's strategy is set to \"FIFO\" (or is not set and the default global strategy is \"FIFO\"), the FIFO lots would be automatically selected for you, as per your wish.","title":"Reducing Multiple Lots"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#lot-basis-modification","text":"Another idea is to support the modification of a specific lot\u2019s cost basis in a single leg. The way this could work, is by specifying the number of units to modify, and the \u201c+ number currency\u201d syntax would be used to adjust the cost basis by a specific number, like this: 2012-05-01 * \"Adjust cost basis by 250 USD for 5 of the 500 USD units\" Assets:Investments:Stock ~5 HOOL {500 + 250 USD} If the number of units is smaller than the total number of units in the lot, split out the lot before applying the cost basis adjustment. I\u2019m not certain I like this. Not specifying the size could also adjust the entire position (I\u2019m not sure if this makes sense as it departs from the current syntax significantly): 2012-05-01 * \"Adjust cost basis by 250 USD for the 500 USD units\" Assets:Investments:Stock HOOL {500 + 250 USD} In any case, all the fields other than the total cost adjustment would be used to select which lot to adjust.","title":"Lot Basis Modification"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#tag-reuse","text":"We will have to be careful in the inventory booking to warn on reuse of lot labels. Labels should be unique.","title":"Tag Reuse"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#examples","text":"Nothing speaks more clearly than concrete examples. If otherwise unspecified, we are assuming that the booking method on the Assets:Investments:Stock account is STRICT .","title":"Examples"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#no-conflict","text":"Given the following inventory lots: 21, (HOOL, 500, USD, 2012-05-01, null) 22, (AAPL, 380, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {} ... This booking should fail (because the amount is not one of the valid lots): 2013-05-01 * Assets:Investments:Stock -10 HOOL {520 USD} ... This one too should fail (no currency matching lot): 2013-05-01 * Assets:Investments:Stock -10 MSFT {80 USD} ... So should this one (invalid date when a date is specified): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2010-01-01} ...","title":"No Conflict"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-cost","text":"Given the following inventory: 21, (HOOL, 500, USD, 2012-05-01, null) 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 25, (HOOL, 510, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {510 USD} ... This booking should fail if the stock account\u2019s method is STRICT (ambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... This booking should succeed if the stock account\u2019s method is FIFO, booking against the lot at 2012-05-01 (earliest): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ...","title":"Explicit Selection By Cost"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-date","text":"Given the same inventory as previously, this booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-05-01} ... This booking should fail if the method is STRICT (ambiguous) 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-06-01} ...","title":"Explicit Selection By Date"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-label","text":"This booking should succeed, because there is a single lot with the \u201cabc\u201c label: 2013-05-01 * Assets:Investments:Stock -10 HOOL {abc} ... If multiple lots have the same label, ambiguous cases may occur; with this inventory, for example: 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 31, (HOOL, 510, USD, 2012-07-01, \u201cabc\u201d) The same booking should fail.","title":"Explicit Selection By Label"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-combination","text":"With the initial inventory, this booking should succeed (unambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} ... There is only one lot at a cost of 500 USD and at an acquisition date of 2012-06-01.","title":"Explicit Selection By Combination"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#not-enough-units","text":"The following booking would be unambiguous, but would fail, because there aren\u2019t enough units of the lot in the inventory to subtract from: 2013-05-01 * Assets:Investments:Stock -33 HOOL {500 USD, 2012-06-01} \u2026","title":"Not Enough Units"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#redundant-selection-of-same-lot","text":"If two separate postings select the same inventory lot in one transaction, it should be able to work: 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -10 HOOL {abc} ... Of course, if there are not enough shares, it should fail: 2013-05-01 * Assets:Investments:Stock -20 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -20 HOOL {abc} ...","title":"Redundant Selection of Same Lot"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#automatic-price-extrapolation","text":"If all the postings of a transaction can have their balance computed, we allow a single price to be automatically calculated - this should work: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD And result in the equivalent of: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {534.51 USD} Income:US:Invest:Gains -340.51 USD Of course, preserving the original date of the lot should work too, so this should work as well: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {2014-02-04} Income:US:Invest:Gains -340.51 USD","title":"Automatic Price Extrapolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#average-cost-booking","text":"Augmenting lots with the average cost syntax should fail: 2014-03-15 * \"Buying at average cost, what does this mean?\" Assets:US:Invest:Stock 10.00 HOOL {*} Income:US:Invest:Gains -5000.00 USD Reducing should work, this is the main use case: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {510.00 USD} Assets:US:Invest:Cash -5100.00 USD 2014-04-28 * \"Obtaining a dividend in stock\" Assets:US:Invest:Stock 1.00 HOOL {520.00 USD} Income:US:Invest:Dividends -520.00 USD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains ; Automatically calculated: -194.29 USD The effect would be to aggregate the 10 HOOL {500.00 USD} lot, the 10 HOOL {510.00 USD} lot, and the 1.00 HOOL {520.00 USD} lot, together into a single lot of 21 HOOL {505.714285 USD}, and to remove 8 HOOL from this lot, which is 8 HOOL x 505.714285 USD/HOOL = 4045.71 USD. We receive 4240.00 USD, which allows us to automatically compute the capital gain: 4240.00 - 4045.71 = 194.29 USD. After the reduction, a single lot of 13 HOOL {505.714285 USD} remains. It should also work if we have multiple currencies in the account, that is adding the following transaction to the above should not make it faile: 2014-04-15 * \"Buying another stock\" Assets:US:Invest:Stock 15.00 AAPL {300.00 USD} Assets:US:Invest:Cash -4500.00 USD However it should fail if we have the same currency priced in distinct cost currencies in the same account: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {623.00 CAD} Assets:US:Invest:Cash -62300.00 CAD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} ; Which HOOL, USD or CAD? Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains But of course, this never happens in practice, so I\u2019m not too concerned. We could potentially support specifying the cost-currency to resolve this case, that is, replacing the sell leg with this: 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {* USD} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains I\u2019m quite sure it wouldn\u2019t be useful, but we could go the extra mile and be as general as we can possibly be.","title":"Average Cost Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#future-work","text":"This proposal does not yet deal with cost basis re-adjustments! We need to way to be able to add or remove a fixed dollar amount to a position\u2019s cost basis, that is, from (1) a lot identifier and (2) a cost-currency amount, we should be able to update the cost of that lot. The transaction still has to balance. Here are some ideas what this might look like: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD + 230.00 USD} Income:US:Invest:Gains -230.00 USD The resulting inventory would be 10 HOOL at 523.00 USD per share, and the rest of the original lots, less 10 HOOL at 500.00 USD per share (there might be some of that remaining, that\u2019s fine). This is the equivalent of 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500.00 USD} Assets:US:Invest:Stock 10.00 HOOL {523.00 USD} Income:US:Invest:Gains -230.00 USD except you did not have to carry out the calculation. This should therefore be implemented as a transformation. However, this is not super useful, because this is already supported... adjustments are usually performed on lots at average cost, and this is where the problem lie: you\u2019d have to make the calculation yourself! Here\u2019s a relevant use case: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD This would first average all the lots of HOOL together and recompute the cost basis with the new amount. This should work: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 5.00 HOOL {500.00 USD} Assert:US:Invest:Cash -2500.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 5.00 HOOL {520.00 USD} Assets:US:Invest:Cash -2600.00 USD 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Notice how we still have to specify an amount of HOOL shares to be repriced. I like this, but I\u2019m wondering if we should allow specifying \u201call the units\u201d like this (this would be a more radical change to the syntax): 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock * HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Perhaps the best way to auto-compute cost basis adjustments would be via powerful interpolation, like this: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500 USD} ; reduce Assets:US:Invest:Stock 10.00 HOOL {} ; augment, interpolated Income:US:Invest:Gains -230.00 USD See the Smarter Elision document for more details on a proposal.","title":"Future Work"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inter-account-booking","text":"Some tax laws require a user to book according to a specific method, and this might apply between all accounts. This means that some sort of transfer needs to be applied in order to handle this correctly. See the separate document for detail. TODO - complete this with more tests for average cost booking!","title":"Inter-Account Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#implementation-notes","text":"","title":"Implementation Notes"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#separate-parsing-interpolation","text":"In order to implement a syntax for reduction that does not specify the cost, we need to make changes to the parser. Because there may be no costs on the posting, it becomes impossible to perform balance checks at parsing time. Therefore, we will need to postpone balance checks to a stage after parsing. This is reasonable and in a way nice: it is best if the Beancount parser does not output many complex errors at that stage. A new \u201cpost-parse\u201d stage will be added, right before running the plugins.","title":"Separate Parsing & Interpolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#conclusion","text":"This document is work-in-progress. I\u2019d really love to get some feedback in order to improve the suggested method, which is why I put this down in words instead of just writing the code. I think a better semantic can be reached by iterating on this design to produce something that works better in all systems, and this can be used as documentation later on for developers to understand why it is designed this way. Your feedback is much appreciated.","title":"Conclusion"},{"location":"balance_assertions_in_beancount.html","text":"Balance Assertions in Beancount \uf0c1 Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-balance This document summarizes the different kinds of semantics for balance assertions in all command-line accounting systems and proposes new syntax for total and file-order running assertions in Beancount. Motivation Partial vs. Complete Assertions File vs. Date Assertions Ordering & Ambiguity Intra-Day Assertions Beginning vs. End of Day Status Proposal File Assertions Complete Assertions Motivation \uf0c1 Both Beancount and Ledger implement balance assertions. These provide the system with checkpoints it can use to verify the integrity of the data entry 1 . Traditional accounting principles state that a user may never change the past\u2014correcting the past involves inserting new entries to undo past mistakes 2 \u2014but we in the command-line accounting community take issue with that: we want to remain able to reconstruct the past and more importantly, to correct past mistakes at the site of their original data entry. Yes, we are dilettantes but we are bent on simplifying the process of bookkeeping by challenging existing concepts and think that this is perfectly reasonable, as long as it does not break known balances. These known balances are what we provide via balance assertions. Another way to look at these balance assertions, is that they are simply the bottom line amounts reported on various account statements, as exemplified in the figure below. Following this thread , we established that there were differing interpretations of balance assertions in the current versions of Ledger (3.0) and Beancount (2.0b). Partial vs. Complete Assertions \uf0c1 An assertion in Beancount currently looks like this: 2012-02-04 balance Assets:CA:Bank:Checking 417.61 CAD The meaning of this directive is: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains exactly 417.61 units of CAD at the end of February 3, 2012\u201d (so it\u2019s dated on the 4th because in Beancount balance assertions are specified at the beginning of the date). It says nothing about other commodities in the account\u2019s inventory. For example, if the account contains units of \u201cUSD\u201d, those are unchecked. We will call this interpretation a partial balance assertion or single-commodity assertion . An alternative assertion would be exhaustive: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains only 417.61 units of CAD at the end of February 3, 2012.\u201d We call this a complete balance assertion . In order to work, this second type of assertion would have to support the specification of the full contents of an inventory. This is currently not supported. Further note that we are not asserting the cost basis of an inventory, just the number of units. File vs. Date Assertions \uf0c1 There are two differing interpretations and implementations of the running balances for assertions: Beancount first sorts all the directives and verifies the balance at the beginning of the date of the directive. In the previous example, that is \u201c the balance before any transactions on 2012-02-04 are applied.\u201d We will call this date assertions or date-based assertions . Ledger keeps a running balance of each account\u2019s inventory during its parsing phase and performs the check at the site of the assertion in the file. We will call this file assertions or file-order, or file-based assertions . This kind of assertion has no date associated with it (this is slightly misleading in Ledger because of the way assertions are specified, as attached to a transaction\u2019s posting, which appears to imply that they occur on the transaction date, but they don\u2019t, they strictly apply to the file location). Ordering & Ambiguity \uf0c1 An important difference between those two types of assertions is that file-based assertions are not order-independent. For example, take the following input file: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 If you move the credit card payment up to the credit card account section, the same set of transactions fails: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary This subtle problem could be difficult for beginners to understand. Moving the file assertion to its own, undated directive might be more indicative syntax of its semantics, something that would look like this: balance Assets:Checking = $3008.67 The absence of a date indicates that the check is not applied at a particular point in time. Intra-Day Assertions \uf0c1 On the other hand, date-based assertions, because of their order-independence, preclude intra-day assertions, that is, a balance assertion that occurs between two postings on the same account during the same day. Beancount does not support intra-day assertions at the moment. Note despite this shortcoming, this has not been much of a problem, because it is often possible to fudge the date where necessary by adding or removing a day, or even just skipping an assertion where it would be impossible (skipping assertions is not a big deal, as they are optional and their purpose is just to provide certainty. As long as you have one that appears some date after the skipped one, it isn\u2019t much of an issue). It would be nice to find a solution to intra-day assertions for date-based assertions, however. One interesting idea would be to extend the semantics to apply the balance in file order within the set of all transactions directly before and after the balance check that occur on the same date as the balance check, for example, this would balance: 2013-05-05 balance Assets:Checking 100 USD 2013-05-20 * \"Interest payment\" Assets:Checking 12.01 USD Income:Interest 2013-05-20 balance Assets:Checking 112.01 USD 2013-05-20 * \"Check deposit\" Assets:Checking 731.73 USD Assets:Receivable The spell would be broken as soon as a directive would appear at a different date. Another idea would be to always sort the balance assertions in file-order as the second sort key (after the date) and apply them as such. I\u2019m not sure this would be easy to understand though. Beginning vs. End of Day \uf0c1 Finally, just for completeness, it is worth mentioning that date assertions have to have well-defined semantics regarding when they apply during the day. In Beancount, they currently apply at the beginning of the day. It might be worthwhile to provide an alternate version of date-based assertions that applies at the end of the day, e.g. \u201cbalance_end\u201d. Beancount v1 used to have this (\u201ccheck_end\u201d) but it was removed in the v2 rewrite, as it wasn\u2019t clear it would be really needed. The simplicity of a single meaning for balance assertions is nice too. Status \uf0c1 Ledger 3.0 currently supports only partial file-order assertions, on transactions. Beancount 2.0 currently supports only partial date-based assertions at the beginning of the day. Proposal \uf0c1 I propose the following improvements to Beancount\u2019s balance assertions. File Assertions \uf0c1 File assertions should be provided as a plugin. They would look like this: 2012-02-03 file_balance Assets:CA:Bank:Checking 417.61 CAD Ideally, in order to make it clear that they apply strictly in file order, they would not have a date, something like this: file_balance Assets:CA:Bank:Checking 417.61 CAD But this breaks the regularity of the syntax for all other directives. It also complicates an otherwise very regular and simple parser just that much more\u2026 all other directives begin with a date and a word, and all other lines are pretty much ignored. It would be a bit of a departure from this. Finally, it would still be nice to have a date just to insert those directives somewhere in the rendered journal. So I\u2019m considering keeping a date for it. If you decide to use those odd assertions, you should know what they mean. I also don\u2019t like the idea of attaching assertions to transactions; transaction syntax is already busy enough, it is calling to remain simple. This should be a standalone directive that few people use. In order to implement this, the plugin would simply resort all the directives according to their file location only (using the fileloc attribute of entries), disregarding the date, and recompute the running balances top to bottom while applying the checks. This can entirely be done via post-processing, like date-based assertions, without disturbing any of the other processing. Moreover, another advantage of doing this in a plugin is that people who don\u2019t use this directive won\u2019t have to pay the cost of calculating these inventories. Complete Assertions \uf0c1 Complete assertions should be supported in Beancount by the current balance assertion directive. They aren\u2019t very important but are potentially useful. Possible syntax ideas: 2012-02-03 balance* Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking = 417.61 CAD, 162 USD 2012-02-03 balance full Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking 417.61 CAD Assets:CA:Bank:Checking 162 USD I\u2019m still undecided which is best. So far it seems a matter of taste. As far as we know, the notion of inputting an explicit expected amount is unique to command-line accounting systems. Other systems \u201creconcile\u201d by freezing changes in the past. \u21a9 There are multiple reasons for this. First, in pre-computer times, accounting was done using books, and recomputing running balances manually would have involved making multiple annoying corrections to a book. This must have been incredibly inconvenient, and inserting correcting entries at the current time is a lot easier. Secondly, if your accounting balances are used to file taxes, changing some of the balances retroactively makes it difficult to go back and check the detail of reported amounts in case of an audit. This problem also applies to our context, but whether a past correction should be allowed is a choice that depends on the context and the particular account, and we leave it up to the user to decide whether it should be allowed. \u21a9","title":"Balance Assertions in Beancount"},{"location":"balance_assertions_in_beancount.html#balance-assertions-in-beancount","text":"Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-balance This document summarizes the different kinds of semantics for balance assertions in all command-line accounting systems and proposes new syntax for total and file-order running assertions in Beancount. Motivation Partial vs. Complete Assertions File vs. Date Assertions Ordering & Ambiguity Intra-Day Assertions Beginning vs. End of Day Status Proposal File Assertions Complete Assertions","title":"Balance Assertions in Beancount"},{"location":"balance_assertions_in_beancount.html#motivation","text":"Both Beancount and Ledger implement balance assertions. These provide the system with checkpoints it can use to verify the integrity of the data entry 1 . Traditional accounting principles state that a user may never change the past\u2014correcting the past involves inserting new entries to undo past mistakes 2 \u2014but we in the command-line accounting community take issue with that: we want to remain able to reconstruct the past and more importantly, to correct past mistakes at the site of their original data entry. Yes, we are dilettantes but we are bent on simplifying the process of bookkeeping by challenging existing concepts and think that this is perfectly reasonable, as long as it does not break known balances. These known balances are what we provide via balance assertions. Another way to look at these balance assertions, is that they are simply the bottom line amounts reported on various account statements, as exemplified in the figure below. Following this thread , we established that there were differing interpretations of balance assertions in the current versions of Ledger (3.0) and Beancount (2.0b).","title":"Motivation"},{"location":"balance_assertions_in_beancount.html#partial-vs-complete-assertions","text":"An assertion in Beancount currently looks like this: 2012-02-04 balance Assets:CA:Bank:Checking 417.61 CAD The meaning of this directive is: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains exactly 417.61 units of CAD at the end of February 3, 2012\u201d (so it\u2019s dated on the 4th because in Beancount balance assertions are specified at the beginning of the date). It says nothing about other commodities in the account\u2019s inventory. For example, if the account contains units of \u201cUSD\u201d, those are unchecked. We will call this interpretation a partial balance assertion or single-commodity assertion . An alternative assertion would be exhaustive: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains only 417.61 units of CAD at the end of February 3, 2012.\u201d We call this a complete balance assertion . In order to work, this second type of assertion would have to support the specification of the full contents of an inventory. This is currently not supported. Further note that we are not asserting the cost basis of an inventory, just the number of units.","title":"Partial vs. Complete Assertions"},{"location":"balance_assertions_in_beancount.html#file-vs-date-assertions","text":"There are two differing interpretations and implementations of the running balances for assertions: Beancount first sorts all the directives and verifies the balance at the beginning of the date of the directive. In the previous example, that is \u201c the balance before any transactions on 2012-02-04 are applied.\u201d We will call this date assertions or date-based assertions . Ledger keeps a running balance of each account\u2019s inventory during its parsing phase and performs the check at the site of the assertion in the file. We will call this file assertions or file-order, or file-based assertions . This kind of assertion has no date associated with it (this is slightly misleading in Ledger because of the way assertions are specified, as attached to a transaction\u2019s posting, which appears to imply that they occur on the transaction date, but they don\u2019t, they strictly apply to the file location).","title":"File vs. Date Assertions"},{"location":"balance_assertions_in_beancount.html#ordering-ambiguity","text":"An important difference between those two types of assertions is that file-based assertions are not order-independent. For example, take the following input file: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 If you move the credit card payment up to the credit card account section, the same set of transactions fails: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary This subtle problem could be difficult for beginners to understand. Moving the file assertion to its own, undated directive might be more indicative syntax of its semantics, something that would look like this: balance Assets:Checking = $3008.67 The absence of a date indicates that the check is not applied at a particular point in time.","title":"Ordering & Ambiguity"},{"location":"balance_assertions_in_beancount.html#intra-day-assertions","text":"On the other hand, date-based assertions, because of their order-independence, preclude intra-day assertions, that is, a balance assertion that occurs between two postings on the same account during the same day. Beancount does not support intra-day assertions at the moment. Note despite this shortcoming, this has not been much of a problem, because it is often possible to fudge the date where necessary by adding or removing a day, or even just skipping an assertion where it would be impossible (skipping assertions is not a big deal, as they are optional and their purpose is just to provide certainty. As long as you have one that appears some date after the skipped one, it isn\u2019t much of an issue). It would be nice to find a solution to intra-day assertions for date-based assertions, however. One interesting idea would be to extend the semantics to apply the balance in file order within the set of all transactions directly before and after the balance check that occur on the same date as the balance check, for example, this would balance: 2013-05-05 balance Assets:Checking 100 USD 2013-05-20 * \"Interest payment\" Assets:Checking 12.01 USD Income:Interest 2013-05-20 balance Assets:Checking 112.01 USD 2013-05-20 * \"Check deposit\" Assets:Checking 731.73 USD Assets:Receivable The spell would be broken as soon as a directive would appear at a different date. Another idea would be to always sort the balance assertions in file-order as the second sort key (after the date) and apply them as such. I\u2019m not sure this would be easy to understand though.","title":"Intra-Day Assertions"},{"location":"balance_assertions_in_beancount.html#beginning-vs-end-of-day","text":"Finally, just for completeness, it is worth mentioning that date assertions have to have well-defined semantics regarding when they apply during the day. In Beancount, they currently apply at the beginning of the day. It might be worthwhile to provide an alternate version of date-based assertions that applies at the end of the day, e.g. \u201cbalance_end\u201d. Beancount v1 used to have this (\u201ccheck_end\u201d) but it was removed in the v2 rewrite, as it wasn\u2019t clear it would be really needed. The simplicity of a single meaning for balance assertions is nice too.","title":"Beginning vs. End of Day"},{"location":"balance_assertions_in_beancount.html#status","text":"Ledger 3.0 currently supports only partial file-order assertions, on transactions. Beancount 2.0 currently supports only partial date-based assertions at the beginning of the day.","title":"Status"},{"location":"balance_assertions_in_beancount.html#proposal","text":"I propose the following improvements to Beancount\u2019s balance assertions.","title":"Proposal"},{"location":"balance_assertions_in_beancount.html#file-assertions","text":"File assertions should be provided as a plugin. They would look like this: 2012-02-03 file_balance Assets:CA:Bank:Checking 417.61 CAD Ideally, in order to make it clear that they apply strictly in file order, they would not have a date, something like this: file_balance Assets:CA:Bank:Checking 417.61 CAD But this breaks the regularity of the syntax for all other directives. It also complicates an otherwise very regular and simple parser just that much more\u2026 all other directives begin with a date and a word, and all other lines are pretty much ignored. It would be a bit of a departure from this. Finally, it would still be nice to have a date just to insert those directives somewhere in the rendered journal. So I\u2019m considering keeping a date for it. If you decide to use those odd assertions, you should know what they mean. I also don\u2019t like the idea of attaching assertions to transactions; transaction syntax is already busy enough, it is calling to remain simple. This should be a standalone directive that few people use. In order to implement this, the plugin would simply resort all the directives according to their file location only (using the fileloc attribute of entries), disregarding the date, and recompute the running balances top to bottom while applying the checks. This can entirely be done via post-processing, like date-based assertions, without disturbing any of the other processing. Moreover, another advantage of doing this in a plugin is that people who don\u2019t use this directive won\u2019t have to pay the cost of calculating these inventories.","title":"File Assertions"},{"location":"balance_assertions_in_beancount.html#complete-assertions","text":"Complete assertions should be supported in Beancount by the current balance assertion directive. They aren\u2019t very important but are potentially useful. Possible syntax ideas: 2012-02-03 balance* Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking = 417.61 CAD, 162 USD 2012-02-03 balance full Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking 417.61 CAD Assets:CA:Bank:Checking 162 USD I\u2019m still undecided which is best. So far it seems a matter of taste. As far as we know, the notion of inputting an explicit expected amount is unique to command-line accounting systems. Other systems \u201creconcile\u201d by freezing changes in the past. \u21a9 There are multiple reasons for this. First, in pre-computer times, accounting was done using books, and recomputing running balances manually would have involved making multiple annoying corrections to a book. This must have been incredibly inconvenient, and inserting correcting entries at the current time is a lot easier. Secondly, if your accounting balances are used to file taxes, changing some of the balances retroactively makes it difficult to go back and check the detail of reported amounts in case of an audit. This problem also applies to our context, but whether a past correction should be allowed is a choice that depends on the context and the particular account, and we leave it up to the user to decide whether it should be allowed. \u21a9","title":"Complete Assertions"},{"location":"beancount_cheat_sheet.html","text":"Beancount Syntax Cheat Sheet \uf0c1 Example Account Name: Assets:US:BofA:Checking Account Types Assets Liabilities Income Expenses Equity + - - + - Commodities All in CAPS: USD, EUR, CAD, AUD GOOG, AAPL, RBF1005 HOME_MAYST, AIRMILES HOURS Directives General syntax: YYYY-MM-DD Opening & Closing Accounts 2001-05-29 open Expenses:Restaurant 2001-05-29 open Assets:Checking USD,EUR ; Currency constraints 2015-04-23 close Assets:Checking Declaring Commodities This is optional; use this only if you want to attach metadata by currency. 1998-07-22 commodity AAPL name: \"Apple Computer Inc.\" Prices Use many times to fill historical price database: 2015-04-30 price AAPL 125.15 USD 2015-05-30 price AAPL 130.28 USD Notes 2013-03-20 note Assets:Checking \"Called to ask about rebate\" Documents 2013-03-20 document Assets:Checking \"path/to/statement.pdf\" Transactions 2015-05-30 * \"Some narration about this transaction\" Liabilities:CreditCard -101.23 USD Expenses:Restaurant 101.23 USD 2015-05-30 ! \"Cable Co\" \"Phone Bill\" #tag \u02c6link id: \"TW378743437\" ; Meta-data Expenses:Home:Phone 87.45 USD Assets:Checking ; You may leave one amount out Postings ... 123.45 USD Simple ... 10 GOOG {502.12 USD} With per-unit cost ... 10 GOOG {{5021.20 USD}} With total cost ... 10 GOOG {502.12 # 9.95 USD} With both costs ... 1000.00 USD @ 1.10 CAD With per-unit price ... 10 GOOG {502.12 USD} @ 1.10 CAD With cost & price ... 10 GOOG {502.12 USD, 2014-05-12 } With date ! ... 123.45 USD ... With flag Balance Assertions and Padding Asserts the amount for only the given currency: 2015-06-01 balance Liabilities:CreditCard -634.30 USD Automatic insertion of transaction to fulfill the following assertion: YYYY-MM-DD pad Assets:Checking Equity:Opening-Balances Events YYYY-MM-DD event \"location\" \"New York, USA\" YYYY-MM-DD event \"address\" \"123 May Street\" Options option \"title\" \"My Personal Ledger\" See this doc for the full list of supported options. Other pushtag #trip-to-peru ... poptag #trip-to-peru ; Comments begin with a semi-colon","title":"Beancount Cheat Sheet"},{"location":"beancount_cheat_sheet.html#beancount-syntax-cheat-sheet","text":"Example Account Name: Assets:US:BofA:Checking","title":"Beancount Syntax Cheat Sheet"},{"location":"beancount_design_doc.html","text":"Beancount Design Doc \uf0c1 Martin Blais ( blais@furius.ca ) http://furius.ca/beancount/doc/design-doc A guide for developers to understand Beancount\u2019s internals. I hope for this document to provide a map of the main objects used in the source code to make it easy to write scripts and plugins on top of Beancount or even extend it. Introduction Invariants Isolation of Inputs Order-Independence All Transactions Must Balance Account Have Types Account Lifetimes & Open Directives Supports Dates Only (and No Time) Metadata is for User Data Overview of the Codebase Core Data Structures Number Commodity Amount Lot Position Inventory Account Flag Posting About Tuples & Mutability Summary Directives Common Properties Transactions Flag Payee & Narration Tags Links Postings Balancing Postings Elision of Amounts Stream Processing Stream Invariants Loader & Processing Order Loader Output Parser Implementation Two Stages of Parsing: Incomplete Entries The Printer Uniqueness & Hashing Display Context Realization The Web Interface Reports vs. Web Client-Side JavaScript The Query Interface Design Principles Minimize Configurability Favor Code over DSLs File Format or Input Language? Grammar via Parser Generator Future Work Tagged Strings Errors Cleanup Types Conclusion Introduction \uf0c1 This document describes the principles behind the design of Beancount and a high-level overview of its codebase, data structures, algorithms, implementation and methodology. This is not a user's manual; if you are interested in just using Beancount, see the associated User's Manual and all the other documents available here . However, if you just want to understand more deeply how Beancount works this document should be very helpful. This should also be of interest to developers. This is a place where I wrote about a lot of the ideas behind Beancount that did not find any other venue. Expect some random thoughts that aren\u2019t important for users. Normally one writes a design document before writing the software. I did not do that. But I figured it would still be worthwhile to spell out some of the ideas that led to this design here. I hope someone finds this useful. Invariants \uf0c1 Isolation of Inputs \uf0c1 Beancount should accept input only from the text file you provide to its tools. In particular, it should not contact any external networked service or open any \u201cglobal\u201d files, even just a cache of historical prices or otherwise. This isolation is by design. Isolating the input to a single source makes it easier to debug, reproduce and isolate problems when they occur. This is a nice property for the tool. In addition, fetching and converting external data is very messy. External data formats can be notoriously bad, and they are too numerous to handle all of them (handling just the most common subset would beg the question of where to include the implementation of new converters). Most importantly, the problems of representing the data vs. that of fetching and converting it segment from each other very well naturally: Beancount provides a core which allows you to ingest all the transactional data and derive reports from it, and its syntax is the hinge that connects it to these external repositories of transactions or prices. It isolates itself from the ugly details of external sources of data in this way. Order-Independence \uf0c1 Beancount offers a guarantee that the ordering of its directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input text and reorder any declaration as is most convenient for you, without having to worry about how the software will make its calculations. This also makes it trivial to implement inclusions of multiple files: you can just concatenate the files if you want, and you can process them in any order. Not even directives that declare accounts or commodities are required to appear before these accounts get used in the file. All directives are parsed and then basically sorted before any calculation or validation occurs (a minor detail is that the line number is used as a secondary key in the sorting, so that the re-ordering is stable). Correspondingly, all the non-directive grammar that is in the language is limited to either setting values with global impact (\u201c option \u201d, \u201c plugin \u201d) or syntax-related conveniences (\u201c pushtag \u201d, \u201c poptag \u201d). There is an exception: Options may have effects during the processing of the file and should appear at the beginning. At the moment, Beancount does not enforce this, e.g., by performing multiple passes of parsing, but it probably should. What\u2019s more, options in included files are currently ignored. You should put all your options in the top-level ledger file. All Transactions Must Balance \uf0c1 All transactions must balance by \u201cweight.\u201d There is no exception. Beancount transactions are required to have balanced, period. In particular, there is no allowance for exceptions such as the \u201cvirtual postings\u201d that can be seen in Ledger. The first implementation of Beancount allowed such postings. As a result, I often used them to \u201cresolve\u201d issues that I did not know how to model well otherwise. When I rewrote Beancount, I set out to convert my entire input file to avoid using these, and over time I succeeded in doing this and learned a lot of new things in the process. I have since become convinced that virtual postings are wholly unnecessary, and that making them available only provides a crutch upon which a lot of users will latch instead of learning to model transfers using balanced transactions. I have yet to come across a sufficiently convincing case for them 1 . This provides the property that any subsets of transactions will sum up to zero. This is a nice property to have, it means we can generate balance sheets at any point in time. Even when we eventually support settlement dates or per-posting dates , we will split up transactions in a way that does not break this invariant. Accounts Have Types \uf0c1 Beancount accounts should be constrained to be of one of five types: Assets, Liabilities, Income, Expenses and Equity. The rationale behind this is to realize that all matters of counting can be characterized by being either permanent (Assets, Liabilities) or transitory (Income, Expenses), and that the great majority of accounts have a usual balance sign: positive (Assets, Expenses) or negative (Liabilities, Income). Given a quantity to accumulate, we can always select one of these four types of labels for it. Enforcing this makes it possible to implement reports of accumulated values for permanent accounts (balance sheet) and reports of changes of periods of time (income statement). Although it is technically possible to explicitly book postings against Equity accounts the usual purpose of items in that category is to account for past changes on the balance sheet (net/retained income or opening balances). These concepts of time and sign generalize beyond that of traditional accounting. Not having them raises difficult and unnecessary questions about how to handle calculating these types of reports. We simply require that you label your accounts into this model. I\u2019ll admit that this requires a bit of practice and forethought, but the end result is a structure that easily allows us to build commonly expected outputs. Account Lifetimes & Open Directives \uf0c1 Accounts have lifetimes; an account opens at a particular date and optionally closes at another. This is directed by the Open and Close directives. All accounts are required to have a corresponding Open directive in the stream. By default, this is enforced by spitting out an error when posting to an account whose Open directive hasn\u2019t been seen in the stream. You can automate the generation of these directives by using the \u201cauto_accounts\u201d plugin, but in any case, the stream of entries will always contain the Open directives. This is an assumption that one should be able to rely upon when writing scripts. (Eventually a similar constraint will be applied for Commodity directives so that the stream always includes one before it is used; and they should be auto-generated as well. This is not the case right now [June 2015].) Supports Dates Only (and No Time) \uf0c1 Beancount does not represent time, only dates. The minimal time interval is one day. This is for simplicity\u2019s sake. The situations where time would be necessary to disambiguate account balances exist, but in practice they are rare enough that their presence does not justify the added complexity of handling time values and providing syntax to deal with it. If you have a use case whereby times are required, you may use metadata to add it in, or more likely, you should probably write custom software for it. This is outside the scope of Beancount by choice. Metadata is for User Data \uf0c1 By default, Beancount will not make assumptions about metadata fields and values. Metadata is reserved for private usage by the user and for plugins. The Beancount core does not read metadata and change its processing based on it. However, plugins may define special metadata values that affect what they produce. That being said, there are a few special metadata fields produced by the Beancount processing: filename: A string, the name of the file the transaction was read from (or the plugin that created it) lineno: An integer, the line number from the file where the transaction was read from. tolerances : A dict of currency to Decimal, the tolerance values used in balancing the transaction. residual (on postings): A boolean, true if the posting has been added to automatically absorb rounding error. There may be a few more produced in the future but in any case, the core should not read any metadata and affect its behavior as a consequence. (I should probably create a central registry or location where all the special values can be found in one place.) Overview of the Codebase \uf0c1 All source code lives under a \u201c beancount \u201d Python package. It consists of several packages with well-defined roles, the dependencies between which are enforced strictly. Beancount source code packages and approximate dependencies between them. At the bottom, we have a beancount.core package which contains the data structures used to represent transactions and other directives in memory. This contains code for accounts, amounts, lots, positions, and inventories. It also contains commonly used routines for processing streams of directives. One level up, the beancount.parser package contains code to parse the Beancount language and a printer that can produce the corresponding text given a stream of directives. It should be possible to convert between text and data structure and round-trip this process without loss. At the root of the main package is a beancount.loader module that coordinates everything that has to be done to load a file in memory. This is the main entry point for anyone writing a script for Beancount, it always starts with loading a stream of entries. This calls the parser, runs some processing code, imports the plugins and runs them in order to produce the final stream of directives which is used to produce reports. The beancount.plugins package contains implementations of various plugins. Each of these plugin modules are independent from each other. Consult the docstrings in the source code to figure out what each of these does. Some of these are prototypes of ideas. There is a beancount.ops package which contains some high-level processing code and some of the default code that always runs by default that is implemented as plugins as well (like padding using the Pad directive). This package needs to be shuffled a bit in order to clarify its role. On top of this, reporting code calls modules from those packages. There are four packages which contain reporting code, corresponding to the Beancount reporting tools: beancount.reports : This package contains code specialized to produce different kinds of outputs (invoked by bean-report). In general I\u2019d like to avoid defining custom routines to produce desired outputs and use the SQL query tool to express grouping & aggregation instead, but I think there will always be a need for at least some of these. The reports are defined in a class hierarchy with a common interface and you should be able to extend them. Each report supports some subset of a number of output formats. beancount.query : This package contains the implementation of a query language and interactive shell (invoked by bean-query) that allows you to group and aggregate positions using an SQL-like DSL. This essentially implements processing over an in-memory table of Posting objects, with functions defined over Amount, Position and Inventory objects. beancount.web : This package contains all the source code for the default web interface (invoked by bean-web). This is a simple Bottle application that serves many of the reports from beancount.report to HTML format, running on your machine locally. The web interface provides simple views and access to your data. (It stands to be improved greatly, it\u2019s in no way perfect.) beancount.projects : This package contains various custom scripts for particular applications that I\u2019ve wanted to share and distribute. Wherever possible, I aim to fold these into reports. There are no scripts to invoke these; you should use \u201c python3 -m beancount.projects. \u201d to run them. There is a beancount.scripts package that contains the \u201cmain\u201d programs for all the scripts under the bin directory . Those scripts are simple launchers that import the corresponding file under beancount.scripts. This allows us to keep all the source code under a single directory and that makes it easier to run linters and other code health checkers on the code\u2014it\u2019s all in one place. Finally, there is a beancount.utils package which is there to contain generic reusable random bits of utility code. And there is a relatively unimportant beancount.docs package that contains code used by the author just to produce and maintain this documentation (code that connects to Google Drive). Enforcing the dependency relationships between those packages is done by a custom script . Core Data Structures \uf0c1 This section describes the basic data structures that are used as building blocks to represent directives. Where possible I will describe the data structure in conceptual terms. (Note for Ledger users : This section introduces some terminology for Beancount; look here if you\u2019re interested to contrast it with concepts and terminology found in Ledger.) Number \uf0c1 Numbers are represented using decimal objects, which are perfectly suited for this. The great majority of numbers entered in accounting systems are naturally decimal numbers and binary representations involve representational errors which cause many problems for display and in precision. Rational numbers avoid this problem, but they do not carry the limited amount of precision that the user intended in the input. We must deal with tolerances explicitly. Therefore, all numbers should use Python\u2019s \u201c decimal.Decimal \u201d objects. Conveniently, Python v3.x supports a C implementation of decimal types natively (in its standard library; this used to be an external \u201ccdecimal\u201d package to install but it has been integrated in the C/Python interpreter). The default constructor for decimal objects does not support some of the input syntax we want to allow, such as commas in the integer part of numbers (e.g., \u201c278 , 401.35 USD\u201d) or initialization from an empty string. These are important cases to handle. So I provide a special constructor to accommodate these inputs: beancount.core.number.D() . This is the only method that should be used to create decimal objects: from beancount.core.number import D \u2026 number = D(\"2,402.21\") I like to import the \u201c D \u201d symbol by itself (and not use number.D ). All the number-related code is located under beancount.core.number . Some number constants have been defined as well: ZERO , ONE , and HALF . Use those instead of explicitly constructed numbers (such as D(\"1\") ) as it makes it easier to grep for such initializations. Commodity \uf0c1 A commodity , or currency (I use both names interchangeably in the code and documentation) is a string that represents a kind of \u201cthing\u201d that can be stored in accounts. In the implementation, it is represented as a Python str object (there is no module with currency manipulation code). These strings may only contain capital letters and numbers and some special characters (see the lexer code). Beancount does not predefine any currency names or categories\u2014all currencies are created equal. Really. It genuinely does not know anything special about dollars or euros or yen or anything else. The only place in the source code where there is a reference to those is in the tests. There is no support for syntax using \u201c$\u201d or other currency symbols; I understand that some users may want this syntax, but in the name of keeping the parser very simple and consistent I choose not to provide that option. Moreover, Beancount does not make a distinction between commodities which represent \"money\" and others that do not (such as shares, hours, etc.). These are treated the same throughout the system. It also does not have the concept of a \"home\" currency 2 ; it's a genuinely multi-currency system. Currencies need not be defined explicitly in the input file format; you can just start using them in the file and they will be recognized as such by their unique syntax (the lexer recognizes and emits currency tokens). However, you may declare some using a Commodity directive. This is only provided so that a per-commodity entity exists upon which the user can attach some metadata, and some reports and plugins are able to find and use that metadata. Account \uf0c1 An account is basically just the name of a bucket associated with a posting and is represented as a simple string (a Python str object). Accounts are detected and tokenized by the lexer and have names with at least two words separated by a colon (\":\"). Accounts don\u2019t have a corresponding object type; we just refer to them by their unique name string (like filenames in Python). When per-account attributes are needed, we can extract the Open directives from the stream of entries and find the one corresponding to the particular account we\u2019re looking for. Similar to Python\u2019s os.path module, there are some routines to manipulate account names in beancount.core.account and the functions are named similarly to those of the os.path module. The first component of an account\u2019s name is limited to one of five types: Assets Liabilities Equity Income Expenses The names of the account types as read in the input syntax may be customized with some \"option\" directives, so that you can change those names to another language, or even just rename \"Income\" to \"Revenue\" if you prefer that. The beancount.core.account_types module contains some helpers to deal with these. Note that the set of account names forms an implicit hierarchy. For example, the names: Assets:US:TDBank:Checking Assets:US:TDBank:Savings implicitly defines a tree of nodes with parent nodes \"Assets\", \"US\", \"TDBank\" with two leaf nodes \"Checking\" and \"Savings\". This implicit tree is never realized during processing, but there is a Python module that allows one to do this easily (see beancount.core.realization ) and create linearized renderings of the tree. Flag \uf0c1 A \u201cflag\u201d is a single-character string that may be associated with Transactions and Postings to indicate whether they are assumed to be correct (\"reconciled\") or flagged as suspicious. The typical value used on transaction instances is the character \u201c*\u201d. On Postings, it is usually left absent (and set to a None). Amount \uf0c1 An Amount is the combination of a number and an associated currency, conceptually: Amount = (Number, Currency) Amount instances are used to represent a quantity of a particular currency (the \u201cunits\u201d) and the price on a posting. A class is defined in beancount.core.amount as a simple tuple-like object. Functions exist to perform simple math operations directly on instances of Amount. You can also create Amount instance using amount.from_string() , for example: value = amount.from_string(\"201.32 USD\") Cost \uf0c1 A Cost object represents the cost basis for a particular lot of a commodity. Conceptually, it is Cost = (Number, Currency, Date, Label) The number and currency is that of the cost itself, not of the commodity. For example, if you bought 40 shares of AAPL stock at 56.78 USD, the Number is a \u201c56.78\u201d decimal and the Currency is \u201cUSD.\u201d For example: Cost(Decimal(\"56.78\"), \"USD\", date(2012, 3, 5), \"lot15\") The Date is the acquisition date of the corresponding lot (a datetime.date object). This is automatically attached to the Cost object when a posting augments an inventory\u2014the Transaction\u2019s date is automatically attached to the Cost object\u2014or if the input syntax provides an explicit date override. The Label can be any string. It is provided as a convenience for a user to refer to a particular lot or disambiguate similar lots. On a Cost object, the number, currency and date attributes are always set. If the label is unset, it has a value of \u201cNone.\u201d CostSpec \uf0c1 In the input syntax, we allow users to provide as little information as necessary in order to disambiguate between the lots contained in the inventory prior to posting. The data provided filters the set of matching lots to an unambiguous choice, or to a subset from which an automated booking algorithm will apply (e.g., \u201cFIFO\u201d). In addition, we allow the user to provide either the per-unit cost and/or the total-cost. This convenience is useful to let Beancount automatically compute the per-unit cost from a total of proceeds. CostSpec = (Number-per-unit, Number-total, Currency, Date, Label, Merge) Since any of the input elements may be omitted, any of the attributes of a CostSpec may be left to None. If a number is missing and necessary to be filled in, the special value \u201cMISSING\u201d will be set on it. The Merge attribute it used to record a user request to merge all the input lots before applying the transaction and to merge them after. This is the method used for all transactions posted to an account with the \u201cAVERAGE\u201d booking method. Position \uf0c1 A position represents some units of a particular commodity held at cost. It consists simply in Position = (Units, Cost) Units is an instance of Amount , and Cost is an instance of Cost , or a null value if the commodity is not held at cost. Inventories contain lists of Position instances. See its definition in beancount.core.position . Posting \uf0c1 Each Transaction directive is composed of multiple Postings (I often informally refer to these in the code and on the mailing-list as the \u201clegs\u201d of a transaction). Each of these postings is associated with an account, a position and an optional price and flag: Posting = (Account, Units, Cost-or-CostSpec, Price, Flag, Metadata) As you can see, a Posting embeds its Position instance 3 . The Units is an Amount , and the \u2018cost\u2019 attribute refers to either a Cost or a CostSpec instance (the parser outputs Posting instances with an CostSpec attribute which is resolved to a Cost instance by the booking process; see How Inventories Work for details). The Price is an instance of Amount or a null value. It is used to declare a currency conversion for balancing the transaction, or the current price of a position held at cost. It is the Amount that appears next to a \u201c@\u201d in the input. Flags on postings are relatively rare; users will normally find it sufficient to flag an entire transaction instead of a specific posting. The flag is usually left to None; if set, it is a single-character string. The Posting type is defined in beancount.core.data , along with all the directive types. Inventory \uf0c1 An Inventory is a container for an account\u2019s balance in various lots of commodities. It is essentially a list of Position instances with suitable operations defined on it. Conceptually you may think of it as a mapping with unique keys: Inventory = [Position1, Position2, Position3, \u2026 , PositionN] Generally, the combination of a position\u2019s ( Units.Currency, Cost) is kept unique in the list, like the key of a mapping. Positions for equal values of currency and cost are merged together by summing up their Units.Number and keeping a single position for it. And simple positions are mixed in the list with positions held at cost. The Inventory is one of the most important and oft-used object in Beancount\u2019s implementation, because it is used to sum the balance of one or more accounts over time. It is also the place where the inventory reduction algorithms get applied to, and traces of that mechanism can be found there. The \u201c How Inventories Work \u201d document provides the full detail of that process. For testing, you can create initialized instances of Inventory using inventory.from_string() . All the inventory code is written in beancount.core.inventory . About Tuples & Mutability \uf0c1 Despite the program being written in a language which does not make mutability \u201cdifficult by default\u201d, I designed the software to avoid mutability in most places. Python provides a \u201c collections.namedtuple \u201d factory that makes up new record types whose attributes cannot be overridden. Well\u2026 this is only partially true: mutable attributes of immutable tuples can be modified. Python does not provide very strong mechanisms to enforce this property. Regardless, functional programming is not so much an attribute of the language used to implement our programs than of the guarantees we build into its structure. A language that supports strong guarantees helps to enforce this. But if, even by just using a set of conventions, we manage to mostly avoid mutability, we have a mostly functional program that avoids most of the pitfalls that occur from unpredictable mutations and our code is that much easier to maintain. Programs with no particular regard for where mutation occurs are most difficult to maintain. By avoiding most of the mutation in a functional approach, we avoid most of those problems. Most of the data structures used in Beancount are namedtuples , which make the modification of their attributes inconvenient on purpose. Most of the code will attempt to avoid mutation, except for local state (within a function) or in a narrow scope that we can easily wrap our head around. Where mutation is possible, by convention I try to avoid it by default. When we do mutation, I try to document the effects. I avoid the creation of classes which mutate their internal state, except for a few cases (e.g. the web application). I prefer functions to objects and where I define classes I mostly avoid inheritance. These properties are especially true for all the small objects: Amount, Lot, Position, Posting objects, and all the directives types from beancount.core.data . On the other hand, the Inventory object is nearly always used as an accumulator and does allow the modification of its internal state (it would require a special, persistent data structure to avoid this). You have to be careful how you share access to Inventory objects\u2026 and modify them, if you ever do. Finally, the loader produces lists of directives which are all simple namedtuple objects. These lists form the main application state. I\u2019ve avoided placing these inside some special container and instead pass them around explicitly, on purpose. Instead of having some sort of big \u201capplication\u201d object, I\u2019ve trimmed down all the fat and all your state is represented in two things: A dated and sorted list of directives which can be the subject of stream processing and a list of constant read-only option values. I think this is simpler. I credit my ability to make wide-ranging changes to a mid-size Python codebase to the adoption of these principles. I would love to have types in order to safeguard against another class of potential errors, and I plan to experiment with Python 3.5\u2019s upcoming typing module . Summary \uf0c1 The following diagram explains how these objects relate to each other, starting from a Posting. For example, to access the number of units of a postings you could use posting.units.number For the cost currency: posting.cost.currency You can print out the tuples in Python to figure out their structure. Previous Design \uf0c1 For the sake of preservation, if you go back in time in the repository, the structure of postings was deeper and more complex. The new design reflects a flatter and simpler version of it. Here is what the old design used to look like: Directives \uf0c1 The main production from loading a Beancount input file is a list of directives . I also call these entries interchangeably in the codebase and in the documents. There are directives of various types: Transaction Balance & Pad Open & Close Commodity Note Event Price Document In a typical Beancount input file, the great majority of entries will be Transactions and some Balance assertions. There will also be Open & perhaps some Close entries. Everything else is optional. Since these combined with a map of option values form the entire application state, you should be able to feed those entries to functions that will produce reports. The system is built around this idea of processing a stream of directives to extract all contents and produce reports, which are essentially different flavors of filtering and aggregations of values attached to this stream of directives. Common Properties \uf0c1 Some properties are in common to all the directives: Date. A datetime.date object. This is useful and consistent. For a Transaction, that\u2019s the date at which it occurs. For an Open or Close directive, that\u2019s the date at which the account was opened or closed. For a Pad directive, that\u2019s the date at which to insert a transaction. For a Note or Document, it is the date at which to insert the note in the stream of postings of that account. For an Event, it is the date at which it occurs. For a Commodity directive which essentially provides a per-commodity hanging point for commodity-related metadata, the date isn\u2019t as meaningful; I choose to date those on the day the commodity was created. Meta . All the directives have metadata attribute (a Python dict object). The purpose of metadata is to allow the user to hang any kind of ancillary data they want and then use this in scripts or queries. Posting instances also have a metadata attribute. Transactions \uf0c1 The Transaction directive is the subject of Beancount and is by far the most common directive found in input files and is worth of special attention. The function of a bookkeeping system is to organize the Postings attached to Transactions in various ways. All the other types of entries occupy supporting roles in our system. A Transaction has the following additional fields. Flag \uf0c1 The single-character flag is usually there to replace the \u201ctxn\u201d keyword (Transaction is the only directive which allows being entered without entering its keyword). I\u2019ve been considering changing the syntax definition somewhat to allow not entering the flag nor the keyword, because I\u2019d like to eventually support that. Right now, either the flag or keyword is required. The flag attribute may be set to None. Payee & Narration \uf0c1 The narration field is a user-provided description of the transaction, such as \"Dinner with Mary-Ann.\" You can put any information in this string. It shows up in the journal report. Oftentimes it is used to enrich the transaction with context that cannot be imported automatically, such as \"transfer from savings account to pay for car repairs.\" The payee name is optional, and exists to describe the entity with which the transaction is conducted, such as \"Whole Foods Market\" or \"Shell.\" Note that I want to be able to produce reports for all transactions associated with a particular payee, so it would be nice to enter consistent payee names. The problem with this is that the effort to do this right is sometimes too great. Either better tools or looser matching over names is required in order to make this work. The input syntax also allows only a single string to be provided. By default this becomes the narration, but I\u2019ve found that in practice it can be useful to have just the payee. It\u2019s just a matter of convenience. At the moment, if you want to enter just the payee string you need to append an empty narration string. This should be revisited at some point. Tags \uf0c1 Tags are sets of strings that can be used to group sets of transactions (or set to None if there are no tags). A view of this subset of transactions can then be generated, including all the usual reports (balance sheet, income statement, journals, etc.). You can tag transactions for a variety of purposes; here are some examples: All transactions during a particular travel might be tagged to later summarize your trip expenses. Those transactions will usually occur around the same date and therefore there is convenient syntax used to automatically tag many transactions that are declared together in a file. Transactions related to a particular project to be accomplished. I took some classes in an online program once, and tagged all related expenses to it. I use this to track all my immigration-related expenses for a particular stage (e.g. green card). Declaring a group of expenses to be paid back by an employer (or your own company) later on. Expenses related to moving between locations. Typical tag names that I use for tags look like #trip-israel-2012 , #conference-siggraph , and #course-cfa . In general, tags are useful where adding a sub-accounts won't do. This is often the case where a group of related expenses are of differing types, and so they would not belong to a single account. Given the right support from the query tools, they could eventually be subsumed by metadata\u2014I have been considering converting tags into metadata keys with a boolean value of True, in order to remove unnecessary complexity. Links \uf0c1 Links are a unique set of strings or None , and in practice will be usually empty for most transactions. They differ from tags only in purpose. Links are there to chain together a list of related transactions and there are tools used to list, navigate and balance a subset of transactions linked together. They are a way for a transaction to refer to other transactions. They are not meant to be used for summarizing. Examples include: transaction-ids from trading accounts (these often provide an id to a \"related\" or \"other\" transaction); account entries associated with related corrections, for example a reversed fee following a phone call could be linked to the original invalid fee from several weeks before; the purchase and sale of a home, and related acquisition expenses. In contrast to tags, their strings are most often unique numbers produced by the importers. No views are produced for links; only a journal of a particular links transactions can be produced and a rendered transaction should be accompanied by an actual \"link\" icon you can click on to view all the other related transactions. Postings \uf0c1 A list of Postings are attached to the Transaction object. Technically this list object is mutable but in practice we try not to modify it. A Posting can ever only be part of a single Transaction. Sometimes different postings of the same transaction will settle at different dates in their respective accounts, so eventually we may allow a posting to have its own date to override the transaction's date, to be used as documentation; in the simplest version, we enforce all transactions to occur punctually, which is simple to understand and was not found to be a significant problem in practice. Eventually we might implement this by implicitly converting Transactions with multiple dates into multiple Transactions and using some sort of transfer account to hold the pending balance in-between dates. See the associated proposal for details. Balancing Postings \uf0c1 The fundamental principle of double-entry book-keeping is enforced in each of the Transaction entries: the sum of all postings must be zero. This section describes the specific way in which we do this, and is the key piece of logic that allow the entire system to balance nicely. This is also one of the few places in this system where calculations go beyond simple filtering and aggregation. As we saw earlier, postings are associated with A position, which is a number of units of a lot (which itself may or may not have a cost) An optional conversion price Given this, how do we balance a transaction? Some terminology is in order: for this example posting: Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD Units. The number of units is the number of the position, or \u201c50\u201d. Currency. The commodity of the lot, that is, \u201cHOOL\u201d. Cost. The cost of this position is the number of units times the per-unit cost in the cost currency, that is 50 x 700 = 35000 USD. (Total) Price. The price of a unit is the attached price amount, 920 USD. The total price of a position is its number of units times the conversion price, in this example 50 x 920 = 46000 USD. Weight. The amount used to balance each posting in the transaction: If the posting has an associated cost, we calculate the cost of the position (regardless of whether a price is present or not); If the posting has no associated cost but has a conversion price, we convert the position into its total price; Finally, if the posting has no associated cost nor conversion price, the number of units of the lot are used directly. Balancing is really simple: each of the Posting's positions are first converted into their weight. These amounts are then grouped together by currency, and the final sum for each currency is asserted to be close to zero, that is, within a small amount of tolerance (as inferred by a combination of by the numbers in the input and the options ). The following example is one of case (2), with a price conversion and a regular leg with neither a cost nor a price (case 3): 2013-07-22 * \"Wired money to foreign account\" Assets:Investment:HOOL -35350 CAD @ 1.01 USD ;; -35000 USD (2) Assets:Investment:Cash 35000 USD ;; 35000 USD (3) ;;------------------ ;; 0 USD In the next example, the posting for the first leg has a cost, the second posting has neither: 2013-07-22 * \"Bought some investment\" Assets:Investment:HOOL 50 HOOL {700 USD} ;; 35000 USD (1) Assets:Investment:Cash -35000 USD ;; -35000 USD (3) ;;------------------ ;; 0 USD Here's a more complex example with both a price and a cost; the price here is ignored for the purpose of balance and is used only to enter a data-point in the internal price database: 2013-07-22 * \"Sold some investment\" Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD ;; -35000 USD (1) Assets:Investment:Cash 46000 USD ;; 46000 USD (3) Income:CapitalGains -11000 USD ;; -11000 USD (3) ;;------------------ ;; 0 USD Finally, here's an example with multiple commodities: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking 4585.38 USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib 540.00 IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation -4.62 VACHR Here there are three groups of weights to balance: USD: (4485.38) + (-25.38) + (-5000) + (540) ~= 0 USD IRAUSD: (540.00) + (-540.00) ~= 0 IRAUSD VACHR: (4.62) + (-4.62) ~= 0 VACHR Elision of Amounts \uf0c1 Transactions allow for at most one posting to be elided and automatically set; if an amount was elided, the final balance of all the other postings is attributed to the balance. With the inventory booking proposal , it should be extended to be able to handle more cases of elision. An interesting idea would be to allow the elided postings to at least specify the currency they're using. This would allow the user to elide multiple postings, like this: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation VACHR Stream Processing \uf0c1 An important by-product of representing all state using a single stream of directives is that most of the operations in Beancount can be implemented by simple functions that accept the list of directives as input and output a modified list of directives. For example, The \u201cPad\u201d directive is implemented by processing the stream of transactions, accumulating balances, inserting a new padding transaction after the Pad directive when a balance assertion fails. Summarization operations (open books, close books, clear net income to equity) are all implemented this way, as functional operators on the list of streams. For example, opening the books at a particular date is implemented by summing all balances before a certain date and replacing those transactions by new transactions that pull the final balance from an Equity account. Closing the books only involves moving the balances of income statement account to Equity and truncation the list of transactions to that date. This is very elegant\u2014the reporting code can be oblivious to the fact that summarization has occurred. Prices on postings held with a cost are normally ignored. The \u201cimplicit prices\u201d option is implemented using a plugin which works its magic by processing the stream of operations and inserting otherwise banal Price directives automatically when such a posting is found. Many types of verifications are also implemented this way; the sellgains plugin verifies that non-income balances check against the converted price of postings disregarding the cost. This one does not insert any new directives, but may generate new errors. These are just some examples. I\u2019m aiming to make most operations work this way. This design has proved to be elegant, but also surprisingly flexible and it has given birth to the plugins system available in Beancount; you might be surprised to learn that I had not originally thought to provide a plugins system... it just emerged over time teasing abstractions and trying to avoid state in my program. I am considering experimenting with weaving the errors within the stream of directives by providing a new \u201cError\u201d directive that could be inserted by stream processing functions. I\u2019m not sure whether this would make anything simpler yet, it\u2019s just an idea at this point [July 2015]. Stream Invariants \uf0c1 The stream of directives comes with a few guarantees you can rest upon: All the directives are ordered by date. Because many dates have multiple directives, in order to have a stable sorting order the line number in the file is used as a secondary sort key. All uses of an account is preceded in the stream by a corresponding Open entry. If the input did not provide one, an Open directive is automatically synthesized. All Balance directives precede any other directive on a particular day. This is enforced in order to make the processing of balance assertions straightforward and to emphasize that their semantics is to occur at the beginning of the day. Loader & Processing Order \uf0c1 The process of loading a list of entries from an input file is the heart of the project. It is important to understand it in order to understand how Beancount works. Refer to the diagram below. It consists of A parsing step that reads in all the input files and produces a stream of potentially incomplete directives. The processing of this stream of entries by built-in and user-specified plugins. This step potentially produces new error objects. A final validation step that ensures the invariants have not been broken by the plugins. The beancount.loader module orchestrates this processing. In the code and documents, I\u2019m careful to distinguish between the terms \u201cparsing\u201d and \u201cloading\u201d carefully. These two concepts are distinct. \u201cParsing\u201d is only a part of \u201cloading.\u201d The user-specified plugins are run in the order they are provided in the input files. Raw mode. Some users have expressed a desire for more control over which built-in plugins are run and in which order they are to be run, and so enabling the \u201craw\u201d option disables the automatic loading of all built-in plugins (you have to replace those with explicit \u201cplugin\u201d directives if you want to do that). I\u2019m not sure if this is going to be useful yet, but it allows for tighter control over the loading process for experimentation. Loader Output \uf0c1 The parser and the loader produce three lists: entries: A list of directive tuples from beancount.core.data. This is the stream of data which should consists mostly of Transaction and Balance instances. As much as possible, all data transformations are performed by inserting or removing entries from this list. Throughout the code and documents, I refer to these as entries or directives interchangeably. errors: A list of error objects. In Beancount I don\u2019t use exceptions to report errors; rather, all the functions produce error objects and they are displayed at the most appropriate time. (These deserve a base common type but for now the convention they respect is that they all provide a source (filename, line-no), message and entry attributes.) options_map: A dict of the options provided in the file and derived during parsing. Though this is a mutable object, we never modify it once produced by the parser. Parser Implementation \uf0c1 The Beancount parser is a mix of C and Python 3 code. This is the reason you need to compile anything in the first place. The parser is implemented in C using a flex tokenizer and a Bison parser generator, mainly for performance reasons. I chose the C language and these crufty old GNU tools because they are portable and will work everywhere. There are better parser generators out there, but they would either require my users to install more compiler tools or exotic languages. The C language is a great common denominator. I want Beancount to work everywhere and to be easy to install. I want this to make it easy on others, but I also want this for myself, because at some point I\u2019ll be working on other projects and the less dependencies I have the lighter it will be to maintain aging software. Just looking ahead. (That\u2019s also why I chose to use Python instead of some of the other languages I\u2019m more naturally attracted to these days (Clojure, Go, ML); I need Beancount to work... when I\u2019m counting the beans I don\u2019t have time to debug problems. Python is careful about its changes and it will be around for a long long time as is.) There is a lexer file lexer.l written in flex and a Bison grammar in grammar.y. These tools are used to generate the corresponding C source code (lexer.h/.c and grammar.h/.c). These, along with some hand-written C code that defines some interface functions for the module (parser.h/.c), are compiled into an extension module (_parser.so). Eventually we could consider creating a small dependency rule in setup.py to invoke flex and Bison automatically but at the moment, in order to minimize the installation burden, I check the generated source code in the repository (lexer.h/c and grammar.h/c). The interaction between the Python and C code works like this: You import beancount.parser.parser and invoke either parse_file() or parse_string(). This uses code in grammar.py to create a Builder object which essentially provides callbacks in Python to process grammar rules. parser.py calls a C function in the extension module with the Builder object. That C code sets up flex to be able to read the input file or string then hands over control to the parser by calling the generated C function \u201cyyparse()\u201d. yyparse() is the parser and will fetch input tokens from the lexer C code using successive calls to \u201cyylex()\u201d and attempt to reduce rules. When a rule or token is successfully reduced, the C code invokes callback methods on the Builder object you provided. The parser\u2019s rules communicate between each other by passing around \u201cPyObject*\u201d instances, so that code has to be a little careful about correctly handling reference counting. This allows me to run Python code on rule reduction, which makes it really easy to customize parser behaviour yet maintain the performance of the grammar rule processing in C. Note that the grammar.py Builder derives from a similar Builder class defined in lexer.py, so that lexer tokens recognized calls methods defined in the lexer.py file to create them and parser rule correspondingly calls methods from grammar.py\u2019s Builder. This isolation is not only clean, it also makes it possible to just use the lexer to tokenize a file from Python (without parsing) for testing the tokenizer; see the tests where this is used if you\u2019re curious how this works. I just came up with this; I haven\u2019t seen this done anywhere else. I tried it and the faster parser made a huge difference compared to using PLY so I stuck with that. I quite like the pattern, it\u2019s a good compromise that offers the flexibility of a parser generator yet allows me to handle the rules using Python. Eventually I\u2019d like to move just some of the most important callbacks to C code in order to make this a great deal faster (I haven\u2019t done any real performance optimizations yet). This has worked well so far but for one thing: There are several limitations inherent in the flex tokenizer that have proved problematic. In particular, in order to recognize transaction postings using indent I have had to tokenize the whitespace that occurs at the beginning of a line. Also, single-characters in the input should be parsed as flags and at the moment this is limited to a small subset of characters. I\u2019d like to eventually write a custom lexer with some lookahead capabilities to better deal with these problems (this is easy to do). Two Stages of Parsing: Incomplete Entries \uf0c1 At the moment, the parser produces transactions which may or may not balance. The validation stage which runs after the plugins carries out the balance assertions. This degree of freedom is allowed to provide the opportunity for users to enter incomplete lists of postings and for corresponding plugins to automate some data entry and insert completing postings. However, the postings on the transactions are always complete objects, with all their expected attributes set. For example, a posting which has its amount elided in the input syntax will have a filled in position by the time it comes out of the parser. We will have to loosen this property when we implement the inventory booking proposal , because we want to support the elision of numbers whose values depend on the accumulated state of previous transactions. Here is an obvious example. A posting like this 2015-04-03 * \"Sell stock\" Assets:Investments:AAPL -10 AAPL {} ... should succeed if at the beginning of April 3rd the account contains exactly 10 units of AAPL, in either a single or multiple lots. There is no ambiguity: \u201csell all the AAPL,\u201d this says. However, the fact of its unambiguity assumes that we\u2019ve computed the inventory balance of that account up until April 3rd\u2026 So the parser will need to be split into two phases: A simple parsing step that produces potentially incomplete entries with missing amounts A separate step for the interpolation which will have available the inventory balances of each account as inputs. This second step is where the booking algorithms (e.g., FIFO) will be invoked from. See the diagram above for reference. Once implemented, everything else should be the same. The Printer \uf0c1 In the same package as the parser lives a printer. This isolates all the functionality that deals with the Beancount \u201clanguage\u201d in the beancount.parser package: the parser converts from input syntax to data structure and the printer does the reverse. No code outside this package should concern itself with the Beancount syntax. At some point I decided to make sure that the printer was able to round-trip through the parser, that is, given a stream of entries produced by the loader, you should be able to convert those to text input and parse them back in and the resulting set of entries should be the same (outputting the re-read input back to text should produce the same text), e.g., Note that the reverse isn\u2019t necessarily true: reading an input file and processing it through the loader potentially synthesizes a lot of entries (thanks to the plugins), so printing it back may not produce the same input file (even ignoring reordering and white space concerns). This is a nice property to have. Among other things, it allows us to use the parser to easily create tests with expected outputs. While there is a test that attempts to protect this property, some of the recent changes break it partially: Metadata round-tripping hasn\u2019t been tested super well. In particular, some of the metadata fields need to be ignored (e.g., filename and lineno). Some directives contain derived data, e.g. the Balance directive has a \u201cdiff_amount\u201d field that contains the difference when there is a failure in asserting a balance. This is used to report errors more easily. I probably need to remove these exceptions at some point because it is the only one of its kind (I could replace it with an inserted \u201cError\u201d directive). This probably needs to get refined a bit at some point with a more complete test (it\u2019s not far as it is). Uniqueness & Hashing \uf0c1 In order to make it possible to compare directives quickly, we support unique hashing of all directives, that is, from each directive we should be able to produce a short and unique id. We can then use these ids to perform set inclusion/exclusion/comparison tests for our unit tests. We provide a base test case class with assertion methods that use this capability . This feature is used liberally throughout our test suites. This is also used to detect and remove duplicates. This feature is optional and enabled by the beancount.plugins.noduplicates plugin. Note that the hashing of directives currently does not include user meta-data . Display Context \uf0c1 Number are input with different numbers of fractional digits: 500 -> 0 fractional digits 520.23 -> 2 fractional digits 1.2357 -> 4 fractional digits 31.462 -> 3 fractional digits Often, a number used for the same currency is input with a different number of digits. Given this variation in the input, a question that arises is how to render the numbers consistently in reports? Consider that: We would like that the user not to have to specify the number of fractional digits to use for rendering by default. Rendering numbers may differ depending on the context: most of the time we want to render numbers using their most commonly seen number of digits, rounding if necessary, but when we are rendering conversion rates we would like to render numbers using the maximum number of digits ever seen. The number of digits used tends to vary with the commodity they represent. For example, US dollars will usually render with two digits of precision; the number of units of a mutual fund such as RGAGX might be recorded by your broker with 3 digits of precision. Japanese Yen will usually be specified in integer units. We want to render text report which need to align numbers with varying number of fractional digits together, aligning them by their period or rightmost digits, and possibly rendering a currency thereafter. In order to deal with this thorny problem, I built a kind of accumulator which is used to record all numbers seen from some input and tally statistics about the precisions witnessed for each currency. I call this a DisplayContext . From this object one can request to build a DisplayFormatter object which can be used to render numbers in a particular way. I refer to those objects using the variable names dcontext and dformat throughout the code. The parser automatically creates a DisplayContext object and feeds it all the numbers it sees during parsing. The object is available in the options_map produced by the loader. Realization \uf0c1 It occurs often that one needs to compute the final balances of a list of filtered transactions, and to report on them in a hierarchical account structure. See diagram below. \uf0c1 For the purpose of creating hierarchical renderings, I provide a process called a \u201c realization .\u201d A realization is a tree of nodes, each of which contains An account name, A list of postings and other entries to that account, and The final balance for that account, A mapping of sub-account names to child nodes. The nodes are of type \u201cRealAccount,\u201d which is also a dict of its children. All variables of type RealAccount in the codebase are by convention prefixed by \u201creal_*\u201d. The realization module provides functions to iterate and produce hierarchical reports on these trees of balances, and the reporting routines all use these. For example, here is a bit of code that will dump a tree of balances of your accounts to the console: import sys from beancount import loader from beancount.core import realization entries, errors, options_map = loader.load_file(\"filename.beancount\") real_root = realization.realize(entries) realization.dump_balances(real_root, file=sys.stdout) The Web Interface \uf0c1 Before the appearance of the SQL syntax to filter and aggregate postings, the only report produced by Beancount was the web interface provided by bean-web. As such, bean-web evolved to be good enough for most usage and general reports. Reports vs. Web \uf0c1 One of the important characteristics of bean-web is that it should just be a thin dispatching shell that serves reports generated from the beancount.reports layer. It used to contain the report rendering code itself, but at some point I began to factor out all the reporting code to a separate package in order to produce reports to other formats, such as text reports and output to CSV. This is mostly finished, but at this time [July 2015] some reports remain that only support HTML output. This is why. Client-Side JavaScript \uf0c1 I would like to eventually include a lot more client-side scripting in the web interface. However, I don\u2019t think I\u2019ll be able to work on this for a while, at least not until all the proposals for changes to the core are complete (e.g., inventory booking improvements, settlement splitting and merging, etc.). If you\u2019d like to contribute to Beancount, improving bean-web or creating your own visualizations would be a great venue. The Query Interface \uf0c1 The current query interface is the result of a prototype . It has not yet been subjected to the amount of testing and refinement as the rest of the codebase. I\u2019ve been experimenting with it and have a lot of ideas for improving the SQL language and the kinds of outputs that can be produced from it. I think of it as \u201c70% done\u201d. However, it works, and though some of the reports can be a little clunky to specify, it produces useful results. It will be the subject of a complete rewrite at some point and when I do, I will keep the current implementation for a little while so that existing scripts don\u2019t just break; I\u2019ll implement a v2 of the shell. Design Principles \uf0c1 Minimize Configurability \uf0c1 First, Beancount should have as few options as possible. Second, command-line programs should have no options that pertain to the processing and semantic of the input file (options that affect the output or that pertain to the script itself are fine). The goal is to avoid feature creep. Just a few options opens the possibility of them interacting in complex ways and raises a lot of questions. In this project, instead of providing options to customize every possible thing, I choose to bias the design towards providing the best behavior by default, even if that means less behavior. This is the Apple approach to design. I don\u2019t usually favor this approach for my projects, but I chose it for this particular one. What I\u2019ve learned in the process is how far you can get and still provide much of the functionality you originally set out for. Too many command-line options makes a program difficult to use. I think Ledger suffers from this, for instance. I can never remember options that I don\u2019t use regularly, so I prefer to just design without them. Options that affect semantics should live in the file itself (and should be minimized as well). Options provided when running a process should be only to affect this process. Having less options also makes the software much easier to refactor. The main obstacle to evolving a library of software is the number of complex interactions between the codes. Guaranteeing a well-defined set of invariants makes it much easier to later on split up functionality or group it differently. So.. by default I will resist making changes that aren't generic or that would not work for most users. On the other hand, large-scale changes that would generalize well and that require little configurability are more likely to see implementation. Favor Code over DSLs \uf0c1 Beancount provides a simple syntax, a parser and printer for it, and a library of very simple data record types to allow a user to easily write their scripts to process their data, taking advantage of the multitude of libraries available from the Python environment. Should the built-in querying tools fail to suffice, I want it to be trivially easy for someone to build what they need on top of Beancount. This also means that the tools provided by Beancount don\u2019t have to support all the features everyone might ever possibly want. Beancount\u2019s strength should be representational coherence and simplicity rather than the richness of its reporting features. Creating new kinds of reports should be easy; changing the internals should be hard. (That said, it does provide an evolving palette of querying utilities that should be sufficient for most users.) In contrast, the implementation of the Ledger system provides high-level operations that are invoked as \"expressions\" defined in a domain-specific language, expressions which are either provided on the command-line or in the input file itself. For the user, this expression language is yet another thing to learn, and it\u2019s yet another thing that might involve limitations requiring expansion and work\u2026 I prefer to avoid DSLs if possible and use Python\u2019s well understood semantics instead, though this is not always sensible. File Format or Input Language? \uf0c1 One may wonder whether Beancount\u2019s input syntax should be a computer language or just a data format. The problem we're trying to solve is essentially that of making it easy for a human being to create a transactions-to-postings-to-accounts-&-positions data structure. What is the difference between a data file format and a simple declarative computer language? One distinction is the intended writer of the file. Is it a human? Or is it a computer? The input files are meant to be manicured by a human, at the very least briefly eyeballed. While we are trying to automate as much of this process as possible via importing code\u2014well, the unpleasant bits, anyway\u2014we do want to ensure that we review all the new transactions that we're adding to the ledger in order to ensure correctness. If the intended writer is a human one could file the input under the computer language rubric (most data formats are designed to be written by computers). We can also judge it by the power of its semantics. Beancount\u2019s language is not one designed to be used to perform computation, that is, you cannot define new \u201cBeancount functions\u201d in it. But it has data types. In this regard, it is more like a file format. Just something to think about, especially in the context of adding new semantics. Grammar via Parser Generator \uf0c1 The grammar of its language should be compatible with the use of commonly used parser generator tools. It should be possible to parse the language with a simple lexer and some grammar input file. Beancount\u2019s original syntax was modeled after the syntax of Ledger. However, in its attempt to make the amount of markup minimal, that syntax is difficult to parse with regular tools. By making simple changes to the grammar, e.g. adding tokens for strings and accounts and commodities, the Beancount v2 reimplementation made it possible to use a flex/bison parser generator. This has important advantages: It makes it really easy to make incremental changes to the grammar. Using a custom parser without a well-defined grammar can make it quite difficult to prototype syntax changes. It makes it much easier to implement a parser in another computer language. I\u2019d like to eventually be able to parse Beancount input files in other languages. The data structures are simple enough that it should be easy to reimplement the core in a different language. It\u2019s just a lot less code to write and maintain. (Eventually I\u2019d like to create a file format to generate parsers in multiple languages from a single input. That will be done later, once all the major features are implemented.) Future Work \uf0c1 Tagged Strings \uf0c1 At the moment, account names, tags, links and commodities are represented as simple Python strings. I\u2019ve tried to keep things simple. At some point I\u2019d like to refine this a bit and create a specialization of strings for each of these types. I\u2019d need to assess the performance impact of doing that beforehand. I\u2019m not entirely sure how it could benefit functionality yet. Errors Cleanup \uf0c1 I\u2019ve been thinking about removing the separate \u201cerrors\u201d list and integrating it into the stream of entries as automatically produced \u201cError\u201d entries. This has the advantage that the errors could be rendered as part of the rest of the stream, but it means we have to process the whole list of entries any time we need to print and find errors (probably not a big deal, given how rarely we output errors). Conclusion \uf0c1 This document is bound to evolve. If you have questions about the internals, please post them to the mailing-list . References \uf0c1 Nothing in Beancount is inspired from the following docs, but you may find them interesting as well: Accounting for Computer Scientists I have been considering the addition of a very constrained form of non-balancing postings that would preserve the property of transactions otherwise being balanced, whereby the extra postings would not be able to interact (or sum with) regular balancing postings. \u21a9 There is an option called operating_currency but it is only used to provide good defaults for building reports, never in the processing of transactions. It is used to tell the reporting code which commodities should be broken out to have their own columns of balances, for example. \u21a9 In previous version of Beancount this was not true, the Posting used to have a \u2018position\u2019 attribute and composed it. I prefer the flattened design, as many of the functions apply to either a Position or a Posting. Think of a Posting as deriving from a Position, though, technically it does not. \u21a9","title":"Beancount Design Doc"},{"location":"beancount_design_doc.html#beancount-design-doc","text":"Martin Blais ( blais@furius.ca ) http://furius.ca/beancount/doc/design-doc A guide for developers to understand Beancount\u2019s internals. I hope for this document to provide a map of the main objects used in the source code to make it easy to write scripts and plugins on top of Beancount or even extend it. Introduction Invariants Isolation of Inputs Order-Independence All Transactions Must Balance Account Have Types Account Lifetimes & Open Directives Supports Dates Only (and No Time) Metadata is for User Data Overview of the Codebase Core Data Structures Number Commodity Amount Lot Position Inventory Account Flag Posting About Tuples & Mutability Summary Directives Common Properties Transactions Flag Payee & Narration Tags Links Postings Balancing Postings Elision of Amounts Stream Processing Stream Invariants Loader & Processing Order Loader Output Parser Implementation Two Stages of Parsing: Incomplete Entries The Printer Uniqueness & Hashing Display Context Realization The Web Interface Reports vs. Web Client-Side JavaScript The Query Interface Design Principles Minimize Configurability Favor Code over DSLs File Format or Input Language? Grammar via Parser Generator Future Work Tagged Strings Errors Cleanup Types Conclusion","title":"Beancount Design Doc"},{"location":"beancount_design_doc.html#introduction","text":"This document describes the principles behind the design of Beancount and a high-level overview of its codebase, data structures, algorithms, implementation and methodology. This is not a user's manual; if you are interested in just using Beancount, see the associated User's Manual and all the other documents available here . However, if you just want to understand more deeply how Beancount works this document should be very helpful. This should also be of interest to developers. This is a place where I wrote about a lot of the ideas behind Beancount that did not find any other venue. Expect some random thoughts that aren\u2019t important for users. Normally one writes a design document before writing the software. I did not do that. But I figured it would still be worthwhile to spell out some of the ideas that led to this design here. I hope someone finds this useful.","title":"Introduction"},{"location":"beancount_design_doc.html#invariants","text":"","title":"Invariants"},{"location":"beancount_design_doc.html#isolation-of-inputs","text":"Beancount should accept input only from the text file you provide to its tools. In particular, it should not contact any external networked service or open any \u201cglobal\u201d files, even just a cache of historical prices or otherwise. This isolation is by design. Isolating the input to a single source makes it easier to debug, reproduce and isolate problems when they occur. This is a nice property for the tool. In addition, fetching and converting external data is very messy. External data formats can be notoriously bad, and they are too numerous to handle all of them (handling just the most common subset would beg the question of where to include the implementation of new converters). Most importantly, the problems of representing the data vs. that of fetching and converting it segment from each other very well naturally: Beancount provides a core which allows you to ingest all the transactional data and derive reports from it, and its syntax is the hinge that connects it to these external repositories of transactions or prices. It isolates itself from the ugly details of external sources of data in this way.","title":"Isolation of Inputs"},{"location":"beancount_design_doc.html#order-independence","text":"Beancount offers a guarantee that the ordering of its directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input text and reorder any declaration as is most convenient for you, without having to worry about how the software will make its calculations. This also makes it trivial to implement inclusions of multiple files: you can just concatenate the files if you want, and you can process them in any order. Not even directives that declare accounts or commodities are required to appear before these accounts get used in the file. All directives are parsed and then basically sorted before any calculation or validation occurs (a minor detail is that the line number is used as a secondary key in the sorting, so that the re-ordering is stable). Correspondingly, all the non-directive grammar that is in the language is limited to either setting values with global impact (\u201c option \u201d, \u201c plugin \u201d) or syntax-related conveniences (\u201c pushtag \u201d, \u201c poptag \u201d). There is an exception: Options may have effects during the processing of the file and should appear at the beginning. At the moment, Beancount does not enforce this, e.g., by performing multiple passes of parsing, but it probably should. What\u2019s more, options in included files are currently ignored. You should put all your options in the top-level ledger file.","title":"Order-Independence"},{"location":"beancount_design_doc.html#all-transactions-must-balance","text":"All transactions must balance by \u201cweight.\u201d There is no exception. Beancount transactions are required to have balanced, period. In particular, there is no allowance for exceptions such as the \u201cvirtual postings\u201d that can be seen in Ledger. The first implementation of Beancount allowed such postings. As a result, I often used them to \u201cresolve\u201d issues that I did not know how to model well otherwise. When I rewrote Beancount, I set out to convert my entire input file to avoid using these, and over time I succeeded in doing this and learned a lot of new things in the process. I have since become convinced that virtual postings are wholly unnecessary, and that making them available only provides a crutch upon which a lot of users will latch instead of learning to model transfers using balanced transactions. I have yet to come across a sufficiently convincing case for them 1 . This provides the property that any subsets of transactions will sum up to zero. This is a nice property to have, it means we can generate balance sheets at any point in time. Even when we eventually support settlement dates or per-posting dates , we will split up transactions in a way that does not break this invariant.","title":"All Transactions Must Balance"},{"location":"beancount_design_doc.html#accounts-have-types","text":"Beancount accounts should be constrained to be of one of five types: Assets, Liabilities, Income, Expenses and Equity. The rationale behind this is to realize that all matters of counting can be characterized by being either permanent (Assets, Liabilities) or transitory (Income, Expenses), and that the great majority of accounts have a usual balance sign: positive (Assets, Expenses) or negative (Liabilities, Income). Given a quantity to accumulate, we can always select one of these four types of labels for it. Enforcing this makes it possible to implement reports of accumulated values for permanent accounts (balance sheet) and reports of changes of periods of time (income statement). Although it is technically possible to explicitly book postings against Equity accounts the usual purpose of items in that category is to account for past changes on the balance sheet (net/retained income or opening balances). These concepts of time and sign generalize beyond that of traditional accounting. Not having them raises difficult and unnecessary questions about how to handle calculating these types of reports. We simply require that you label your accounts into this model. I\u2019ll admit that this requires a bit of practice and forethought, but the end result is a structure that easily allows us to build commonly expected outputs.","title":"Accounts Have Types"},{"location":"beancount_design_doc.html#account-lifetimes-open-directives","text":"Accounts have lifetimes; an account opens at a particular date and optionally closes at another. This is directed by the Open and Close directives. All accounts are required to have a corresponding Open directive in the stream. By default, this is enforced by spitting out an error when posting to an account whose Open directive hasn\u2019t been seen in the stream. You can automate the generation of these directives by using the \u201cauto_accounts\u201d plugin, but in any case, the stream of entries will always contain the Open directives. This is an assumption that one should be able to rely upon when writing scripts. (Eventually a similar constraint will be applied for Commodity directives so that the stream always includes one before it is used; and they should be auto-generated as well. This is not the case right now [June 2015].)","title":"Account Lifetimes & Open Directives"},{"location":"beancount_design_doc.html#supports-dates-only-and-no-time","text":"Beancount does not represent time, only dates. The minimal time interval is one day. This is for simplicity\u2019s sake. The situations where time would be necessary to disambiguate account balances exist, but in practice they are rare enough that their presence does not justify the added complexity of handling time values and providing syntax to deal with it. If you have a use case whereby times are required, you may use metadata to add it in, or more likely, you should probably write custom software for it. This is outside the scope of Beancount by choice.","title":"Supports Dates Only (and No Time)"},{"location":"beancount_design_doc.html#metadata-is-for-user-data","text":"By default, Beancount will not make assumptions about metadata fields and values. Metadata is reserved for private usage by the user and for plugins. The Beancount core does not read metadata and change its processing based on it. However, plugins may define special metadata values that affect what they produce. That being said, there are a few special metadata fields produced by the Beancount processing: filename: A string, the name of the file the transaction was read from (or the plugin that created it) lineno: An integer, the line number from the file where the transaction was read from. tolerances : A dict of currency to Decimal, the tolerance values used in balancing the transaction. residual (on postings): A boolean, true if the posting has been added to automatically absorb rounding error. There may be a few more produced in the future but in any case, the core should not read any metadata and affect its behavior as a consequence. (I should probably create a central registry or location where all the special values can be found in one place.)","title":"Metadata is for User Data"},{"location":"beancount_design_doc.html#overview-of-the-codebase","text":"All source code lives under a \u201c beancount \u201d Python package. It consists of several packages with well-defined roles, the dependencies between which are enforced strictly. Beancount source code packages and approximate dependencies between them. At the bottom, we have a beancount.core package which contains the data structures used to represent transactions and other directives in memory. This contains code for accounts, amounts, lots, positions, and inventories. It also contains commonly used routines for processing streams of directives. One level up, the beancount.parser package contains code to parse the Beancount language and a printer that can produce the corresponding text given a stream of directives. It should be possible to convert between text and data structure and round-trip this process without loss. At the root of the main package is a beancount.loader module that coordinates everything that has to be done to load a file in memory. This is the main entry point for anyone writing a script for Beancount, it always starts with loading a stream of entries. This calls the parser, runs some processing code, imports the plugins and runs them in order to produce the final stream of directives which is used to produce reports. The beancount.plugins package contains implementations of various plugins. Each of these plugin modules are independent from each other. Consult the docstrings in the source code to figure out what each of these does. Some of these are prototypes of ideas. There is a beancount.ops package which contains some high-level processing code and some of the default code that always runs by default that is implemented as plugins as well (like padding using the Pad directive). This package needs to be shuffled a bit in order to clarify its role. On top of this, reporting code calls modules from those packages. There are four packages which contain reporting code, corresponding to the Beancount reporting tools: beancount.reports : This package contains code specialized to produce different kinds of outputs (invoked by bean-report). In general I\u2019d like to avoid defining custom routines to produce desired outputs and use the SQL query tool to express grouping & aggregation instead, but I think there will always be a need for at least some of these. The reports are defined in a class hierarchy with a common interface and you should be able to extend them. Each report supports some subset of a number of output formats. beancount.query : This package contains the implementation of a query language and interactive shell (invoked by bean-query) that allows you to group and aggregate positions using an SQL-like DSL. This essentially implements processing over an in-memory table of Posting objects, with functions defined over Amount, Position and Inventory objects. beancount.web : This package contains all the source code for the default web interface (invoked by bean-web). This is a simple Bottle application that serves many of the reports from beancount.report to HTML format, running on your machine locally. The web interface provides simple views and access to your data. (It stands to be improved greatly, it\u2019s in no way perfect.) beancount.projects : This package contains various custom scripts for particular applications that I\u2019ve wanted to share and distribute. Wherever possible, I aim to fold these into reports. There are no scripts to invoke these; you should use \u201c python3 -m beancount.projects. \u201d to run them. There is a beancount.scripts package that contains the \u201cmain\u201d programs for all the scripts under the bin directory . Those scripts are simple launchers that import the corresponding file under beancount.scripts. This allows us to keep all the source code under a single directory and that makes it easier to run linters and other code health checkers on the code\u2014it\u2019s all in one place. Finally, there is a beancount.utils package which is there to contain generic reusable random bits of utility code. And there is a relatively unimportant beancount.docs package that contains code used by the author just to produce and maintain this documentation (code that connects to Google Drive). Enforcing the dependency relationships between those packages is done by a custom script .","title":"Overview of the Codebase"},{"location":"beancount_design_doc.html#core-data-structures","text":"This section describes the basic data structures that are used as building blocks to represent directives. Where possible I will describe the data structure in conceptual terms. (Note for Ledger users : This section introduces some terminology for Beancount; look here if you\u2019re interested to contrast it with concepts and terminology found in Ledger.)","title":"Core Data Structures"},{"location":"beancount_design_doc.html#number","text":"Numbers are represented using decimal objects, which are perfectly suited for this. The great majority of numbers entered in accounting systems are naturally decimal numbers and binary representations involve representational errors which cause many problems for display and in precision. Rational numbers avoid this problem, but they do not carry the limited amount of precision that the user intended in the input. We must deal with tolerances explicitly. Therefore, all numbers should use Python\u2019s \u201c decimal.Decimal \u201d objects. Conveniently, Python v3.x supports a C implementation of decimal types natively (in its standard library; this used to be an external \u201ccdecimal\u201d package to install but it has been integrated in the C/Python interpreter). The default constructor for decimal objects does not support some of the input syntax we want to allow, such as commas in the integer part of numbers (e.g., \u201c278 , 401.35 USD\u201d) or initialization from an empty string. These are important cases to handle. So I provide a special constructor to accommodate these inputs: beancount.core.number.D() . This is the only method that should be used to create decimal objects: from beancount.core.number import D \u2026 number = D(\"2,402.21\") I like to import the \u201c D \u201d symbol by itself (and not use number.D ). All the number-related code is located under beancount.core.number . Some number constants have been defined as well: ZERO , ONE , and HALF . Use those instead of explicitly constructed numbers (such as D(\"1\") ) as it makes it easier to grep for such initializations.","title":"Number"},{"location":"beancount_design_doc.html#commodity","text":"A commodity , or currency (I use both names interchangeably in the code and documentation) is a string that represents a kind of \u201cthing\u201d that can be stored in accounts. In the implementation, it is represented as a Python str object (there is no module with currency manipulation code). These strings may only contain capital letters and numbers and some special characters (see the lexer code). Beancount does not predefine any currency names or categories\u2014all currencies are created equal. Really. It genuinely does not know anything special about dollars or euros or yen or anything else. The only place in the source code where there is a reference to those is in the tests. There is no support for syntax using \u201c$\u201d or other currency symbols; I understand that some users may want this syntax, but in the name of keeping the parser very simple and consistent I choose not to provide that option. Moreover, Beancount does not make a distinction between commodities which represent \"money\" and others that do not (such as shares, hours, etc.). These are treated the same throughout the system. It also does not have the concept of a \"home\" currency 2 ; it's a genuinely multi-currency system. Currencies need not be defined explicitly in the input file format; you can just start using them in the file and they will be recognized as such by their unique syntax (the lexer recognizes and emits currency tokens). However, you may declare some using a Commodity directive. This is only provided so that a per-commodity entity exists upon which the user can attach some metadata, and some reports and plugins are able to find and use that metadata.","title":"Commodity"},{"location":"beancount_design_doc.html#account","text":"An account is basically just the name of a bucket associated with a posting and is represented as a simple string (a Python str object). Accounts are detected and tokenized by the lexer and have names with at least two words separated by a colon (\":\"). Accounts don\u2019t have a corresponding object type; we just refer to them by their unique name string (like filenames in Python). When per-account attributes are needed, we can extract the Open directives from the stream of entries and find the one corresponding to the particular account we\u2019re looking for. Similar to Python\u2019s os.path module, there are some routines to manipulate account names in beancount.core.account and the functions are named similarly to those of the os.path module. The first component of an account\u2019s name is limited to one of five types: Assets Liabilities Equity Income Expenses The names of the account types as read in the input syntax may be customized with some \"option\" directives, so that you can change those names to another language, or even just rename \"Income\" to \"Revenue\" if you prefer that. The beancount.core.account_types module contains some helpers to deal with these. Note that the set of account names forms an implicit hierarchy. For example, the names: Assets:US:TDBank:Checking Assets:US:TDBank:Savings implicitly defines a tree of nodes with parent nodes \"Assets\", \"US\", \"TDBank\" with two leaf nodes \"Checking\" and \"Savings\". This implicit tree is never realized during processing, but there is a Python module that allows one to do this easily (see beancount.core.realization ) and create linearized renderings of the tree.","title":"Account"},{"location":"beancount_design_doc.html#flag","text":"A \u201cflag\u201d is a single-character string that may be associated with Transactions and Postings to indicate whether they are assumed to be correct (\"reconciled\") or flagged as suspicious. The typical value used on transaction instances is the character \u201c*\u201d. On Postings, it is usually left absent (and set to a None).","title":"Flag"},{"location":"beancount_design_doc.html#amount","text":"An Amount is the combination of a number and an associated currency, conceptually: Amount = (Number, Currency) Amount instances are used to represent a quantity of a particular currency (the \u201cunits\u201d) and the price on a posting. A class is defined in beancount.core.amount as a simple tuple-like object. Functions exist to perform simple math operations directly on instances of Amount. You can also create Amount instance using amount.from_string() , for example: value = amount.from_string(\"201.32 USD\")","title":"Amount"},{"location":"beancount_design_doc.html#cost","text":"A Cost object represents the cost basis for a particular lot of a commodity. Conceptually, it is Cost = (Number, Currency, Date, Label) The number and currency is that of the cost itself, not of the commodity. For example, if you bought 40 shares of AAPL stock at 56.78 USD, the Number is a \u201c56.78\u201d decimal and the Currency is \u201cUSD.\u201d For example: Cost(Decimal(\"56.78\"), \"USD\", date(2012, 3, 5), \"lot15\") The Date is the acquisition date of the corresponding lot (a datetime.date object). This is automatically attached to the Cost object when a posting augments an inventory\u2014the Transaction\u2019s date is automatically attached to the Cost object\u2014or if the input syntax provides an explicit date override. The Label can be any string. It is provided as a convenience for a user to refer to a particular lot or disambiguate similar lots. On a Cost object, the number, currency and date attributes are always set. If the label is unset, it has a value of \u201cNone.\u201d","title":"Cost"},{"location":"beancount_design_doc.html#costspec","text":"In the input syntax, we allow users to provide as little information as necessary in order to disambiguate between the lots contained in the inventory prior to posting. The data provided filters the set of matching lots to an unambiguous choice, or to a subset from which an automated booking algorithm will apply (e.g., \u201cFIFO\u201d). In addition, we allow the user to provide either the per-unit cost and/or the total-cost. This convenience is useful to let Beancount automatically compute the per-unit cost from a total of proceeds. CostSpec = (Number-per-unit, Number-total, Currency, Date, Label, Merge) Since any of the input elements may be omitted, any of the attributes of a CostSpec may be left to None. If a number is missing and necessary to be filled in, the special value \u201cMISSING\u201d will be set on it. The Merge attribute it used to record a user request to merge all the input lots before applying the transaction and to merge them after. This is the method used for all transactions posted to an account with the \u201cAVERAGE\u201d booking method.","title":"CostSpec"},{"location":"beancount_design_doc.html#position","text":"A position represents some units of a particular commodity held at cost. It consists simply in Position = (Units, Cost) Units is an instance of Amount , and Cost is an instance of Cost , or a null value if the commodity is not held at cost. Inventories contain lists of Position instances. See its definition in beancount.core.position .","title":"Position"},{"location":"beancount_design_doc.html#posting","text":"Each Transaction directive is composed of multiple Postings (I often informally refer to these in the code and on the mailing-list as the \u201clegs\u201d of a transaction). Each of these postings is associated with an account, a position and an optional price and flag: Posting = (Account, Units, Cost-or-CostSpec, Price, Flag, Metadata) As you can see, a Posting embeds its Position instance 3 . The Units is an Amount , and the \u2018cost\u2019 attribute refers to either a Cost or a CostSpec instance (the parser outputs Posting instances with an CostSpec attribute which is resolved to a Cost instance by the booking process; see How Inventories Work for details). The Price is an instance of Amount or a null value. It is used to declare a currency conversion for balancing the transaction, or the current price of a position held at cost. It is the Amount that appears next to a \u201c@\u201d in the input. Flags on postings are relatively rare; users will normally find it sufficient to flag an entire transaction instead of a specific posting. The flag is usually left to None; if set, it is a single-character string. The Posting type is defined in beancount.core.data , along with all the directive types.","title":"Posting"},{"location":"beancount_design_doc.html#inventory","text":"An Inventory is a container for an account\u2019s balance in various lots of commodities. It is essentially a list of Position instances with suitable operations defined on it. Conceptually you may think of it as a mapping with unique keys: Inventory = [Position1, Position2, Position3, \u2026 , PositionN] Generally, the combination of a position\u2019s ( Units.Currency, Cost) is kept unique in the list, like the key of a mapping. Positions for equal values of currency and cost are merged together by summing up their Units.Number and keeping a single position for it. And simple positions are mixed in the list with positions held at cost. The Inventory is one of the most important and oft-used object in Beancount\u2019s implementation, because it is used to sum the balance of one or more accounts over time. It is also the place where the inventory reduction algorithms get applied to, and traces of that mechanism can be found there. The \u201c How Inventories Work \u201d document provides the full detail of that process. For testing, you can create initialized instances of Inventory using inventory.from_string() . All the inventory code is written in beancount.core.inventory .","title":"Inventory"},{"location":"beancount_design_doc.html#about-tuples-mutability","text":"Despite the program being written in a language which does not make mutability \u201cdifficult by default\u201d, I designed the software to avoid mutability in most places. Python provides a \u201c collections.namedtuple \u201d factory that makes up new record types whose attributes cannot be overridden. Well\u2026 this is only partially true: mutable attributes of immutable tuples can be modified. Python does not provide very strong mechanisms to enforce this property. Regardless, functional programming is not so much an attribute of the language used to implement our programs than of the guarantees we build into its structure. A language that supports strong guarantees helps to enforce this. But if, even by just using a set of conventions, we manage to mostly avoid mutability, we have a mostly functional program that avoids most of the pitfalls that occur from unpredictable mutations and our code is that much easier to maintain. Programs with no particular regard for where mutation occurs are most difficult to maintain. By avoiding most of the mutation in a functional approach, we avoid most of those problems. Most of the data structures used in Beancount are namedtuples , which make the modification of their attributes inconvenient on purpose. Most of the code will attempt to avoid mutation, except for local state (within a function) or in a narrow scope that we can easily wrap our head around. Where mutation is possible, by convention I try to avoid it by default. When we do mutation, I try to document the effects. I avoid the creation of classes which mutate their internal state, except for a few cases (e.g. the web application). I prefer functions to objects and where I define classes I mostly avoid inheritance. These properties are especially true for all the small objects: Amount, Lot, Position, Posting objects, and all the directives types from beancount.core.data . On the other hand, the Inventory object is nearly always used as an accumulator and does allow the modification of its internal state (it would require a special, persistent data structure to avoid this). You have to be careful how you share access to Inventory objects\u2026 and modify them, if you ever do. Finally, the loader produces lists of directives which are all simple namedtuple objects. These lists form the main application state. I\u2019ve avoided placing these inside some special container and instead pass them around explicitly, on purpose. Instead of having some sort of big \u201capplication\u201d object, I\u2019ve trimmed down all the fat and all your state is represented in two things: A dated and sorted list of directives which can be the subject of stream processing and a list of constant read-only option values. I think this is simpler. I credit my ability to make wide-ranging changes to a mid-size Python codebase to the adoption of these principles. I would love to have types in order to safeguard against another class of potential errors, and I plan to experiment with Python 3.5\u2019s upcoming typing module .","title":"About Tuples & Mutability"},{"location":"beancount_design_doc.html#summary","text":"The following diagram explains how these objects relate to each other, starting from a Posting. For example, to access the number of units of a postings you could use posting.units.number For the cost currency: posting.cost.currency You can print out the tuples in Python to figure out their structure.","title":"Summary"},{"location":"beancount_design_doc.html#previous-design","text":"For the sake of preservation, if you go back in time in the repository, the structure of postings was deeper and more complex. The new design reflects a flatter and simpler version of it. Here is what the old design used to look like:","title":"Previous Design"},{"location":"beancount_design_doc.html#directives","text":"The main production from loading a Beancount input file is a list of directives . I also call these entries interchangeably in the codebase and in the documents. There are directives of various types: Transaction Balance & Pad Open & Close Commodity Note Event Price Document In a typical Beancount input file, the great majority of entries will be Transactions and some Balance assertions. There will also be Open & perhaps some Close entries. Everything else is optional. Since these combined with a map of option values form the entire application state, you should be able to feed those entries to functions that will produce reports. The system is built around this idea of processing a stream of directives to extract all contents and produce reports, which are essentially different flavors of filtering and aggregations of values attached to this stream of directives.","title":"Directives"},{"location":"beancount_design_doc.html#common-properties","text":"Some properties are in common to all the directives: Date. A datetime.date object. This is useful and consistent. For a Transaction, that\u2019s the date at which it occurs. For an Open or Close directive, that\u2019s the date at which the account was opened or closed. For a Pad directive, that\u2019s the date at which to insert a transaction. For a Note or Document, it is the date at which to insert the note in the stream of postings of that account. For an Event, it is the date at which it occurs. For a Commodity directive which essentially provides a per-commodity hanging point for commodity-related metadata, the date isn\u2019t as meaningful; I choose to date those on the day the commodity was created. Meta . All the directives have metadata attribute (a Python dict object). The purpose of metadata is to allow the user to hang any kind of ancillary data they want and then use this in scripts or queries. Posting instances also have a metadata attribute.","title":"Common Properties"},{"location":"beancount_design_doc.html#transactions","text":"The Transaction directive is the subject of Beancount and is by far the most common directive found in input files and is worth of special attention. The function of a bookkeeping system is to organize the Postings attached to Transactions in various ways. All the other types of entries occupy supporting roles in our system. A Transaction has the following additional fields.","title":"Transactions"},{"location":"beancount_design_doc.html#flag_1","text":"The single-character flag is usually there to replace the \u201ctxn\u201d keyword (Transaction is the only directive which allows being entered without entering its keyword). I\u2019ve been considering changing the syntax definition somewhat to allow not entering the flag nor the keyword, because I\u2019d like to eventually support that. Right now, either the flag or keyword is required. The flag attribute may be set to None.","title":"Flag"},{"location":"beancount_design_doc.html#payee-narration","text":"The narration field is a user-provided description of the transaction, such as \"Dinner with Mary-Ann.\" You can put any information in this string. It shows up in the journal report. Oftentimes it is used to enrich the transaction with context that cannot be imported automatically, such as \"transfer from savings account to pay for car repairs.\" The payee name is optional, and exists to describe the entity with which the transaction is conducted, such as \"Whole Foods Market\" or \"Shell.\" Note that I want to be able to produce reports for all transactions associated with a particular payee, so it would be nice to enter consistent payee names. The problem with this is that the effort to do this right is sometimes too great. Either better tools or looser matching over names is required in order to make this work. The input syntax also allows only a single string to be provided. By default this becomes the narration, but I\u2019ve found that in practice it can be useful to have just the payee. It\u2019s just a matter of convenience. At the moment, if you want to enter just the payee string you need to append an empty narration string. This should be revisited at some point.","title":"Payee & Narration"},{"location":"beancount_design_doc.html#tags","text":"Tags are sets of strings that can be used to group sets of transactions (or set to None if there are no tags). A view of this subset of transactions can then be generated, including all the usual reports (balance sheet, income statement, journals, etc.). You can tag transactions for a variety of purposes; here are some examples: All transactions during a particular travel might be tagged to later summarize your trip expenses. Those transactions will usually occur around the same date and therefore there is convenient syntax used to automatically tag many transactions that are declared together in a file. Transactions related to a particular project to be accomplished. I took some classes in an online program once, and tagged all related expenses to it. I use this to track all my immigration-related expenses for a particular stage (e.g. green card). Declaring a group of expenses to be paid back by an employer (or your own company) later on. Expenses related to moving between locations. Typical tag names that I use for tags look like #trip-israel-2012 , #conference-siggraph , and #course-cfa . In general, tags are useful where adding a sub-accounts won't do. This is often the case where a group of related expenses are of differing types, and so they would not belong to a single account. Given the right support from the query tools, they could eventually be subsumed by metadata\u2014I have been considering converting tags into metadata keys with a boolean value of True, in order to remove unnecessary complexity.","title":"Tags"},{"location":"beancount_design_doc.html#links","text":"Links are a unique set of strings or None , and in practice will be usually empty for most transactions. They differ from tags only in purpose. Links are there to chain together a list of related transactions and there are tools used to list, navigate and balance a subset of transactions linked together. They are a way for a transaction to refer to other transactions. They are not meant to be used for summarizing. Examples include: transaction-ids from trading accounts (these often provide an id to a \"related\" or \"other\" transaction); account entries associated with related corrections, for example a reversed fee following a phone call could be linked to the original invalid fee from several weeks before; the purchase and sale of a home, and related acquisition expenses. In contrast to tags, their strings are most often unique numbers produced by the importers. No views are produced for links; only a journal of a particular links transactions can be produced and a rendered transaction should be accompanied by an actual \"link\" icon you can click on to view all the other related transactions.","title":"Links"},{"location":"beancount_design_doc.html#postings","text":"A list of Postings are attached to the Transaction object. Technically this list object is mutable but in practice we try not to modify it. A Posting can ever only be part of a single Transaction. Sometimes different postings of the same transaction will settle at different dates in their respective accounts, so eventually we may allow a posting to have its own date to override the transaction's date, to be used as documentation; in the simplest version, we enforce all transactions to occur punctually, which is simple to understand and was not found to be a significant problem in practice. Eventually we might implement this by implicitly converting Transactions with multiple dates into multiple Transactions and using some sort of transfer account to hold the pending balance in-between dates. See the associated proposal for details.","title":"Postings"},{"location":"beancount_design_doc.html#balancing-postings","text":"The fundamental principle of double-entry book-keeping is enforced in each of the Transaction entries: the sum of all postings must be zero. This section describes the specific way in which we do this, and is the key piece of logic that allow the entire system to balance nicely. This is also one of the few places in this system where calculations go beyond simple filtering and aggregation. As we saw earlier, postings are associated with A position, which is a number of units of a lot (which itself may or may not have a cost) An optional conversion price Given this, how do we balance a transaction? Some terminology is in order: for this example posting: Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD Units. The number of units is the number of the position, or \u201c50\u201d. Currency. The commodity of the lot, that is, \u201cHOOL\u201d. Cost. The cost of this position is the number of units times the per-unit cost in the cost currency, that is 50 x 700 = 35000 USD. (Total) Price. The price of a unit is the attached price amount, 920 USD. The total price of a position is its number of units times the conversion price, in this example 50 x 920 = 46000 USD. Weight. The amount used to balance each posting in the transaction: If the posting has an associated cost, we calculate the cost of the position (regardless of whether a price is present or not); If the posting has no associated cost but has a conversion price, we convert the position into its total price; Finally, if the posting has no associated cost nor conversion price, the number of units of the lot are used directly. Balancing is really simple: each of the Posting's positions are first converted into their weight. These amounts are then grouped together by currency, and the final sum for each currency is asserted to be close to zero, that is, within a small amount of tolerance (as inferred by a combination of by the numbers in the input and the options ). The following example is one of case (2), with a price conversion and a regular leg with neither a cost nor a price (case 3): 2013-07-22 * \"Wired money to foreign account\" Assets:Investment:HOOL -35350 CAD @ 1.01 USD ;; -35000 USD (2) Assets:Investment:Cash 35000 USD ;; 35000 USD (3) ;;------------------ ;; 0 USD In the next example, the posting for the first leg has a cost, the second posting has neither: 2013-07-22 * \"Bought some investment\" Assets:Investment:HOOL 50 HOOL {700 USD} ;; 35000 USD (1) Assets:Investment:Cash -35000 USD ;; -35000 USD (3) ;;------------------ ;; 0 USD Here's a more complex example with both a price and a cost; the price here is ignored for the purpose of balance and is used only to enter a data-point in the internal price database: 2013-07-22 * \"Sold some investment\" Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD ;; -35000 USD (1) Assets:Investment:Cash 46000 USD ;; 46000 USD (3) Income:CapitalGains -11000 USD ;; -11000 USD (3) ;;------------------ ;; 0 USD Finally, here's an example with multiple commodities: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking 4585.38 USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib 540.00 IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation -4.62 VACHR Here there are three groups of weights to balance: USD: (4485.38) + (-25.38) + (-5000) + (540) ~= 0 USD IRAUSD: (540.00) + (-540.00) ~= 0 IRAUSD VACHR: (4.62) + (-4.62) ~= 0 VACHR","title":"Balancing Postings"},{"location":"beancount_design_doc.html#elision-of-amounts","text":"Transactions allow for at most one posting to be elided and automatically set; if an amount was elided, the final balance of all the other postings is attributed to the balance. With the inventory booking proposal , it should be extended to be able to handle more cases of elision. An interesting idea would be to allow the elided postings to at least specify the currency they're using. This would allow the user to elide multiple postings, like this: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation VACHR","title":"Elision of Amounts"},{"location":"beancount_design_doc.html#stream-processing","text":"An important by-product of representing all state using a single stream of directives is that most of the operations in Beancount can be implemented by simple functions that accept the list of directives as input and output a modified list of directives. For example, The \u201cPad\u201d directive is implemented by processing the stream of transactions, accumulating balances, inserting a new padding transaction after the Pad directive when a balance assertion fails. Summarization operations (open books, close books, clear net income to equity) are all implemented this way, as functional operators on the list of streams. For example, opening the books at a particular date is implemented by summing all balances before a certain date and replacing those transactions by new transactions that pull the final balance from an Equity account. Closing the books only involves moving the balances of income statement account to Equity and truncation the list of transactions to that date. This is very elegant\u2014the reporting code can be oblivious to the fact that summarization has occurred. Prices on postings held with a cost are normally ignored. The \u201cimplicit prices\u201d option is implemented using a plugin which works its magic by processing the stream of operations and inserting otherwise banal Price directives automatically when such a posting is found. Many types of verifications are also implemented this way; the sellgains plugin verifies that non-income balances check against the converted price of postings disregarding the cost. This one does not insert any new directives, but may generate new errors. These are just some examples. I\u2019m aiming to make most operations work this way. This design has proved to be elegant, but also surprisingly flexible and it has given birth to the plugins system available in Beancount; you might be surprised to learn that I had not originally thought to provide a plugins system... it just emerged over time teasing abstractions and trying to avoid state in my program. I am considering experimenting with weaving the errors within the stream of directives by providing a new \u201cError\u201d directive that could be inserted by stream processing functions. I\u2019m not sure whether this would make anything simpler yet, it\u2019s just an idea at this point [July 2015].","title":"Stream Processing"},{"location":"beancount_design_doc.html#stream-invariants","text":"The stream of directives comes with a few guarantees you can rest upon: All the directives are ordered by date. Because many dates have multiple directives, in order to have a stable sorting order the line number in the file is used as a secondary sort key. All uses of an account is preceded in the stream by a corresponding Open entry. If the input did not provide one, an Open directive is automatically synthesized. All Balance directives precede any other directive on a particular day. This is enforced in order to make the processing of balance assertions straightforward and to emphasize that their semantics is to occur at the beginning of the day.","title":"Stream Invariants"},{"location":"beancount_design_doc.html#loader-processing-order","text":"The process of loading a list of entries from an input file is the heart of the project. It is important to understand it in order to understand how Beancount works. Refer to the diagram below. It consists of A parsing step that reads in all the input files and produces a stream of potentially incomplete directives. The processing of this stream of entries by built-in and user-specified plugins. This step potentially produces new error objects. A final validation step that ensures the invariants have not been broken by the plugins. The beancount.loader module orchestrates this processing. In the code and documents, I\u2019m careful to distinguish between the terms \u201cparsing\u201d and \u201cloading\u201d carefully. These two concepts are distinct. \u201cParsing\u201d is only a part of \u201cloading.\u201d The user-specified plugins are run in the order they are provided in the input files. Raw mode. Some users have expressed a desire for more control over which built-in plugins are run and in which order they are to be run, and so enabling the \u201craw\u201d option disables the automatic loading of all built-in plugins (you have to replace those with explicit \u201cplugin\u201d directives if you want to do that). I\u2019m not sure if this is going to be useful yet, but it allows for tighter control over the loading process for experimentation.","title":"Loader & Processing Order"},{"location":"beancount_design_doc.html#loader-output","text":"The parser and the loader produce three lists: entries: A list of directive tuples from beancount.core.data. This is the stream of data which should consists mostly of Transaction and Balance instances. As much as possible, all data transformations are performed by inserting or removing entries from this list. Throughout the code and documents, I refer to these as entries or directives interchangeably. errors: A list of error objects. In Beancount I don\u2019t use exceptions to report errors; rather, all the functions produce error objects and they are displayed at the most appropriate time. (These deserve a base common type but for now the convention they respect is that they all provide a source (filename, line-no), message and entry attributes.) options_map: A dict of the options provided in the file and derived during parsing. Though this is a mutable object, we never modify it once produced by the parser.","title":"Loader Output"},{"location":"beancount_design_doc.html#parser-implementation","text":"The Beancount parser is a mix of C and Python 3 code. This is the reason you need to compile anything in the first place. The parser is implemented in C using a flex tokenizer and a Bison parser generator, mainly for performance reasons. I chose the C language and these crufty old GNU tools because they are portable and will work everywhere. There are better parser generators out there, but they would either require my users to install more compiler tools or exotic languages. The C language is a great common denominator. I want Beancount to work everywhere and to be easy to install. I want this to make it easy on others, but I also want this for myself, because at some point I\u2019ll be working on other projects and the less dependencies I have the lighter it will be to maintain aging software. Just looking ahead. (That\u2019s also why I chose to use Python instead of some of the other languages I\u2019m more naturally attracted to these days (Clojure, Go, ML); I need Beancount to work... when I\u2019m counting the beans I don\u2019t have time to debug problems. Python is careful about its changes and it will be around for a long long time as is.) There is a lexer file lexer.l written in flex and a Bison grammar in grammar.y. These tools are used to generate the corresponding C source code (lexer.h/.c and grammar.h/.c). These, along with some hand-written C code that defines some interface functions for the module (parser.h/.c), are compiled into an extension module (_parser.so). Eventually we could consider creating a small dependency rule in setup.py to invoke flex and Bison automatically but at the moment, in order to minimize the installation burden, I check the generated source code in the repository (lexer.h/c and grammar.h/c). The interaction between the Python and C code works like this: You import beancount.parser.parser and invoke either parse_file() or parse_string(). This uses code in grammar.py to create a Builder object which essentially provides callbacks in Python to process grammar rules. parser.py calls a C function in the extension module with the Builder object. That C code sets up flex to be able to read the input file or string then hands over control to the parser by calling the generated C function \u201cyyparse()\u201d. yyparse() is the parser and will fetch input tokens from the lexer C code using successive calls to \u201cyylex()\u201d and attempt to reduce rules. When a rule or token is successfully reduced, the C code invokes callback methods on the Builder object you provided. The parser\u2019s rules communicate between each other by passing around \u201cPyObject*\u201d instances, so that code has to be a little careful about correctly handling reference counting. This allows me to run Python code on rule reduction, which makes it really easy to customize parser behaviour yet maintain the performance of the grammar rule processing in C. Note that the grammar.py Builder derives from a similar Builder class defined in lexer.py, so that lexer tokens recognized calls methods defined in the lexer.py file to create them and parser rule correspondingly calls methods from grammar.py\u2019s Builder. This isolation is not only clean, it also makes it possible to just use the lexer to tokenize a file from Python (without parsing) for testing the tokenizer; see the tests where this is used if you\u2019re curious how this works. I just came up with this; I haven\u2019t seen this done anywhere else. I tried it and the faster parser made a huge difference compared to using PLY so I stuck with that. I quite like the pattern, it\u2019s a good compromise that offers the flexibility of a parser generator yet allows me to handle the rules using Python. Eventually I\u2019d like to move just some of the most important callbacks to C code in order to make this a great deal faster (I haven\u2019t done any real performance optimizations yet). This has worked well so far but for one thing: There are several limitations inherent in the flex tokenizer that have proved problematic. In particular, in order to recognize transaction postings using indent I have had to tokenize the whitespace that occurs at the beginning of a line. Also, single-characters in the input should be parsed as flags and at the moment this is limited to a small subset of characters. I\u2019d like to eventually write a custom lexer with some lookahead capabilities to better deal with these problems (this is easy to do).","title":"Parser Implementation"},{"location":"beancount_design_doc.html#two-stages-of-parsing-incomplete-entries","text":"At the moment, the parser produces transactions which may or may not balance. The validation stage which runs after the plugins carries out the balance assertions. This degree of freedom is allowed to provide the opportunity for users to enter incomplete lists of postings and for corresponding plugins to automate some data entry and insert completing postings. However, the postings on the transactions are always complete objects, with all their expected attributes set. For example, a posting which has its amount elided in the input syntax will have a filled in position by the time it comes out of the parser. We will have to loosen this property when we implement the inventory booking proposal , because we want to support the elision of numbers whose values depend on the accumulated state of previous transactions. Here is an obvious example. A posting like this 2015-04-03 * \"Sell stock\" Assets:Investments:AAPL -10 AAPL {} ... should succeed if at the beginning of April 3rd the account contains exactly 10 units of AAPL, in either a single or multiple lots. There is no ambiguity: \u201csell all the AAPL,\u201d this says. However, the fact of its unambiguity assumes that we\u2019ve computed the inventory balance of that account up until April 3rd\u2026 So the parser will need to be split into two phases: A simple parsing step that produces potentially incomplete entries with missing amounts A separate step for the interpolation which will have available the inventory balances of each account as inputs. This second step is where the booking algorithms (e.g., FIFO) will be invoked from. See the diagram above for reference. Once implemented, everything else should be the same.","title":"Two Stages of Parsing: Incomplete Entries"},{"location":"beancount_design_doc.html#the-printer","text":"In the same package as the parser lives a printer. This isolates all the functionality that deals with the Beancount \u201clanguage\u201d in the beancount.parser package: the parser converts from input syntax to data structure and the printer does the reverse. No code outside this package should concern itself with the Beancount syntax. At some point I decided to make sure that the printer was able to round-trip through the parser, that is, given a stream of entries produced by the loader, you should be able to convert those to text input and parse them back in and the resulting set of entries should be the same (outputting the re-read input back to text should produce the same text), e.g., Note that the reverse isn\u2019t necessarily true: reading an input file and processing it through the loader potentially synthesizes a lot of entries (thanks to the plugins), so printing it back may not produce the same input file (even ignoring reordering and white space concerns). This is a nice property to have. Among other things, it allows us to use the parser to easily create tests with expected outputs. While there is a test that attempts to protect this property, some of the recent changes break it partially: Metadata round-tripping hasn\u2019t been tested super well. In particular, some of the metadata fields need to be ignored (e.g., filename and lineno). Some directives contain derived data, e.g. the Balance directive has a \u201cdiff_amount\u201d field that contains the difference when there is a failure in asserting a balance. This is used to report errors more easily. I probably need to remove these exceptions at some point because it is the only one of its kind (I could replace it with an inserted \u201cError\u201d directive). This probably needs to get refined a bit at some point with a more complete test (it\u2019s not far as it is).","title":"The Printer"},{"location":"beancount_design_doc.html#uniqueness-hashing","text":"In order to make it possible to compare directives quickly, we support unique hashing of all directives, that is, from each directive we should be able to produce a short and unique id. We can then use these ids to perform set inclusion/exclusion/comparison tests for our unit tests. We provide a base test case class with assertion methods that use this capability . This feature is used liberally throughout our test suites. This is also used to detect and remove duplicates. This feature is optional and enabled by the beancount.plugins.noduplicates plugin. Note that the hashing of directives currently does not include user meta-data .","title":"Uniqueness & Hashing"},{"location":"beancount_design_doc.html#display-context","text":"Number are input with different numbers of fractional digits: 500 -> 0 fractional digits 520.23 -> 2 fractional digits 1.2357 -> 4 fractional digits 31.462 -> 3 fractional digits Often, a number used for the same currency is input with a different number of digits. Given this variation in the input, a question that arises is how to render the numbers consistently in reports? Consider that: We would like that the user not to have to specify the number of fractional digits to use for rendering by default. Rendering numbers may differ depending on the context: most of the time we want to render numbers using their most commonly seen number of digits, rounding if necessary, but when we are rendering conversion rates we would like to render numbers using the maximum number of digits ever seen. The number of digits used tends to vary with the commodity they represent. For example, US dollars will usually render with two digits of precision; the number of units of a mutual fund such as RGAGX might be recorded by your broker with 3 digits of precision. Japanese Yen will usually be specified in integer units. We want to render text report which need to align numbers with varying number of fractional digits together, aligning them by their period or rightmost digits, and possibly rendering a currency thereafter. In order to deal with this thorny problem, I built a kind of accumulator which is used to record all numbers seen from some input and tally statistics about the precisions witnessed for each currency. I call this a DisplayContext . From this object one can request to build a DisplayFormatter object which can be used to render numbers in a particular way. I refer to those objects using the variable names dcontext and dformat throughout the code. The parser automatically creates a DisplayContext object and feeds it all the numbers it sees during parsing. The object is available in the options_map produced by the loader.","title":"Display Context"},{"location":"beancount_design_doc.html#realization","text":"It occurs often that one needs to compute the final balances of a list of filtered transactions, and to report on them in a hierarchical account structure. See diagram below.","title":"Realization"},{"location":"beancount_design_doc.html#_1","text":"For the purpose of creating hierarchical renderings, I provide a process called a \u201c realization .\u201d A realization is a tree of nodes, each of which contains An account name, A list of postings and other entries to that account, and The final balance for that account, A mapping of sub-account names to child nodes. The nodes are of type \u201cRealAccount,\u201d which is also a dict of its children. All variables of type RealAccount in the codebase are by convention prefixed by \u201creal_*\u201d. The realization module provides functions to iterate and produce hierarchical reports on these trees of balances, and the reporting routines all use these. For example, here is a bit of code that will dump a tree of balances of your accounts to the console: import sys from beancount import loader from beancount.core import realization entries, errors, options_map = loader.load_file(\"filename.beancount\") real_root = realization.realize(entries) realization.dump_balances(real_root, file=sys.stdout)","title":""},{"location":"beancount_design_doc.html#the-web-interface","text":"Before the appearance of the SQL syntax to filter and aggregate postings, the only report produced by Beancount was the web interface provided by bean-web. As such, bean-web evolved to be good enough for most usage and general reports.","title":"The Web Interface"},{"location":"beancount_design_doc.html#reports-vs-web","text":"One of the important characteristics of bean-web is that it should just be a thin dispatching shell that serves reports generated from the beancount.reports layer. It used to contain the report rendering code itself, but at some point I began to factor out all the reporting code to a separate package in order to produce reports to other formats, such as text reports and output to CSV. This is mostly finished, but at this time [July 2015] some reports remain that only support HTML output. This is why.","title":"Reports vs. Web"},{"location":"beancount_design_doc.html#client-side-javascript","text":"I would like to eventually include a lot more client-side scripting in the web interface. However, I don\u2019t think I\u2019ll be able to work on this for a while, at least not until all the proposals for changes to the core are complete (e.g., inventory booking improvements, settlement splitting and merging, etc.). If you\u2019d like to contribute to Beancount, improving bean-web or creating your own visualizations would be a great venue.","title":"Client-Side JavaScript"},{"location":"beancount_design_doc.html#the-query-interface","text":"The current query interface is the result of a prototype . It has not yet been subjected to the amount of testing and refinement as the rest of the codebase. I\u2019ve been experimenting with it and have a lot of ideas for improving the SQL language and the kinds of outputs that can be produced from it. I think of it as \u201c70% done\u201d. However, it works, and though some of the reports can be a little clunky to specify, it produces useful results. It will be the subject of a complete rewrite at some point and when I do, I will keep the current implementation for a little while so that existing scripts don\u2019t just break; I\u2019ll implement a v2 of the shell.","title":"The Query Interface"},{"location":"beancount_design_doc.html#design-principles","text":"","title":"Design Principles"},{"location":"beancount_design_doc.html#minimize-configurability","text":"First, Beancount should have as few options as possible. Second, command-line programs should have no options that pertain to the processing and semantic of the input file (options that affect the output or that pertain to the script itself are fine). The goal is to avoid feature creep. Just a few options opens the possibility of them interacting in complex ways and raises a lot of questions. In this project, instead of providing options to customize every possible thing, I choose to bias the design towards providing the best behavior by default, even if that means less behavior. This is the Apple approach to design. I don\u2019t usually favor this approach for my projects, but I chose it for this particular one. What I\u2019ve learned in the process is how far you can get and still provide much of the functionality you originally set out for. Too many command-line options makes a program difficult to use. I think Ledger suffers from this, for instance. I can never remember options that I don\u2019t use regularly, so I prefer to just design without them. Options that affect semantics should live in the file itself (and should be minimized as well). Options provided when running a process should be only to affect this process. Having less options also makes the software much easier to refactor. The main obstacle to evolving a library of software is the number of complex interactions between the codes. Guaranteeing a well-defined set of invariants makes it much easier to later on split up functionality or group it differently. So.. by default I will resist making changes that aren't generic or that would not work for most users. On the other hand, large-scale changes that would generalize well and that require little configurability are more likely to see implementation.","title":"Minimize Configurability"},{"location":"beancount_design_doc.html#favor-code-over-dsls","text":"Beancount provides a simple syntax, a parser and printer for it, and a library of very simple data record types to allow a user to easily write their scripts to process their data, taking advantage of the multitude of libraries available from the Python environment. Should the built-in querying tools fail to suffice, I want it to be trivially easy for someone to build what they need on top of Beancount. This also means that the tools provided by Beancount don\u2019t have to support all the features everyone might ever possibly want. Beancount\u2019s strength should be representational coherence and simplicity rather than the richness of its reporting features. Creating new kinds of reports should be easy; changing the internals should be hard. (That said, it does provide an evolving palette of querying utilities that should be sufficient for most users.) In contrast, the implementation of the Ledger system provides high-level operations that are invoked as \"expressions\" defined in a domain-specific language, expressions which are either provided on the command-line or in the input file itself. For the user, this expression language is yet another thing to learn, and it\u2019s yet another thing that might involve limitations requiring expansion and work\u2026 I prefer to avoid DSLs if possible and use Python\u2019s well understood semantics instead, though this is not always sensible.","title":"Favor Code over DSLs"},{"location":"beancount_design_doc.html#file-format-or-input-language","text":"One may wonder whether Beancount\u2019s input syntax should be a computer language or just a data format. The problem we're trying to solve is essentially that of making it easy for a human being to create a transactions-to-postings-to-accounts-&-positions data structure. What is the difference between a data file format and a simple declarative computer language? One distinction is the intended writer of the file. Is it a human? Or is it a computer? The input files are meant to be manicured by a human, at the very least briefly eyeballed. While we are trying to automate as much of this process as possible via importing code\u2014well, the unpleasant bits, anyway\u2014we do want to ensure that we review all the new transactions that we're adding to the ledger in order to ensure correctness. If the intended writer is a human one could file the input under the computer language rubric (most data formats are designed to be written by computers). We can also judge it by the power of its semantics. Beancount\u2019s language is not one designed to be used to perform computation, that is, you cannot define new \u201cBeancount functions\u201d in it. But it has data types. In this regard, it is more like a file format. Just something to think about, especially in the context of adding new semantics.","title":"File Format or Input Language?"},{"location":"beancount_design_doc.html#grammar-via-parser-generator","text":"The grammar of its language should be compatible with the use of commonly used parser generator tools. It should be possible to parse the language with a simple lexer and some grammar input file. Beancount\u2019s original syntax was modeled after the syntax of Ledger. However, in its attempt to make the amount of markup minimal, that syntax is difficult to parse with regular tools. By making simple changes to the grammar, e.g. adding tokens for strings and accounts and commodities, the Beancount v2 reimplementation made it possible to use a flex/bison parser generator. This has important advantages: It makes it really easy to make incremental changes to the grammar. Using a custom parser without a well-defined grammar can make it quite difficult to prototype syntax changes. It makes it much easier to implement a parser in another computer language. I\u2019d like to eventually be able to parse Beancount input files in other languages. The data structures are simple enough that it should be easy to reimplement the core in a different language. It\u2019s just a lot less code to write and maintain. (Eventually I\u2019d like to create a file format to generate parsers in multiple languages from a single input. That will be done later, once all the major features are implemented.)","title":"Grammar via Parser Generator"},{"location":"beancount_design_doc.html#future-work","text":"","title":"Future Work"},{"location":"beancount_design_doc.html#tagged-strings","text":"At the moment, account names, tags, links and commodities are represented as simple Python strings. I\u2019ve tried to keep things simple. At some point I\u2019d like to refine this a bit and create a specialization of strings for each of these types. I\u2019d need to assess the performance impact of doing that beforehand. I\u2019m not entirely sure how it could benefit functionality yet.","title":"Tagged Strings"},{"location":"beancount_design_doc.html#errors-cleanup","text":"I\u2019ve been thinking about removing the separate \u201cerrors\u201d list and integrating it into the stream of entries as automatically produced \u201cError\u201d entries. This has the advantage that the errors could be rendered as part of the rest of the stream, but it means we have to process the whole list of entries any time we need to print and find errors (probably not a big deal, given how rarely we output errors).","title":"Errors Cleanup"},{"location":"beancount_design_doc.html#conclusion","text":"This document is bound to evolve. If you have questions about the internals, please post them to the mailing-list .","title":"Conclusion"},{"location":"beancount_design_doc.html#references","text":"Nothing in Beancount is inspired from the following docs, but you may find them interesting as well: Accounting for Computer Scientists I have been considering the addition of a very constrained form of non-balancing postings that would preserve the property of transactions otherwise being balanced, whereby the extra postings would not be able to interact (or sum with) regular balancing postings. \u21a9 There is an option called operating_currency but it is only used to provide good defaults for building reports, never in the processing of transactions. It is used to tell the reporting code which commodities should be broken out to have their own columns of balances, for example. \u21a9 In previous version of Beancount this was not true, the Posting used to have a \u2018position\u2019 attribute and composed it. I prefer the flattened design, as many of the functions apply to either a Position or a Posting. Think of a Posting as deriving from a Position, though, technically it does not. \u21a9","title":"References"},{"location":"beancount_history_and_credits.html","text":"Beancount History and Credits \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/history A history of the development of Beancount and credits for contributors.. History of Beancount \uf0c1 John Wiegley's Ledger was the inspiration for the first version of Beancount. His system is where much of the original ideas for this system came from. When I first learned about double-entry bookkeeping and realized that it could be the perfect method to solve many of the tracking problems I was having in counting various installments for my company, and after a quick disappointment in the solutions that were available at the time (including GnuCash, which I could break very easily), I was quickly led to the Ledger website. There, John laid out his vision for a text-based system, in particular, the idea of doing away with credits and debits and just the signs, and the basics of a convenient input syntax which allows you to omit the amount of one of the postings. I got really excited and had various enthusiastic discussions with him about Ledger and how I wanted to use it. There was some cross-pollination of ideas and John was very receptive to proposals for adding new features. I was so intensely curious about bookkeeping that I began writing a Python interface to Ledger. But in the end I found myself rewriting the entire thing in Python\u2013not for dislike of Ledger but rather because it was simple enough that I could do most of it in little time, and immediately add some features I thought would be useful. One reason for doing so was that instead of parsing the input file every time and generating one report to the console, I would parse it once and then serve the various reports from the in-memory database of transactions, requested via a web page. Therefore, I did not need processing speed, so having to use C++ for performance reasons was not necessary anymore, and I chose to just stick with a dynamic language, which allowed me to add many features quickly. This became Beancount version 1, which stood on its own and evolved its own set of experimental features. My dream was to be able to quickly and easily manipulate these transaction objects to get various views and breakdowns of the data. I don't think the first implementation pushed the limits far enough, however; the code was substandard, to be honest\u2014I wrote it really quickly\u2014and making modifications to the system was awkward. In particular, the way I originally implemented the tracking of capital gains was inelegant and involved some manual counting. I was unhappy with this, but it worked. It was also using ugly ad-hoc parser code in order to remain reasonably compatible with Ledger syntax\u2014I thought it would be interesting to be able to share some common input syntax and use either system for validation and maybe even to convert between them\u2013and that made me wary of making modifications to the syntax to evolve new features, so it stabilized for a few years and I lost interest in adding new features. But it was correct and it worked, mostly, so I used the system continuously from 2008 to 2012 to manage my own personal finances, my company's finances, and joint property with my wife, with detailed transactions; this was great. I learned a lot about how to keep books during that time (the cookbook document is meant to help you do the same). In the summer of 2013, I had an epiphany and realized a correct and generalizable way to implement capital gains, how to merge the tracking of positions held at a cost and regular positions, and a set of simple rules for carrying out operations on them sensibly (the design of how inventories work). I also saw a better way to factor out the internal data structures, and decided to break from the constraint of compatibility with Ledger and redesign the input syntax in order to parse the input language using a lex/yacc generator, which would allow me to easily evolve the input syntax without having to deal with parsing issues, and to create ports to other languages more easily. In the process, a much simpler and more consistent syntax emerged, and in a fit of sweat and a few intense weekends I re-implemented the entire thing from scratch, without even looking at the previous version, clean-room. Beancount version 2 was born, much better than the last, modular, and easy to extend with plugins. The result is what I think is an elegant design involving a small set of objects, a design that could easily be a basis for reimplementation in other computer languages. This is described in the accompanying design doc, for those who would have an interest in having a go at it (this would be welcome and I'm expecting this will happen). While Ledger remains an interesting project bubbling with ideas for expressing the problem of bookkeeping, the second version of Beancount proposes a simpler design that leaves out features that are not strictly necessary and aims at maximum usability through a simple web interface and a very small set of command-line options. Since I had more than 5 years worth of real-world usage experience with the first version, I set a goal for myself to remove all the features that I thought weren't actually useful and introduced unnecessary complexity (like virtual transactions, allowing accounts not in the five types, etc.), and to simplify the system as much as possible without compromising on its usability. The resulting language and software were much simpler to use and understand, the resulting data structures are much simpler to use, the processing more \u201cfunctional\u201d in nature, and the internals of Beancount are very modular. I converted all my 6 years worth of input data\u2013thanks to some very simple Python scripts to manipulate the file\u2013and began using the new version exclusively. It is now in a fully functional state. Ledger's syntax implements many features that trigger a lot of implicitly-defined behaviour; I find this confusing and some of the reasons for this are documented in the many improvement proposals. I don\u2019t like command-line options. In contrast, Beancount's design provides a less expressive, lower-level syntax but one that closely matches the generated in-memory data structure, and that is hopefully more explicit in that way. I think both projects have strengths and weaknesses. Despite its popularity, the latest version of Ledger remains with a number of shortcomings in my view. In particular, reductions in positions are not booked at the moment they occur, but rather they appear to simply accumulate and get matched only at display time, using different methods depending on the command-line options. Therefore, it is possible in Ledger to hold positive and negative lots of the same commodity in an account simultaneously. I believe that this can lead to confusing and even incorrect accounting for trading lots. I think this is still being figured out by the Ledger look-alikes community and that they will eventually converge to the same solution I have, or perhaps even figure out a better solution. In the redesign, I separated out configuration directives that I had used for importing and over many iterations eventually figured out an elegant way to mostly automate imports and automatically detect the various input files and convert them into my input syntax. The result is the LedgerHub design doc . LedgerHub is currently implemented and I\u2019m using it exclusively, but has insufficient testing for me to stamp a public release [July 2014]. You are welcome to try it out for yourself. In June and July 2014, I decided to dump seven years\u2019 worth of thinking about command-line accounting in a set of Google Docs and this now forms the basis for the current documentation of Beancount. I hope to be evolving it gradually from here on. Chronology \uf0c1 Ledger was begun in August 2003 http://web.archive.org/web/*/http://www.newartisans.com/ledger/ Beancount was begun in 2008 http://web.archive.org/web/*/furius.ca/beancount HLedger was also begun in 2008 https://github.com/simonmichael/hledger/graphs/contributors?from=2007-01-21&to=2014-09-08&type=c Credits \uf0c1 So far I\u2019ve been contributing all the code to Beancount. Some users have made significant contributions in other ways: Daniel Clemente has been reporting all the issues he came across while using Beancount to manage his company and personal finances. His relentless perseverance and attention to detail has helped me put a focus on fixing the rough corners of Beancount that I knew to avoid myself. After many years of prodding, my old friend Filippo Tampieri has finally decided to convert his trading history in Beancount format. He has contributed a number of sophisticated reviews of my documentation and is working on adding various methods for evaluating returns on assets.","title":"Beancount History and Credits"},{"location":"beancount_history_and_credits.html#beancount-history-and-credits","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/history A history of the development of Beancount and credits for contributors..","title":"Beancount History and Credits"},{"location":"beancount_history_and_credits.html#history-of-beancount","text":"John Wiegley's Ledger was the inspiration for the first version of Beancount. His system is where much of the original ideas for this system came from. When I first learned about double-entry bookkeeping and realized that it could be the perfect method to solve many of the tracking problems I was having in counting various installments for my company, and after a quick disappointment in the solutions that were available at the time (including GnuCash, which I could break very easily), I was quickly led to the Ledger website. There, John laid out his vision for a text-based system, in particular, the idea of doing away with credits and debits and just the signs, and the basics of a convenient input syntax which allows you to omit the amount of one of the postings. I got really excited and had various enthusiastic discussions with him about Ledger and how I wanted to use it. There was some cross-pollination of ideas and John was very receptive to proposals for adding new features. I was so intensely curious about bookkeeping that I began writing a Python interface to Ledger. But in the end I found myself rewriting the entire thing in Python\u2013not for dislike of Ledger but rather because it was simple enough that I could do most of it in little time, and immediately add some features I thought would be useful. One reason for doing so was that instead of parsing the input file every time and generating one report to the console, I would parse it once and then serve the various reports from the in-memory database of transactions, requested via a web page. Therefore, I did not need processing speed, so having to use C++ for performance reasons was not necessary anymore, and I chose to just stick with a dynamic language, which allowed me to add many features quickly. This became Beancount version 1, which stood on its own and evolved its own set of experimental features. My dream was to be able to quickly and easily manipulate these transaction objects to get various views and breakdowns of the data. I don't think the first implementation pushed the limits far enough, however; the code was substandard, to be honest\u2014I wrote it really quickly\u2014and making modifications to the system was awkward. In particular, the way I originally implemented the tracking of capital gains was inelegant and involved some manual counting. I was unhappy with this, but it worked. It was also using ugly ad-hoc parser code in order to remain reasonably compatible with Ledger syntax\u2014I thought it would be interesting to be able to share some common input syntax and use either system for validation and maybe even to convert between them\u2013and that made me wary of making modifications to the syntax to evolve new features, so it stabilized for a few years and I lost interest in adding new features. But it was correct and it worked, mostly, so I used the system continuously from 2008 to 2012 to manage my own personal finances, my company's finances, and joint property with my wife, with detailed transactions; this was great. I learned a lot about how to keep books during that time (the cookbook document is meant to help you do the same). In the summer of 2013, I had an epiphany and realized a correct and generalizable way to implement capital gains, how to merge the tracking of positions held at a cost and regular positions, and a set of simple rules for carrying out operations on them sensibly (the design of how inventories work). I also saw a better way to factor out the internal data structures, and decided to break from the constraint of compatibility with Ledger and redesign the input syntax in order to parse the input language using a lex/yacc generator, which would allow me to easily evolve the input syntax without having to deal with parsing issues, and to create ports to other languages more easily. In the process, a much simpler and more consistent syntax emerged, and in a fit of sweat and a few intense weekends I re-implemented the entire thing from scratch, without even looking at the previous version, clean-room. Beancount version 2 was born, much better than the last, modular, and easy to extend with plugins. The result is what I think is an elegant design involving a small set of objects, a design that could easily be a basis for reimplementation in other computer languages. This is described in the accompanying design doc, for those who would have an interest in having a go at it (this would be welcome and I'm expecting this will happen). While Ledger remains an interesting project bubbling with ideas for expressing the problem of bookkeeping, the second version of Beancount proposes a simpler design that leaves out features that are not strictly necessary and aims at maximum usability through a simple web interface and a very small set of command-line options. Since I had more than 5 years worth of real-world usage experience with the first version, I set a goal for myself to remove all the features that I thought weren't actually useful and introduced unnecessary complexity (like virtual transactions, allowing accounts not in the five types, etc.), and to simplify the system as much as possible without compromising on its usability. The resulting language and software were much simpler to use and understand, the resulting data structures are much simpler to use, the processing more \u201cfunctional\u201d in nature, and the internals of Beancount are very modular. I converted all my 6 years worth of input data\u2013thanks to some very simple Python scripts to manipulate the file\u2013and began using the new version exclusively. It is now in a fully functional state. Ledger's syntax implements many features that trigger a lot of implicitly-defined behaviour; I find this confusing and some of the reasons for this are documented in the many improvement proposals. I don\u2019t like command-line options. In contrast, Beancount's design provides a less expressive, lower-level syntax but one that closely matches the generated in-memory data structure, and that is hopefully more explicit in that way. I think both projects have strengths and weaknesses. Despite its popularity, the latest version of Ledger remains with a number of shortcomings in my view. In particular, reductions in positions are not booked at the moment they occur, but rather they appear to simply accumulate and get matched only at display time, using different methods depending on the command-line options. Therefore, it is possible in Ledger to hold positive and negative lots of the same commodity in an account simultaneously. I believe that this can lead to confusing and even incorrect accounting for trading lots. I think this is still being figured out by the Ledger look-alikes community and that they will eventually converge to the same solution I have, or perhaps even figure out a better solution. In the redesign, I separated out configuration directives that I had used for importing and over many iterations eventually figured out an elegant way to mostly automate imports and automatically detect the various input files and convert them into my input syntax. The result is the LedgerHub design doc . LedgerHub is currently implemented and I\u2019m using it exclusively, but has insufficient testing for me to stamp a public release [July 2014]. You are welcome to try it out for yourself. In June and July 2014, I decided to dump seven years\u2019 worth of thinking about command-line accounting in a set of Google Docs and this now forms the basis for the current documentation of Beancount. I hope to be evolving it gradually from here on.","title":"History of Beancount"},{"location":"beancount_history_and_credits.html#chronology","text":"Ledger was begun in August 2003 http://web.archive.org/web/*/http://www.newartisans.com/ledger/ Beancount was begun in 2008 http://web.archive.org/web/*/furius.ca/beancount HLedger was also begun in 2008 https://github.com/simonmichael/hledger/graphs/contributors?from=2007-01-21&to=2014-09-08&type=c","title":"Chronology"},{"location":"beancount_history_and_credits.html#credits","text":"So far I\u2019ve been contributing all the code to Beancount. Some users have made significant contributions in other ways: Daniel Clemente has been reporting all the issues he came across while using Beancount to manage his company and personal finances. His relentless perseverance and attention to detail has helped me put a focus on fixing the rough corners of Beancount that I knew to avoid myself. After many years of prodding, my old friend Filippo Tampieri has finally decided to convert his trading history in Beancount format. He has contributed a number of sophisticated reviews of my documentation and is working on adding various methods for evaluating returns on assets.","title":"Credits"},{"location":"beancount_language_syntax.html","text":"Beancount Language Syntax \uf0c1 Martin Blais , Updated: April 2016 http://furius.ca/beancount/doc/syntax Introduction Syntax Overview Directives Ordering of Directives Accounts Commodities / Currencies Strings Comments Directives Open Close Commodity Transactions Metadata Payee & Narration Costs and Prices Balancing Rule - The \u201cweight\u201d of postings Reducing Positions Amount Interpolation Tags The Tag Stack Links Balance Assertions Multiple Commodities Lots Are Aggregated Checks on Parent Accounts Before Close Pad Unused Pad Directives Commodities Cost Basis Multiple Paddings Notes Documents Documents from a Directory Prices Prices from Postings Prices on the Same Day Events Query Custom Metadata Options Operating Currencies Plugins Includes What\u2019s Next? Introduction \uf0c1 This is a user's manual to the language of Beancount, the command-line double-entry bookkeeping system. Beancount defines a computer language that allows you to enter financial transactions in a text file and extract various reports from it. It is a generic counting tool that works with multiple currencies, commodities held at cost (e.g., stocks), and even allows you to track unusual things, like vacation hours, air miles and rewards points, and anything else you might want to count, even beans. This document provides an introduction to Beancount\u2019s syntax and some of the technical details needed for one to understand how it carries out its calculations. This document does not provide an introduction to the double-entry method , a motivation , nor examples and guidelines for entering transactions in your input file, nor how to run the tools . These subjects have their own dedicated documents , and it is recommended that you have had a look at those before diving into this user\u2019s manual. This manual covers the technical details for using Beancount. Syntax Overview \uf0c1 Directives \uf0c1 Beancount is a declarative language. The input consists of a text file containing mainly a list of directives , or entries (we use these terms interchangeably in the code and documentation); there is also syntax for defining various options . Each directive begins with an associated date , which determines the point in time at which the directive will apply, and its type , which defines which kind of event this directive represents. All the directives begin with a syntax that looks like this: YYYY-MM-DD \u2026 where YYYY is the year, MM is the numerical month, and DD the numerical date. All digits are required, for example, the 7th of May 2007 should be \u201c2007-05-07\u201d, including its zeros. Beancount supports the international ISO 8601 standard format for dates, with dashes (e.g., \u201c2014-02-03\u201d), or the same ordering with slashes (e.g., \u201c2014/02/03\u201d). Here are some example directives, just to give you an idea of the aesthetics: 2014-02-03 open Assets:US:BofA:Checking 2014-04-10 note Assets:US:BofA:Checking \"Called to confirm wire transfer.\" 2014-05-02 balance Assets:US:BofA:Checking 154.20 USD The end product of a parsed input file is a simple list of these entries, in a data structure. All operations in Beancount are performed on these entries. Each particular directive type is documented in a section below. Ordering of Directives \uf0c1 The order of declaration of the directives is not important. In fact, the entries are re-sorted chronologically after parsing and before being processed. This is an important feature of the language, because it makes it possible for you to organize your input file any way you like without having to worry about affecting the meaning of the directives. Except for transactions, each directive is assumed to occur at the beginning of each day. For example, you could declare an account being opened on the same day as its first transaction: 2014-02-03 open Assets:US:BofA:Checking 2014-02-03 * \"Initial deposit\" Assets:US:BofA:Checking 100 USD Assets:Cash -100 USD However, if you hypothetically closed that account immediately, you could not declare it closed on the same day, you would have to fudge the date forward by declaring the close on 2/4: 2014-02-04 close Assets:US:BofA:Checking This also explains why balance assertions are verified before any transactions that occur on the same date. This is for consistency. Accounts \uf0c1 Beancount accumulates commodities in accounts. The names of these accounts do not have to be declared before being used in the file, they are recognized as \u201caccounts\u201d by virtue of their syntax alone 1 . An account name is a colon-separated list of capitalized words which begin with a letter, and whose first word must be one of five account types: Assets Liabilities Equity Income Expenses Each component of the account names begin with a capital letter or a number and are followed by letters, numbers or dash (-) characters. All other characters are disallowed. Here are some realistic example account names: Assets:US:BofA:Checking Liabilities:CA:RBC:CreditCard Equity:Retained-Earnings Income:US:Acme:Salary Expenses:Food:Groceries The set of all names of accounts seen in an input file implicitly define a hierarchy of accounts (sometimes called a chart-of-accounts ), similarly to how files are organized in a file system. For example, the following account names: Assets:US:BofA:Checking Assets:US:BofA:Savings Assets:US:Vanguard:Cash Assets:US:Vanguard:RGAGX Assets:Receivables implicitly declare a tree of accounts that looks like this: `-- Assets |-- Receivables `-- US |-- BofA | |-- Checking | `-- Savings `-- Vanguard |-- Cash `-- RGAGX We would say that \u201c Assets:US:BofA \u201d is the parent account of \u201c Assets:US:BofA:Checking \u201d, and that the latter is a child account of the former. Commodities / Currencies \uf0c1 Accounts contain currencies , which we sometimes also call commodities (we use both terms interchangeably). Like account names, currency names are recognized by their syntax, though, unlike account names, they need not be declared before being used). The syntax for a currency is a word all in capital letters, like these: USD CAD EUR MSFT IBM AIRMILE (Technically, a currency name may be up to 24 characters long, and it must start with a capital letter, must end with with a capital letter or number, and its other characters must only be capital letters, numbers, or punctuation limited to these characters: \u201c'._-\u201d (apostrophe, period, underscore, dash.) The first three might evoke real world currencies to you (US dollars, Canadian dollars, Euros); the next two, stock ticker names (Microsoft and IBM). And the last: rewards points (airmiles). Beancount knows of no such thing; from its perspective all of these instruments are treated similarly. There is no built-in notion of any previously existing currency. These currency names are just names of \u201cthings\u201d that can be put in accounts and accumulated in inventories associated with these accounts. There is something elegant about the fact that there is no \u201cspecial\u201d currency unit, that all commodities are treated equally the same: Beancount is inherently a multi-currency system. You will appreciate this if, like many of us, you are an expat and your life is divided between two or three continents. You can handle an international ledger of accounts without any problems. And your use of currencies can get quite creative: you can create a currency for your home, for example (e.g. MYLOFT ), a currency to count accumulated vacation hours ( VACHR ), or a currency to count potential contributions to your retirement accounts allowed annually ( IRAUSD ). You can actually solve many problems this way. The cookbook describes many such concrete examples. Beancount does not support the dollar sign syntax, e.g., \u201c$120.00\u201d. You should always use names for currencies in your input file. This makes the input more regular and is a design choice. For monetary units, I suggest that you use the standard ISO 4217 currency code as a guideline; these quickly become familiar. However, as detailed above, you may include some other characters in currency names, like underscores (_), dashes (-), periods (.), or apostrophes (\u2018), but no spaces. Finally, you will notice that there exists a \u201c commodity \u201d directive that can be used to declare currencies. It is entirely optional: currencies come into being as you use them. The purpose of the directive is simply to attach metadata to it. Strings \uf0c1 Whenever we need to insert some free text as part of an entry, it should be surrounded by double-quotes. This applies to the payee and narration fields, mainly; basically anything that\u2019s not a date, a number, a currency, an account name. Strings may be split over multiple lines. (Strings with multiple lines will include their newline characters and those need to be handled accordingly when rendering.) Comments \uf0c1 The Beancount input file isn\u2019t intended to contain only your directives: you can be liberal in placing comments and headers in it to organize your file. Any text on a line after the character \u201c;\u201d is ignored, text like this: ; I paid and left the taxi, forgot to take change, it was cold. 2015-01-01 * \"Taxi home from concert in Brooklyn\" Assets:Cash -20 USD ; inline comment Expenses:Taxi You can use one or more \u201c;\u201d characters if you like. Prepend on all lines if you want to enter a larger comment text. If you prefer to have the comment text parsed in and rendered in your journals, see the Note directive elsewhere in this document. Any line that does not begin as a valid Beancount syntax directive (e.g. with a date) is silently ignored. This way you can insert markup to organize your file for various outline modes, such as org-mode in Emacs. For example, you could organize your input file by institution like this and fold & unfold each of the sections independently,: * Banking ** Bank of America 2003-01-05 open Assets:US:BofA:Checking 2003-01-05 open Assets:US:BofA:Savings ;; Transactions follow \u2026 ** TD Bank 2006-03-15 open Assets:US:TD:Cash ;; More transactions follow \u2026 The unmatching lines are simply ignored. Note to visiting Ledger users: In Ledger, \u201c;\u201d is used both for marking comments and for attaching \u201cLedger tags\u201d (Beancount metadata) to postings. This is not the case in Beancount. In Beancount comments are always just comments. Metadata has its own separate syntax. Directives \uf0c1 For a quick reference & overview of directive syntax, please consult the Syntax Cheat Sheet . Open \uf0c1 All accounts need to be declared \u201copen\u201d in order to accept amounts posted to them. You do this by writing a directive that looks like this: 2014-05-01 open Liabilities:CreditCard:CapitalOne USD The general format of the Open directive is: YYYY-MM-DD open Account [ConstraintCurrency,...] [\"BookingMethod\"] The comma-separated optional list of constraint currencies enforces that all changes posted to this account are in units of one of the declared currencies. Specifying a currency constraint is recommended: the more constraints you provide Beancount with, the less likely you will be to make data entry mistakes because if you do, it will warn you about it. Each account should be declared \u201copened\u201d at a particular date that precedes (or is the same as) the date of the first transaction that posts an amount to that account. Just to be clear: an Open directive does not have to appear before transactions in the file , but rather, the date of the Open directive must precede the date of postings to that account. The order of declarations in the file is not important. So for example, this is a legal input file: 2014-05-05 * \"Using my new credit card\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant 2014-05-01 open Liabilities:CreditCard:CapitalOne USD 1990-01-01 open Expenses:Restaurant Another optional declaration for opening accounts is the \u201cbooking method\u201d, which is the algorithm that will be invoked if a reducing lot leads to an ambiguous choice of matching lots from the inventory (0, 2 or more lots match). Currently, the possible values it can take are: STRICT : The lot specification has to match exactly one lot. This is the default method. If this booking method is invoked, it will simply raise an error. This ensures that your input file explicitly selects all matching lots. NONE : No lot matching is performed. Lots of any price will be accepted. A mix of positive and negative numbers of lots for the same currency is allowed. (This is similar to how Ledger treats matching\u2026 it ignores it.) Close \uf0c1 Similarly to the Open directive, there is a Close directive that can be used to tell Beancount that an account has become inactive, for example: ; Closing credit card after fraud was detected. 2016-11-28 close Liabilities:CreditCard:CapitalOne The general format of the Close directive is: YYYY-MM-DD close Account This directive is used in a couple of ways: An error message is raised if you post amounts to that account after its closing date (it's a sanity check). This helps avoid mistakes in data entry. It helps the reporting code figure out which accounts are still active and filter out closed accounts outside of the reporting period. This is especially useful as your ledger accumulates much data over time, as there will be accounts that stop existing and which you just don't want to see in reports for years that follow their closure. Note that a Close directive does not currently generate an implicit zero balance check. You may want to add one just before the closing date to ensure that the account is correctly closed with empty contents. At the moment, once an account is closed, you cannot reopen it after that date. (Though you can, of course, delete or comment-out the directive that closed it.) Finally, there are utility functions in the code that allow you to establish which accounts are open on a particular date. I strongly recommend that you close accounts when they actually close in reality, it will keep your ledger more tidy. Commodity \uf0c1 There is a \u201cCommodity\u201d directive that can be used to declare currencies, financial instruments, commodities (different names for the same thing in Beancount): 1867-07-01 commodity CAD The general format of the Commodity directive is: YYYY-MM-DD commodity Currency This directive is a late arrival, and is entirely optional: you can use commodities without having to really declare them this way. The purpose of this directive is to attach commodity-specific metadata fields on it, so that it can be gathered by plugins later on. For example, you might want to provide a long descriptive name for each commodity, such as \u201cSwiss Franc\u201d for commodity \u201cCHF\u201d, or \u201cHooli Corporation Class C Shares\u201d for \u201cHOOL\u201d, like this: 1867-07-01 commodity CAD name: \"Canadian Dollar\" asset-class: \"cash\" 2012-01-01 commodity HOOL name: \"Hooli Corporation Class C Shares\" asset-class: \"stock\" For example, a plugin could then gather the metadata attribute names and perform some aggregations per asset class. You can use any date for a commodity\u2026 but a relevant date is the date at which it was created or introduced, e.g. Canadian dollars were first introduced in 1867, ILS (new Israeli Shekels) are in use since 1986-01-01. For a company, the date the corporation was formed and shares created could be a good date. Since the main purpose of this directive is to collect per-commodity information, the particular date you choose doesn\u2019t matter all that much. It is an error to declare the same commodity twice. Transactions \uf0c1 Transactions are the most common type of directives that occur in a ledger. They are slightly different to the other ones, because they can be followed by a list of postings. Here is an example: 2014-05-05 txn \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant As for all the other directives, a transaction directive begins with a date in the YYYY-MM-DD format and is followed by the directive name, in this case, \u201c txn \u201d. However, because transactions are the raison d\u2019\u00eatre for our double-entry system and as such are by far the most common type of directive that should appear in a Beancount input file, we make a special case and allow the user to elide the \u201ctxn\u201d keyword and just use a flag instead of it: 2014-05-05 * \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant A flag is used to indicate the status of a transaction, and the particular meaning of the flag is yours to define. We recommend using the following interpretation for them: *: Completed transaction, known amounts, \u201cthis looks correct.\u201d !: Incomplete transaction, needs confirmation or revision, \u201cthis looks incorrect.\u201d In the case of the first example using \u201ctxn\u201d to leave the transaction unflagged, the default flag (\u201c*\u201d) will be set on the transaction object. (I nearly always use the \u201c*\u201d variant and never the keyword one, it is mainly there for consistency with all the other directive formats.) You can also attach flags to the postings themselves, if you want to flag one of the transaction\u2019s legs in particular: 2014-05-05 * \"Transfer from Savings account\" Assets:MyBank:Checking -400.00 USD ! Assets:MyBank:Savings This is useful in the intermediate stage of de-duping transactions (see Getting Started document for more details). The general format of a Transaction directive is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ... The lines that follow the first line are for \u201cPostings.\u201d You can attach as many postings as you want to a transaction. For example, a salary entry might look like this: 2014-03-19 * \"Acme Corp\" \"Bi-monthly salary payment\" Assets:MyBank:Checking 3062.68 USD ; Direct deposit Income:AcmeCorp:Salary -4615.38 USD ; Gross salary Expenses:Taxes:TY2014:Federal 920.53 USD ; Federal taxes Expenses:Taxes:TY2014:SocSec 286.15 USD ; Social security Expenses:Taxes:TY2014:Medicare 66.92 USD ; Medicare Expenses:Taxes:TY2014:StateNY 277.90 USD ; New York taxes Expenses:Taxes:TY2014:SDI 1.20 USD ; Disability insurance The Amount in \u201cPostings\u201d can also be an arithmetic expression using ( ) * / - + . For example, 2014-10-05 * \"Costco\" \"Shopping for birthday\" Liabilities:CreditCard:CapitalOne -45.00 USD Assets:AccountsReceivable:John ((40.00/3) + 5) USD Assets:AccountsReceivable:Michael 40.00/3 USD Expenses:Shopping The crucial and only constraint on postings is that the sum of their balance amounts must be zero. This is explained in full detail below. Metadata \uf0c1 It\u2019s also possible to attach metadata to the transaction and/or any of its postings, so the fully general format is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... ... See the dedicated section on metadata below. Payee & Narration \uf0c1 A transaction may have an optional \u201cpayee\u201d and/or a \u201cnarration.\u201d In the first example above, the payee is \u201cCafe Mogador\u201d and the narration is \u201cLamb tagine with wine\u201d. The payee is a string that represents an external entity that is involved in the transaction. Payees are sometimes useful on transactions that post amounts to Expense accounts, whereby the account accumulates a category of expenses from multiple businesses. A good example is \u201c Expenses:Restaurant \u201d, which will include all postings for the various restaurants one might visit. A narration is a description of the transaction that you write. It can be a comment about the context, the person who accompanied you, some note about the product you bought... whatever you want it to be. It\u2019s for you to insert whatever you like. I like to insert notes when I reconcile, it\u2019s quick and I can refer to my notes later on, for example, to answer the question \u201cWhat was the name of that great little sushi place I visited with Andreas on the West side last winter?\u201d If you place a single string on a transaction line, it becomes its narration: 2014-05-05 * \"Lamb tagine with wine\" \u2026 If you want to set just a payee, put an empty narration string: 2014-05-05 * \"Cafe Mogador\" \"\" \u2026 For legacy reasons, a pipe symbol (\u201c|\u201d) is accepted between those strings (but this will be removed at some point in the future): 2014-05-05 * \"Cafe Mogador\" | \"\" \u2026 You may also leave out either (but you must provide a flag): 2014-05-05 * \u2026 Note for Ledger users. Ledger does not have separate narration and payee fields, it has only one field, which is referred to by the \u201cPayee\u201d metadata tag, and the narration ends up in a saved comment (a \u201cpersistent note\u201d). In Beancount, a Transaction object simply has two fields: payee and narration, where payee just happens to have an empty value a lot of the time. For a deeper discussion of how and when to use payees or not, see Payees, Subaccounts, and Assets . Costs and Prices \uf0c1 Postings represent a single amount being deposited to or withdrawn from an account. The simplest type of posting includes only its amount: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard 400.00 USD If you converted the amount from another currency, you must provide a conversion rate to balance the transaction (see next section). This is done by attaching a \u201cprice\u201d to the posting, which is the rate of conversion: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @ 1.09 CAD Assets:FR:SocGen:Checking 436.01 CAD You could also use the \u201c@@\u201d syntax to specify the total cost: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @@ 436.01 CAD Assets:FR:SocGen:Checking 436.01 CAD Beancount will automatically compute the per-unit price, that is 1.090025 CAD (note that the precision will differ between the last two examples). After the transaction, we are not interested in keeping track of the exchange rate for the units of USD deposited into the account; those units of \u201cUSD\u201d are simply deposited. In a sense, the rate at which they were converted at has been forgotten. However, some commodities that you deposit in accounts must be \u201cheld at cost.\u201d This happens when you want to keep track of the cost basis of those commodities. The typical use case is investing, for example when you deposit shares of a stock in an account. What you want in that circumstance is for the acquisition cost of the commodities you deposit to be attached to the units, such that when you remove those units later on (when you sell), you should be able to identify by cost which of those units to remove, in order to control the effect of taxes (and avoid errors). You can imagine that for each of the units in the account there is an attached cost basis. This will allow us to automatically compute capital gains later. In order to specify that a posting to an account is to be held at a specific cost, include the cost in curly brackets: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 10 IVV {183.07 USD} Assets:ETrade:Cash -1830.70 USD This is the subject of a deeper discussion. Refer to \u201c How Inventories Work \u201d and \u201c Trading with Beancount \u201d documents for an in-depth discussion of these topics. Finally, you can include both a cost and a price on a posting: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains The price will only be used to insert a price entry in the prices database (see Price section below). This is discussed in more details in the Balancing Postings section of this document. Important Note. Amounts specified as either per-share or total prices or costs are always unsigned . It is an error to use a negative sign or a negative cost and Beancount will raise an error if you attempt to do so. Balancing Rule - The \u201cweight\u201d of postings \uf0c1 A crucial aspect of the double-entry method is ensuring that the sum of all the amounts on its postings equals ZERO, in all currencies. This is the central, non-negotiable condition that engenders the accounting equation, and makes it possible to filter any subset of transactions and drawing balance sheets that balance to zero. But with different types of units, the previously introduced price conversions and units \u201cheld at cost\u201d, what does it all mean? It\u2019s simple: we have devised a simple and clear rule for obtaining an amount and a currency from each posting, to be used for balancing them together. We call this the \u201cweight\u201d of a posting, or the balancing amount. Here is a short example of weights derived from postings using the four possible types of cost/price combinations: YYYY-MM-DD Account 10.00 USD -> 10.00 USD Account 10.00 CAD @ 1.01 USD -> 10.10 USD Account 10 SOME {2.02 USD} -> 20.20 USD Account 10 SOME {2.02 USD} @ 2.50 USD -> 20.20 USD Here is the explanation of how it is calculated: If the posting has only an amount and no cost, the balance amount is just the amount and currency on that posting. Using the first example from the previous section, the amount is -400.00 USD, and that is balanced against the 400.00 USD of the second leg. If the posting has only a price , the price is multiplied by the number of units and the price currency is used. In the second example from the preceding section, that is -400.00 USD x 1.09 CAD(/USD) = -436.00 CAD, and that is balanced against the other posting of 436.00 CAD 2 . If the posting has a cost , the cost is multiplied by the number of units and the cost currency is used. In the third example from the preceding section, that is 10 IVV x 183.08 USD(/IVV) = 1830.70 USD. That is balanced against the cash leg of -1830.70 USD, so all is good. Finally, if a posting has both a cost and a price , we simply ignore the price. This optional price is used later on to generate an entry in the in-memory prices database, but it is not used in balancing at all. With this rule, you should be able to easily balance all your transactions. Moreover, this rule makes it possible to let Beancount automatically calculate capital gains for you (see Trading with Beancount for details). Reducing Positions \uf0c1 When you post a reduction to a position in an account, the reduction must always match an existing lot. For example, if an account holds 3200 USD and a transaction posts a -1200 USD change to that account, the 1200 USD match against the existing 3200 USD, and the result is a single position of 2000 USD. This also works for negative values. For example, if an account has a -1300 USD balance and you post a +2000 USD change to it, you obtain a 700 USD balance. A change posted to an account, regardless of the account type, can result in a positive or negative balance; there are no limitations on the balances of simple commodity amounts (that is, those with no cost associated to them). For example, while Assets accounts normally have a positive balance and Liabilities accounts usually a negative one, you can legally credit an Assets account to a negative balance, or debit a Liabilities account to a positive balance. This is because in the real world these things do happen: you might write a check too many and obtain temporary credit from your bank\u2019s checking account (usually along with an outrageous \u201coverdraft\u201d fee), or pay that credit card balance twice by mistake. For commodities held at cost, the cost specification of the posting must match one of the lots held in the inventory before the transaction is applied. The list of lots is gathered, and matched against the specification in the {...} part of the posting. For example, if you provide a cost, only those lots whose cost match that will remain. If you provide a date, only those lots which match that date will remain. And you can use a label as well. If you provide a cost and a date, both of these are matched against the list of candidate lots to reduce. This is essentially a filter on the list of lots. If the filtered list results in a single lot, that lot is chosen to be reduced. If the list results in multiple lots, but the total amount being reduced equals the total amount in the lots, all those lots are reduced by that posting. For example, if in the past you had the following transactions: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 20 IVV {183.07 USD, \"ref-001\"} \u2026 2014-03-22 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 15 IVV {187.12 USD} \u2026 Each of the following reductions would be unambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {183.07 USD} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {2014-02-11} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {\"ref-001\"} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -35 IVV {} \u2026 However, the following would be ambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {} \u2026 If multiple lots match against the reducing posting and their number is not the total number, we are in a situation of ambiguous matches . What happens then, is that the account\u2019s booking method is invoked. There are multiple booking methods, but by default, all accounts are set to use the \u201cSTRICT\u201d booking method. This method simply issues an error in an ambiguous situation. You may set the account\u2019s booking method to \u201cFIFO\u201d to instruct Beancount to select the oldest of the lots. Or \u201cLIFO\u201d for the latest (youngest) of the lots. This will automatically select all the necessary matching lots to fulfill the reduction. PLEASE NOTE! Requiring the dates to match will be dealt with more sensibly in the near future. See A Proposal for an Improvement on Inventory Booking for details of this upcoming change. For such postings, a change that results in a negative number of units is usually impossible. Beancount does not currently allow holding a negative number of a commodity held at cost. For example, an input with just this transaction will fail: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD If it did not, this would result in a balance of -10 units of MSFT. On the other hand, if the account had a balance of 12 units of MSFT held at 43.40 USD on 5/23, the transaction would book just fine, reducing the existing 12 units to 2. Most often, the error that will occur is that the account will be holding a balance of 10 or more units of MSFT at a different cost , and the user will specify an incorrect value for the cost. For instance, if the account had a positive balance of 20 MSFT {42.10 USD}, the transaction above would still fail, because there aren\u2019t 10 or more units of MSFT at 43.40 USD to remove from. This constraint is enforced for a few reasons: Mistakes in data entry for stock units are not uncommon, they have an important negative impact on the correctness of your Ledger\u2014the amounts are usually large\u2014and they will typically trigger an error from this constraint. Therefore, the error check is a useful way to detect these types of errors. Negative numbers of units held at cost are fairly rare. Chances are you don\u2019t need them at all. Exceptions include: short sales of stock, holding spreads on futures contracts, and depending on how you account for them, short positions in currency trading. This is why this check is enabled by default. PLEASE NOTE! In a future version of Beancount, we will relax this constraint somewhat. We will allow an account to hold a negative number of units of a commodity if and only if there are no other units of that commodity held in the account. Either that, or we will allow you to mark an account as having no such constraints at all. The purpose is to allow the account of short positions in commodities. The only blocking factor is this constraint. For more details of the inventory booking algorithm, see the How Inventories Work document. Amount Interpolation \uf0c1 Beancount is able to fill in some of the details of a transaction automatically. You can currently elide the amount of at most one posting within a transaction: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard In the example above, the amount of the credit card posting has been elided. It is automatically calculated by Beancount at 400.00 USD to balance the transaction. This also works with multiple postings, and with postings with costs: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains In this case, the units of IVV are sold at a higher price ($197.90) than they were bought for ($183.07). The cash first posting has a weight of -10 x 183.07 = -1830.70 and the second posting a straightforward $1979.90. The last posting will be ascribed the difference, that is, a balance of -149.20 USD , which is to say, a gain of $149.20. When calculating the amount to be balanced, the same balance amounts that are used to check that the transaction balances to zero are used to fill in the missing amounts. For example, the following would not trigger an error: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash The cash account would receive 1830.70 USD automatically, because the balance amount of the IVV posting is -1830.70 USD (if a posting has both a cost and a price, the cost basis is always used and the optional price ignored). While this is accepted and correct from a balancing perspective, this would be incomplete from an accounting perspective: the capital gain on a sale needs to be accounted for separately and besides, the amount deposited to the cash account if you did as above would fall short of the real deposit (1979.00 USD) and hopefully a subsequent balance assertion in the cash account would indicate this oversight by triggering an error. Finally, this also works when the balance includes multiple commodities: 2014-07-12 * \"Uncle Bob gave me his foreign currency collection!\" Income:Gifts -117.00 ILS Income:Gifts -3000.00 INR Income:Gifts -800.00 JPY Assets:ForeignCash Multiple postings (one for each commodity required to balance) will be inserted to replace the elided one. Tags \uf0c1 Transactions can be tagged with arbitrary strings: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 Expenses:Flights -1230.27 USD Liabilities:CreditCard This is similar to the popular idea of \u201chash tagging\u201d on Twitter and such. These tags essentially allow you to mark a subset of transactions. They can then be used as a filter to generate reports on only this subset of transactions. They have numerous uses. I like to use them to mark all my trips. Multiple tags can be specified as well: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 #germany Expenses:Flights -1230.27 USD Liabilities:CreditCard (If you want to store key-value pairs on directives, see the section on metadata below.) The Tag Stack \uf0c1 Oftentimes multiple transactions related to a single tag will be entered consecutively in a file. As a convenience, the parser can automatically tag transactions within a block of text. How this works is simple: the parser has a \u201cstack\u201d of current tags which it applies to all transactions as it reads them one-by-one. You can push and pop tags onto/from this stack, like this: pushtag #berlin-trip-2014 2014-04-23 * \"Flight to Berlin\" Expenses:Flights -1230.27 USD Liabilities:CreditCard poptag #berlin-trip-2014 This way, you can also push multiple tags onto a long, consecutive set of transactions without having to type them all in. Links \uf0c1 Transactions can also be linked together. You may think of the link as a special kind of tag that can be used to group together a set of financially related transactions over time. For example you may use links to group together transactions that are each related with a specific invoice. This allows to track payments (or write-offs) associated with the invoice: 2014-02-05 * \"Invoice for January\" ^invoice-pepe-studios-jan14 Income:Clients:PepeStudios -8450.00 USD Assets:AccountsReceivable 2014-02-20 * \"Check deposit - payment from Pepe\" ^invoice-pepe-studios-jan14 Assets:BofA:Checking 8450.00 USD Assets:AccountsReceivable Or track multiple transfers related to a single nefarious purpose: 2014-02-05 * \"Moving money to Isle of Man\" ^transfers-offshore-17 Assets:WellsFargo:Savings -40000.00 USD Assets:WellsFargo:Checking 40000.00 USD 2014-02-09 * \"Wire to FX broker\" ^transfers-offshore-17 Assets:WellsFargo:Checking -40025.00 USD Expenses:Fees:WireTransfers 25.00 USD Assets:OANDA:USDollar 40000.00 2014-03-16 * \"Conversion to offshore beans\" ^transfers-offshore-17 Assets:OANDA:USDollar -40000.00 USD Assets:OANDA:GBPounds 23391.81 GBP @ 1.71 USD 2014-03-16 * \"God save the Queen (and taxes)\" ^transfers-offshore-17 Assets:OANDA:GBPounds -23391.81 GBP Expenses:Fees:WireTransfers 15.00 GBP Assets:Brittania:PrivateBanking 23376.81 GBP Linked transactions can be rendered by the web interface in their own dedicated journal, regardless of the current view/filtered set of transactions (the list of links is a global page). Balance Assertions \uf0c1 A balance assertion is a way for you to input your statement balance into the flow of transactions. It tells Beancount to verify that the number of units of a particular commodity in some account should equal some expected value at some point in time. For instance, this 2014-12-26 balance Liabilities:US:CreditCard -3492.02 USD says \u201cCheck that the number of USD units in account \u201c Liabilities:US:CreditCard \u201d on the morning of December 26th, 2014 is -3492.02 USD.\u201d When processing the list of entries, if Beancount encounters a different balance than this for USD it will report an error. If no error is reported, you should have some confidence that the list of transactions that precedes it in this account is highly likely to be correct. This is useful in practice, because in many cases some transactions can get imported separately from the accounts of each of their postings (see the de-duping problem ). This can result in you booking the same transaction twice without noticing, and regularly inserting a balance assertion will catch that problem every time. The general format of the Balance directive is: YYYY-MM-DD balance Account Amount Note that a balance assertion, like all other non-transaction directives, applies at the beginning of its date (i.e., midnight at the start of day). Just imagine that the balance check occurs right after midnight on that day. Balance assertions only make sense on balance sheet accounts (Assets and Liabilities). Because the postings on Income and Expenses accounts are only interesting because of their transient value, i.e., for these accounts we\u2019re interested in sums of changes over a period of time (not the absolute value), it makes little sense to use a balance assertion on income statement accounts. Also, take note that each account benefits from an implicit balance assertion that the account is empty after it is opened at the date of its Open directive. You do not need to explicitly assert a zero balance when opening accounts. Multiple Commodities \uf0c1 A Beancount account may contain more than one commodity (although in practice, you will find that this does not occur often, it is sensible to create dedicated sub-accounts to hold each commodity, for example, holding a portfolio of stocks). A balance assertion applies only to the commodity of the assertion; it leaves the other commodities in the balance unchecked. If you want to check multiple commodities, use multiple balance assertions, like this: ; Check cash balances from wallet 2014-08-09 balance Assets:Cash 562.00 USD 2014-08-09 balance Assets:Cash 210.00 CAD 2014-08-09 balance Assets:Cash 60.00 EUR There is currently no way to exhaustively check the full list of commodities in an account ( a proposal is underway ). Note that in this example if an exhaustive check really matters to you, you could circumvent by defining a subaccount of the cash account to segregate each commodity separately, like this Assets:Cash:USD , Assets:Cash:CAD . Lots Are Aggregated \uf0c1 The balance assertion applies to the sum of units of a particular commody, irrespective of their cost. For example, if you hold three lots of the same commodity in an account, for example, 5 HOOL {500 USD} and 6 HOOL {510 USD}, the following balance check should succeed: 2014-08-09 balance Assets:Investing:HOOL 11 HOOL All the lots are aggregated together and you can verify their number of units. Checks on Parent Accounts \uf0c1 Balance assertions may be performed on parent accounts, and will include the balances of theirs and their sub-accounts: 2014-01-01 open Assets:Investing 2014-01-01 open Assets:Investing:Apple AAPL 2014-01-01 open Assets:Investing:Amazon AMZN 2014-01-01 open Assets:Investing:Microsoft MSFT 2014-01-01 open Equity:Opening-Balances 2014-06-01 * Assets:Investing:Apple 5 AAPL {578.23 USD} Assets:Investing:Amazon 5 AMZN {346.20 USD} Assets:Investing:Microsoft 5 MSFT {42.09 USD} Equity:Opening-Balances 2014-07-13 balance Assets:Investing 5 AAPL 2014-07-13 balance Assets:Investing 5 AMZN 2014-07-13 balance Assets:Investing 5 MSFT Note that this does require that a parent account have been declared as Open, in order to be legitimately used in the balance assertions directive. Before Close \uf0c1 It is useful to insert a balance assertion for 0 units just before closing an account, just to make sure its contents are empty as you close it. The Close directive does not insert that for you automatically (we may eventually build a plug-in for it). Local Tolerance \uf0c1 It's pretty common that sometimes one needs to override the tolerance on the balance check to loosen it on that balance assertion. This can be done using a local tolerance amount off of the balance amount, like this: 2013-09-20 balance Assets:Investing:Funds 319.020 ~ 0.002 RGAGX Pad \uf0c1 A padding directive automatically inserts a transaction that will make the subsequent balance assertion succeed, if it is needed. It inserts the difference needed to fulfill that balance assertion. (What \u201crubber space\u201d is in LaTeX, Pad directives are to balances in Beancount.) Note that by \u201csubsequent,\u201d I mean in date order , not in the order of the declarations in the file. This is the conceptual equivalent of a transaction that will automatically expand or contract to fill the difference between two balance assertions over time. It looks like this: 2014-06-01 pad Assets:BofA:Checking Equity:Opening-Balances The general format of the Pad directive is: YYYY-MM-DD pad Account AccountPad The first account is the account to credit the automatically calculated amount to. This is the account that should have a balance assertion following it (if the account does not have a balance assertion, the pad entry is benign and does nothing). The second account is for the other leg of the transaction, it is the source where the funds will come from, and this is almost always some Equity account. The reason for this is that this directive is generally used for initializing the balances of new accounts, to save us from having to either insert such a directive manually, or from having to enter the full past history of transactions that will bring the account to its current balance. Here is a realistic example usage scenario: ; Account was opened way back in the past. 2002-01-17 open Assets:US:BofA:Checking 2002-01-17 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD This will result in the following transaction being inserted right after the Pad directive, on the same date: 2002-01-17 P \"(Padding inserted for balance of 987.34 USD)\" Assets:US:BofA:Checking 987.34 USD Equity:Opening-Balances -987.34 USD This is a normal transaction\u2014you will see it appear in the rendered journals along with the other ones. (Note the special \u201cP\u201d flag, which can be used by scripts to find these.) Observe that without balance assertions, Pad directives make no sense. Therefore, like balance assertions, they are normally only used on balance sheet accounts (Assets and Liabilities). You could also insert Pad entries between balance assertions, it works too. For example: 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD \u2026 more transactions\u2026 2014-08-08 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-08-09 balance Assets:US:BofA:Checking 1137.23 USD Without intervening transactions, this would insert the following padding transaction: 2014-08-08 P \"(Padding inserted for balance of 1137.23 USD)\" Assets:US:BofA:Checking 149.89 USD Equity:Opening-Balances -149.89 USD In case that\u2019s not obvious, 149.89 USD is the difference between 1137.23 USD and 987.34 USD. If there were more intervening transactions posting amounts to the checking account, the amount would automatically have been adjusted to make the second assertion pass. Unused Pad Directives \uf0c1 You may not currently leave unused Pad directives in your input file. They will trigger an error: 2014-01-01 open Assets:US:BofA:Checking 2014-02-01 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-06-01 * \"Initializing account\" Assets:US:BofA:Checking 212.00 USD Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 212.00 USD (Being this strict is a matter for debate, I suppose, and it could eventually be moved to an optional plugin.) Commodities \uf0c1 Note that the Pad directive does not specify any commodities at all. All commodities with corresponding balance assertions in the account are affected. For instance, the following code would have a padding directive insert a transaction with separate postings for USD and CAD: 2002-01-17 open Assets:Cash 2002-01-17 pad Assets:Cash Equity:Opening-Balances 2014-07-09 balance Assets:Cash 987.34 USD 2014-07-09 balance Assets:Cash 236.24 CAD If the account contained other commodities that aren\u2019t balance asserted, no posting would be inserted for those. Cost Basis \uf0c1 At the moment, Pad directives do not work with accounts holding positions held at cost. The directive is really only useful for cash accounts. (This is mainly because balance assertions do not yet allow specifying a cost basis to assert. It is possible that in the future we decide to support asserting the total cost basis, and that point we could consider supporting padding with cost basis.) Multiple Paddings \uf0c1 You cannot currently insert multiple padding entries for the same account and commodity: 2002-01-17 open Assets:Cash 2002-02-01 pad Assets:Cash Equity:Opening-Balances 2002-03-01 pad Assets:Cash Equity:Opening-Balances 2014-04-19 balance Assets:Cash 987.34 USD (There is a proposal pending to allow this and spread the padding amount evenly among all the intervening Pad directives, but it is as of yet unimplemented.) Notes \uf0c1 A Note directive is simply used to attach a dated comment to the journal of a particular account, like this: 2013-11-03 note Liabilities:CreditCard \"Called about fraudulent card.\" When you render the journal, the note should be rendered in context. This can be useful to record facts and claims associated with a financial event. I often use this to record snippets of information that would otherwise not make their way to a transaction. The general format of the Note directive is: YYYY-MM-DD note Account Description The description string may be split among multiple lines. Documents \uf0c1 A Document directive can be used to attach an external file to the journal of an account: 2013-11-03 document Liabilities:CreditCard \"/home/joe/stmts/apr-2014.pdf\" The filename gets rendered as a browser link in the journals of the web interface for the corresponding account and you should be able to click on it to view the contents of the file itself. This is useful to integrate account statements and other downloads into the flow of accounts, so that they\u2019re easily accessible from a few clicks. Scripts could also be written to obtain this list of documents from an account name and do something with them. The general format of the Document directive is: YYYY-MM-DD document Account PathToDocument Documents from a Directory \uf0c1 A more convenient way to create these entries is to use a special option to specify directories that contain a hierarchy of sub-directories that mirrors that of the chart of accounts. For example, the Document directive shown in the previous section could have been created from a directory hierarchy that looks like this: stmts `-- Liabilities `-- CreditCard `-- 2014-04-27.apr-2014.pdf By simply specifying the root directory as an option (note the absence of a trailing slash): option \"documents\" \"/home/joe/stmts\" The files that will be picked up are those that begin with a date as above, in the YYYY-MM-DD format. You may specify this option multiple times if you have many such document archives. In the past I have used one directory for each year (if you scan all your documents, the directory can grow to a large size, scanned documents tend to be large files). Organizing your electronic document statements and scans using the hierarchy of your ledger\u2019s accounts is a fantastic way to organize them and establish a clear, unambiguous place to find these documents later on, when they\u2019re needed. Prices \uf0c1 Beancount sometimes creates an in-memory data store of prices for each commodity, that is used for various reasons. In particular, it is used to report unrealized gains on account holdings. Price directives can be used to provide data points for this database. A Price directive establishes the rate of exchange between one commodity (the base currency) and another (the quote currency): 2014-07-09 price HOOL 579.18 USD This directive says: \u201cThe price of one unit of HOOL on July 9th, 2014 was 579.18 USD.\u201d Price entries for currency exchange rates work the same way: 2014-07-09 price USD 1.08 CAD The general format of the Price directive is: YYYY-MM-DD price Commodity Price Remember that Beancount knows nothing about what HOOL, USD or CAD are. They are opaque \u201cthings.\u201d You attach meaning to them. So setting a price per hour for your vacation hours is perfectly valid and useful too, if you account for your unused vacations on such terms: 2014-07-09 price VACHR 38.46 USD ; Equiv. $80,000 year Prices from Postings \uf0c1 If you use the beancount.plugins.implicit_prices plugin, every time a Posting appears that has a cost or an optional price declared, it will use that cost or price to automatically synthesize a Price directive. For example, this transaction: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD automatically becomes this after parsing: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD 2014-05-23 price MSFT 43.40 USD This is convenient and if you enable it, will probably be where most of the price points in your ledger\u2019s price database will come from. You can print a table of the parsed prices from a ledger (it is just another type of report). Prices on the Same Day \uf0c1 Notice that there is no notion of time; Beancount is not designed to solve problems for intra-day traders, though of course, it certainly is able to handle multiple trades per day. It just stores its prices per day. (Generally, if you need many features that require a notion of intra-day time, you\u2019re better off using another system, this is not the scope of a bookkeeping system.) When multiple Price directives do exist for the same day, the last one to appear in the file will be selected for inclusion in the Price database. Events \uf0c1 Event directives 3 are used to track the value of some variable of your choice over time. For example, your location: 2014-07-09 event \"location\" \"Paris, France\" The above directive says: \u201cChange the value of the \u2018location\u2019 event to \u2018Paris, France\u2019 as of the 9th of July 2014 and onwards.\u201d A particular event type only has a single value per day. The general format of the Event directive is: YYYY-MM-DD event Name Value The event\u2019s Name string does not have to be declared anywhere, it just begins to exist the first time you use the directive. The Value string can be anything you like; it has no prescribed structure. How to use these is best explained by providing examples: Location : You can use events for tracking the city or country you\u2019re in. This is sensible usage within a ledger because the nature of your expenses are heavily correlated to where you are. It\u2019s convenient: if you use Beancount regularly, it is very little effort to add this bit of information to your file. I usually have a \u201ccash daybook\u201d section where I put miscellaneous cash expenses, and that\u2019s where I enter those directives. Address : If you move around a lot, it\u2019s useful to keep a record of your past home addresses. This is sometimes requested on government forms for immigration. The Green Card application process in the US, for instance, Employer : You can record your date of employment and departure for each job this way. Then you can count the number of days you worked there. Trading window : If you\u2019re an employee of a public company, you can record the dates that you\u2019re allowed to trade its stock. This can then be used to ensure you did not trade that stock when the window is closed. Events are often used to report on numbers of days. For example, in the province of Quebec (Canada) you are insured for free health care coverage if \u201c you spend 183 days of the calendar year or more, excluding trips of 21 days or less .\u201d If you travel abroad a lot, you could easily write a script to warn you of remaining day allocations to avoid losing your coverage. Many expats are in similar situations. In the same vein, the IRS recognizes US immigrants as \u201cresident alien\u201d\u2014and thus subject to filing taxes, yay!\u2014if you pass the Substantial Presence Test , which is defined as \u201cbeing physically present in the US on at least 31 days during the current year, and 193 days during the 3 year period that includes the current year and the 2 years immediately before that, counting: all the days you were present in the current year, and \u2153 of the days of the year before that, and \u2159 of the year preceding that one.\u201d Ouch\u2026 my head hurts. This gets complicated. Armed with your location over time, you could report it automatically. Events will also be usable in the filtering language, to specify non-contiguous periods of time. For example, if you are tracking your location using Event directives, you could produce reports for transactions that occur only when you are in a specific location, e.g., \u201cShow me my expenses on all my trips to Germany,\u201d or \u201cGive me a list of payees for restaurant transactions when I\u2019m in Montreal.\u201d PLEASE NOTE! Filters haven\u2019t been implemented yet. Also, reports on events have not yet been re-implemented in Beancount 2.0. They will be reintroduced again soon, as well as filtering. It is worth noticing that the Price and Event directives are the only ones not associated to an account. Query \uf0c1 It can be convenient to be able to associate SQL queries in a Beancount file to be able to run these as a report automatically. This is still an early development / experimental directive. In any case, you can insert queries in the stream of transactions like this: 2014-07-09 query \"france-balances\" \" SELECT account, sum(position) WHERE \u2018trip-france-2014\u2019 in tags\" The grammar is YYYY-MM-DD query Name SqlContents Each query has a name, which will probably be used to invoke its running as a report type. Also, the date of the query is intended to be the date at which the query is intended to be run for, that is, transactions following it should be ignored. If you\u2019re familiar with the SQL syntax, it\u2019s an implicit CLOSE. Custom \uf0c1 The long-term plan for Beancount is to allow plugins and external clients to define their own directive types, to be declared and validated by the Beancount input language parser. In the meantime, a generic directive is provided for clients to prototype new features, e.g., budgeting. 2014-07-09 custom \"budget\" \"...\" TRUE 45.30 USD The grammar for this directive is flexible: YYYY-MM-DD custom TypeName Value1 ... The first argument is a string and is intended to be unique to your directive. Think of this as the type of your directive. Following it, you can put an arbitrary list of strings, dates, booleans, amounts, and numbers. Note that there is no validation that checks that the number and types of arguments following the TypeName is consistent for a particular type. (See this thread for the origin story around this feature.) Metadata \uf0c1 You may attach arbitrary data to each of your entries and postings. The syntax for it looks like this: 2013-03-14 open Assets:BTrade:HOOLI category: \"taxable\" 2013-08-26 * \"Buying some shares of Hooli\" statement: \"confirmation-826453.pdf\" Assets:BTrade:HOOLI 10 HOOL @ {498.45 USD} decision: \"scheduled\" Assets:BTrade:Cash In this example, a \u201ccategory\u201d attribute is attached to an account\u2019s Open directive, a \u201cstatement\u201d attribute is attached to a Transaction directive (with a string value that represents a filename) and a \u201cdecision\u201d attribute has been attached to the first posting of the transaction (the additional indentation from the posting is not strictly necessary but it helps with readability). Metadata can be attached to any directive type. Keys must begin with a lowercase character from a-z and may contain (uppercase or lowercase) letters, numbers, dashes and underscores. Moreover, the values can be any of the following data types: Strings Accounts Currency Dates (datetime.date) Tags Numbers (Decimal) Amount (beancount.core.amount.Amount) There are two ways in which this data can be used: Query tools that come with Beancount (such as bean-query) will allow you to make use of the metadata values for filtering and aggregation. You can access and use the metadata in custom scripts. The metadata values are accessible as a \u201c .meta \u201d attribute on all directives and is a Python dict. There are no special meanings attached to particular attributes, these are intended for users to define. However, all directives are guaranteed to contain a \u2018 filename \u2019 (string) and a \u2018 lineno \u2019 (integer) attribute which reflect the location they were created from. Finally, attributes without a value will be parsed and have a value of 'None'. If an attribute is repeated multiple times, only the first value for this attribute will be parsed and retained and the following values ignored. Options \uf0c1 The great majority of a Beancount input file consists in directives, as seen in the previous section. However, there are a few global options that can be set in the input file as well, by using a special undated \u201coption\u201d directive: option \"title\" \"Ed\u2019s Personal Ledger\" The general format of the Option directive is: option Name Value where Name and Value are both strings. Note that depending the option, the effect may be to set a single value, or add to a list of existing values. In other words, some options are lists. There are three ways to view the list of options: In this document , which I update regularly. To view the definitive list of options supported by your installed version, use the following command: bean-doctor list-options Finally, you can peek at the source code as well. Operating Currencies \uf0c1 One notable option is \u201c operating_currency \u201d. By default Beancount does not treat any of the commodities any different from each other. In particular, it doesn\u2019t know that there\u2019s anything special about the most common of commodities one uses: their currencies. For example, if you live in New Zealand, you\u2019re going to have an overwhelming number of NZD commodities in your transactions. But useful reports try to reduce all non-currency commodities into one of the main currencies used. Also, it\u2019s useful to break out the currency units into their own dedicated columns. This may also be useful for exporting in order to avoid having to specify the units for that column and import to a spreadsheet with numbers you can process. For this reason, you are able to declare the most common currencies you use in an option: option \"operating_currency\" \"USD\" You may declare more than one. In any case, this option is only ever used by reporting code, it never changes the behavior of Beancount\u2019s processing or semantics. Plugins \uf0c1 In order to load plugin Python modules, use the dedicated \u201cplugin\u201d directive: plugin \"beancount.plugins.module_name\" The name of a plugin should be the name of a Python module in your PYTHONPATH. Those modules will be imported by the Beancount loader and run on the list of parsed entries in order for the plugins to transform the entries or output errors. This allows you to integrate some of your code within Beancount, making arbitrary transformations on the entries. See Scripting & Plugins for details. Plugins also optionally accept some configuration parameters. These can be provided by an optional final string argument, like this: plugin \"beancount.plugins.module_name\" \"configuration data\" The general format of the Option directive is: plugin ModuleName StringConfig The format of the configuration data is plugin-dependent. At the moment, an arbitrary string is passed provided to the plugin. See the plugins\u2019 documentation for specific detail on what it can accept. Also see the \u201cplugin processing mode\u201d option which affects the list of built-in plugins that get run. Includes \uf0c1 Include directives are supported. This allows you to split up large input files into multiple files. The syntax looks like this: include \"path/to/include/file.beancount\" The general format is include Filename The specified path can be an absolute or a relative filename. If the filename is relative, it is relative to the including filename\u2019s directory. This makes it easy to put relative includes in a hierarchy of directories that can be placed under source control and checked out anywhere. Include directives are not processed strictly (as in C, for example). The include directives are accumulated by the Beancount parser and processed separately by the loader. This is possible because the order of declarations of a Beancount input file is not relevant. However, for the moment, options are parsed per-file. The options-map that is kept for post-parse processing is the options-map returned for the top-level file. This is probably subject to review in the future. What\u2019s Next? \uf0c1 This document described all the possible syntax of the Beancount language. If you haven\u2019t written any Beancount input yet, you can head to the Getting Started guide, or browse through a list of practical use cases in the Command-line Accounting Cookbook . Note that there exists an \u201cOpen\u201d directive that is used to provide the start date of each account. That can be located anywhere in the file, it does not have to appear in the file somewhere before you use an account name. You can just start using account names in transactions right away, though all account names that receive postings to them will eventually have to have a corresponding Open directive with a date that precedes all transactions posted to the account in the input file. \u21a9 Note that this is valid whether the price is specified as a per-unit price with the @ syntax or as a total price using the @@ syntax. \u21a9 I really dislike the name \u201cevent\u201d for this directive. I\u2019ve been trying to find a better alternative, so far without success. The name \u201cregister\u201d might be more appropriate, as it resembles that of a processor\u2019s register, but that could be confused with an account register report. \u201cvariable\u201d might work, but somehow that just sounds too computer-sciency and out of context. If you can think of something better, please make a suggestion and I\u2019ll seriously entertain a complete rename (with legacy support for \u201cevent\u201d). \u21a9","title":"Beancount Language Syntax"},{"location":"beancount_language_syntax.html#beancount-language-syntax","text":"Martin Blais , Updated: April 2016 http://furius.ca/beancount/doc/syntax Introduction Syntax Overview Directives Ordering of Directives Accounts Commodities / Currencies Strings Comments Directives Open Close Commodity Transactions Metadata Payee & Narration Costs and Prices Balancing Rule - The \u201cweight\u201d of postings Reducing Positions Amount Interpolation Tags The Tag Stack Links Balance Assertions Multiple Commodities Lots Are Aggregated Checks on Parent Accounts Before Close Pad Unused Pad Directives Commodities Cost Basis Multiple Paddings Notes Documents Documents from a Directory Prices Prices from Postings Prices on the Same Day Events Query Custom Metadata Options Operating Currencies Plugins Includes What\u2019s Next?","title":"Beancount Language Syntax"},{"location":"beancount_language_syntax.html#introduction","text":"This is a user's manual to the language of Beancount, the command-line double-entry bookkeeping system. Beancount defines a computer language that allows you to enter financial transactions in a text file and extract various reports from it. It is a generic counting tool that works with multiple currencies, commodities held at cost (e.g., stocks), and even allows you to track unusual things, like vacation hours, air miles and rewards points, and anything else you might want to count, even beans. This document provides an introduction to Beancount\u2019s syntax and some of the technical details needed for one to understand how it carries out its calculations. This document does not provide an introduction to the double-entry method , a motivation , nor examples and guidelines for entering transactions in your input file, nor how to run the tools . These subjects have their own dedicated documents , and it is recommended that you have had a look at those before diving into this user\u2019s manual. This manual covers the technical details for using Beancount.","title":"Introduction"},{"location":"beancount_language_syntax.html#syntax-overview","text":"","title":"Syntax Overview"},{"location":"beancount_language_syntax.html#directives","text":"Beancount is a declarative language. The input consists of a text file containing mainly a list of directives , or entries (we use these terms interchangeably in the code and documentation); there is also syntax for defining various options . Each directive begins with an associated date , which determines the point in time at which the directive will apply, and its type , which defines which kind of event this directive represents. All the directives begin with a syntax that looks like this: YYYY-MM-DD \u2026 where YYYY is the year, MM is the numerical month, and DD the numerical date. All digits are required, for example, the 7th of May 2007 should be \u201c2007-05-07\u201d, including its zeros. Beancount supports the international ISO 8601 standard format for dates, with dashes (e.g., \u201c2014-02-03\u201d), or the same ordering with slashes (e.g., \u201c2014/02/03\u201d). Here are some example directives, just to give you an idea of the aesthetics: 2014-02-03 open Assets:US:BofA:Checking 2014-04-10 note Assets:US:BofA:Checking \"Called to confirm wire transfer.\" 2014-05-02 balance Assets:US:BofA:Checking 154.20 USD The end product of a parsed input file is a simple list of these entries, in a data structure. All operations in Beancount are performed on these entries. Each particular directive type is documented in a section below.","title":"Directives"},{"location":"beancount_language_syntax.html#ordering-of-directives","text":"The order of declaration of the directives is not important. In fact, the entries are re-sorted chronologically after parsing and before being processed. This is an important feature of the language, because it makes it possible for you to organize your input file any way you like without having to worry about affecting the meaning of the directives. Except for transactions, each directive is assumed to occur at the beginning of each day. For example, you could declare an account being opened on the same day as its first transaction: 2014-02-03 open Assets:US:BofA:Checking 2014-02-03 * \"Initial deposit\" Assets:US:BofA:Checking 100 USD Assets:Cash -100 USD However, if you hypothetically closed that account immediately, you could not declare it closed on the same day, you would have to fudge the date forward by declaring the close on 2/4: 2014-02-04 close Assets:US:BofA:Checking This also explains why balance assertions are verified before any transactions that occur on the same date. This is for consistency.","title":"Ordering of Directives"},{"location":"beancount_language_syntax.html#accounts","text":"Beancount accumulates commodities in accounts. The names of these accounts do not have to be declared before being used in the file, they are recognized as \u201caccounts\u201d by virtue of their syntax alone 1 . An account name is a colon-separated list of capitalized words which begin with a letter, and whose first word must be one of five account types: Assets Liabilities Equity Income Expenses Each component of the account names begin with a capital letter or a number and are followed by letters, numbers or dash (-) characters. All other characters are disallowed. Here are some realistic example account names: Assets:US:BofA:Checking Liabilities:CA:RBC:CreditCard Equity:Retained-Earnings Income:US:Acme:Salary Expenses:Food:Groceries The set of all names of accounts seen in an input file implicitly define a hierarchy of accounts (sometimes called a chart-of-accounts ), similarly to how files are organized in a file system. For example, the following account names: Assets:US:BofA:Checking Assets:US:BofA:Savings Assets:US:Vanguard:Cash Assets:US:Vanguard:RGAGX Assets:Receivables implicitly declare a tree of accounts that looks like this: `-- Assets |-- Receivables `-- US |-- BofA | |-- Checking | `-- Savings `-- Vanguard |-- Cash `-- RGAGX We would say that \u201c Assets:US:BofA \u201d is the parent account of \u201c Assets:US:BofA:Checking \u201d, and that the latter is a child account of the former.","title":"Accounts"},{"location":"beancount_language_syntax.html#commodities-currencies","text":"Accounts contain currencies , which we sometimes also call commodities (we use both terms interchangeably). Like account names, currency names are recognized by their syntax, though, unlike account names, they need not be declared before being used). The syntax for a currency is a word all in capital letters, like these: USD CAD EUR MSFT IBM AIRMILE (Technically, a currency name may be up to 24 characters long, and it must start with a capital letter, must end with with a capital letter or number, and its other characters must only be capital letters, numbers, or punctuation limited to these characters: \u201c'._-\u201d (apostrophe, period, underscore, dash.) The first three might evoke real world currencies to you (US dollars, Canadian dollars, Euros); the next two, stock ticker names (Microsoft and IBM). And the last: rewards points (airmiles). Beancount knows of no such thing; from its perspective all of these instruments are treated similarly. There is no built-in notion of any previously existing currency. These currency names are just names of \u201cthings\u201d that can be put in accounts and accumulated in inventories associated with these accounts. There is something elegant about the fact that there is no \u201cspecial\u201d currency unit, that all commodities are treated equally the same: Beancount is inherently a multi-currency system. You will appreciate this if, like many of us, you are an expat and your life is divided between two or three continents. You can handle an international ledger of accounts without any problems. And your use of currencies can get quite creative: you can create a currency for your home, for example (e.g. MYLOFT ), a currency to count accumulated vacation hours ( VACHR ), or a currency to count potential contributions to your retirement accounts allowed annually ( IRAUSD ). You can actually solve many problems this way. The cookbook describes many such concrete examples. Beancount does not support the dollar sign syntax, e.g., \u201c$120.00\u201d. You should always use names for currencies in your input file. This makes the input more regular and is a design choice. For monetary units, I suggest that you use the standard ISO 4217 currency code as a guideline; these quickly become familiar. However, as detailed above, you may include some other characters in currency names, like underscores (_), dashes (-), periods (.), or apostrophes (\u2018), but no spaces. Finally, you will notice that there exists a \u201c commodity \u201d directive that can be used to declare currencies. It is entirely optional: currencies come into being as you use them. The purpose of the directive is simply to attach metadata to it.","title":"Commodities / Currencies"},{"location":"beancount_language_syntax.html#strings","text":"Whenever we need to insert some free text as part of an entry, it should be surrounded by double-quotes. This applies to the payee and narration fields, mainly; basically anything that\u2019s not a date, a number, a currency, an account name. Strings may be split over multiple lines. (Strings with multiple lines will include their newline characters and those need to be handled accordingly when rendering.)","title":"Strings"},{"location":"beancount_language_syntax.html#comments","text":"The Beancount input file isn\u2019t intended to contain only your directives: you can be liberal in placing comments and headers in it to organize your file. Any text on a line after the character \u201c;\u201d is ignored, text like this: ; I paid and left the taxi, forgot to take change, it was cold. 2015-01-01 * \"Taxi home from concert in Brooklyn\" Assets:Cash -20 USD ; inline comment Expenses:Taxi You can use one or more \u201c;\u201d characters if you like. Prepend on all lines if you want to enter a larger comment text. If you prefer to have the comment text parsed in and rendered in your journals, see the Note directive elsewhere in this document. Any line that does not begin as a valid Beancount syntax directive (e.g. with a date) is silently ignored. This way you can insert markup to organize your file for various outline modes, such as org-mode in Emacs. For example, you could organize your input file by institution like this and fold & unfold each of the sections independently,: * Banking ** Bank of America 2003-01-05 open Assets:US:BofA:Checking 2003-01-05 open Assets:US:BofA:Savings ;; Transactions follow \u2026 ** TD Bank 2006-03-15 open Assets:US:TD:Cash ;; More transactions follow \u2026 The unmatching lines are simply ignored. Note to visiting Ledger users: In Ledger, \u201c;\u201d is used both for marking comments and for attaching \u201cLedger tags\u201d (Beancount metadata) to postings. This is not the case in Beancount. In Beancount comments are always just comments. Metadata has its own separate syntax.","title":"Comments"},{"location":"beancount_language_syntax.html#directives_1","text":"For a quick reference & overview of directive syntax, please consult the Syntax Cheat Sheet .","title":"Directives"},{"location":"beancount_language_syntax.html#open","text":"All accounts need to be declared \u201copen\u201d in order to accept amounts posted to them. You do this by writing a directive that looks like this: 2014-05-01 open Liabilities:CreditCard:CapitalOne USD The general format of the Open directive is: YYYY-MM-DD open Account [ConstraintCurrency,...] [\"BookingMethod\"] The comma-separated optional list of constraint currencies enforces that all changes posted to this account are in units of one of the declared currencies. Specifying a currency constraint is recommended: the more constraints you provide Beancount with, the less likely you will be to make data entry mistakes because if you do, it will warn you about it. Each account should be declared \u201copened\u201d at a particular date that precedes (or is the same as) the date of the first transaction that posts an amount to that account. Just to be clear: an Open directive does not have to appear before transactions in the file , but rather, the date of the Open directive must precede the date of postings to that account. The order of declarations in the file is not important. So for example, this is a legal input file: 2014-05-05 * \"Using my new credit card\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant 2014-05-01 open Liabilities:CreditCard:CapitalOne USD 1990-01-01 open Expenses:Restaurant Another optional declaration for opening accounts is the \u201cbooking method\u201d, which is the algorithm that will be invoked if a reducing lot leads to an ambiguous choice of matching lots from the inventory (0, 2 or more lots match). Currently, the possible values it can take are: STRICT : The lot specification has to match exactly one lot. This is the default method. If this booking method is invoked, it will simply raise an error. This ensures that your input file explicitly selects all matching lots. NONE : No lot matching is performed. Lots of any price will be accepted. A mix of positive and negative numbers of lots for the same currency is allowed. (This is similar to how Ledger treats matching\u2026 it ignores it.)","title":"Open"},{"location":"beancount_language_syntax.html#close","text":"Similarly to the Open directive, there is a Close directive that can be used to tell Beancount that an account has become inactive, for example: ; Closing credit card after fraud was detected. 2016-11-28 close Liabilities:CreditCard:CapitalOne The general format of the Close directive is: YYYY-MM-DD close Account This directive is used in a couple of ways: An error message is raised if you post amounts to that account after its closing date (it's a sanity check). This helps avoid mistakes in data entry. It helps the reporting code figure out which accounts are still active and filter out closed accounts outside of the reporting period. This is especially useful as your ledger accumulates much data over time, as there will be accounts that stop existing and which you just don't want to see in reports for years that follow their closure. Note that a Close directive does not currently generate an implicit zero balance check. You may want to add one just before the closing date to ensure that the account is correctly closed with empty contents. At the moment, once an account is closed, you cannot reopen it after that date. (Though you can, of course, delete or comment-out the directive that closed it.) Finally, there are utility functions in the code that allow you to establish which accounts are open on a particular date. I strongly recommend that you close accounts when they actually close in reality, it will keep your ledger more tidy.","title":"Close"},{"location":"beancount_language_syntax.html#commodity","text":"There is a \u201cCommodity\u201d directive that can be used to declare currencies, financial instruments, commodities (different names for the same thing in Beancount): 1867-07-01 commodity CAD The general format of the Commodity directive is: YYYY-MM-DD commodity Currency This directive is a late arrival, and is entirely optional: you can use commodities without having to really declare them this way. The purpose of this directive is to attach commodity-specific metadata fields on it, so that it can be gathered by plugins later on. For example, you might want to provide a long descriptive name for each commodity, such as \u201cSwiss Franc\u201d for commodity \u201cCHF\u201d, or \u201cHooli Corporation Class C Shares\u201d for \u201cHOOL\u201d, like this: 1867-07-01 commodity CAD name: \"Canadian Dollar\" asset-class: \"cash\" 2012-01-01 commodity HOOL name: \"Hooli Corporation Class C Shares\" asset-class: \"stock\" For example, a plugin could then gather the metadata attribute names and perform some aggregations per asset class. You can use any date for a commodity\u2026 but a relevant date is the date at which it was created or introduced, e.g. Canadian dollars were first introduced in 1867, ILS (new Israeli Shekels) are in use since 1986-01-01. For a company, the date the corporation was formed and shares created could be a good date. Since the main purpose of this directive is to collect per-commodity information, the particular date you choose doesn\u2019t matter all that much. It is an error to declare the same commodity twice.","title":"Commodity"},{"location":"beancount_language_syntax.html#transactions","text":"Transactions are the most common type of directives that occur in a ledger. They are slightly different to the other ones, because they can be followed by a list of postings. Here is an example: 2014-05-05 txn \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant As for all the other directives, a transaction directive begins with a date in the YYYY-MM-DD format and is followed by the directive name, in this case, \u201c txn \u201d. However, because transactions are the raison d\u2019\u00eatre for our double-entry system and as such are by far the most common type of directive that should appear in a Beancount input file, we make a special case and allow the user to elide the \u201ctxn\u201d keyword and just use a flag instead of it: 2014-05-05 * \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant A flag is used to indicate the status of a transaction, and the particular meaning of the flag is yours to define. We recommend using the following interpretation for them: *: Completed transaction, known amounts, \u201cthis looks correct.\u201d !: Incomplete transaction, needs confirmation or revision, \u201cthis looks incorrect.\u201d In the case of the first example using \u201ctxn\u201d to leave the transaction unflagged, the default flag (\u201c*\u201d) will be set on the transaction object. (I nearly always use the \u201c*\u201d variant and never the keyword one, it is mainly there for consistency with all the other directive formats.) You can also attach flags to the postings themselves, if you want to flag one of the transaction\u2019s legs in particular: 2014-05-05 * \"Transfer from Savings account\" Assets:MyBank:Checking -400.00 USD ! Assets:MyBank:Savings This is useful in the intermediate stage of de-duping transactions (see Getting Started document for more details). The general format of a Transaction directive is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ... The lines that follow the first line are for \u201cPostings.\u201d You can attach as many postings as you want to a transaction. For example, a salary entry might look like this: 2014-03-19 * \"Acme Corp\" \"Bi-monthly salary payment\" Assets:MyBank:Checking 3062.68 USD ; Direct deposit Income:AcmeCorp:Salary -4615.38 USD ; Gross salary Expenses:Taxes:TY2014:Federal 920.53 USD ; Federal taxes Expenses:Taxes:TY2014:SocSec 286.15 USD ; Social security Expenses:Taxes:TY2014:Medicare 66.92 USD ; Medicare Expenses:Taxes:TY2014:StateNY 277.90 USD ; New York taxes Expenses:Taxes:TY2014:SDI 1.20 USD ; Disability insurance The Amount in \u201cPostings\u201d can also be an arithmetic expression using ( ) * / - + . For example, 2014-10-05 * \"Costco\" \"Shopping for birthday\" Liabilities:CreditCard:CapitalOne -45.00 USD Assets:AccountsReceivable:John ((40.00/3) + 5) USD Assets:AccountsReceivable:Michael 40.00/3 USD Expenses:Shopping The crucial and only constraint on postings is that the sum of their balance amounts must be zero. This is explained in full detail below.","title":"Transactions"},{"location":"beancount_language_syntax.html#metadata","text":"It\u2019s also possible to attach metadata to the transaction and/or any of its postings, so the fully general format is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... ... See the dedicated section on metadata below.","title":"Metadata"},{"location":"beancount_language_syntax.html#payee-narration","text":"A transaction may have an optional \u201cpayee\u201d and/or a \u201cnarration.\u201d In the first example above, the payee is \u201cCafe Mogador\u201d and the narration is \u201cLamb tagine with wine\u201d. The payee is a string that represents an external entity that is involved in the transaction. Payees are sometimes useful on transactions that post amounts to Expense accounts, whereby the account accumulates a category of expenses from multiple businesses. A good example is \u201c Expenses:Restaurant \u201d, which will include all postings for the various restaurants one might visit. A narration is a description of the transaction that you write. It can be a comment about the context, the person who accompanied you, some note about the product you bought... whatever you want it to be. It\u2019s for you to insert whatever you like. I like to insert notes when I reconcile, it\u2019s quick and I can refer to my notes later on, for example, to answer the question \u201cWhat was the name of that great little sushi place I visited with Andreas on the West side last winter?\u201d If you place a single string on a transaction line, it becomes its narration: 2014-05-05 * \"Lamb tagine with wine\" \u2026 If you want to set just a payee, put an empty narration string: 2014-05-05 * \"Cafe Mogador\" \"\" \u2026 For legacy reasons, a pipe symbol (\u201c|\u201d) is accepted between those strings (but this will be removed at some point in the future): 2014-05-05 * \"Cafe Mogador\" | \"\" \u2026 You may also leave out either (but you must provide a flag): 2014-05-05 * \u2026 Note for Ledger users. Ledger does not have separate narration and payee fields, it has only one field, which is referred to by the \u201cPayee\u201d metadata tag, and the narration ends up in a saved comment (a \u201cpersistent note\u201d). In Beancount, a Transaction object simply has two fields: payee and narration, where payee just happens to have an empty value a lot of the time. For a deeper discussion of how and when to use payees or not, see Payees, Subaccounts, and Assets .","title":"Payee & Narration"},{"location":"beancount_language_syntax.html#costs-and-prices","text":"Postings represent a single amount being deposited to or withdrawn from an account. The simplest type of posting includes only its amount: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard 400.00 USD If you converted the amount from another currency, you must provide a conversion rate to balance the transaction (see next section). This is done by attaching a \u201cprice\u201d to the posting, which is the rate of conversion: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @ 1.09 CAD Assets:FR:SocGen:Checking 436.01 CAD You could also use the \u201c@@\u201d syntax to specify the total cost: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @@ 436.01 CAD Assets:FR:SocGen:Checking 436.01 CAD Beancount will automatically compute the per-unit price, that is 1.090025 CAD (note that the precision will differ between the last two examples). After the transaction, we are not interested in keeping track of the exchange rate for the units of USD deposited into the account; those units of \u201cUSD\u201d are simply deposited. In a sense, the rate at which they were converted at has been forgotten. However, some commodities that you deposit in accounts must be \u201cheld at cost.\u201d This happens when you want to keep track of the cost basis of those commodities. The typical use case is investing, for example when you deposit shares of a stock in an account. What you want in that circumstance is for the acquisition cost of the commodities you deposit to be attached to the units, such that when you remove those units later on (when you sell), you should be able to identify by cost which of those units to remove, in order to control the effect of taxes (and avoid errors). You can imagine that for each of the units in the account there is an attached cost basis. This will allow us to automatically compute capital gains later. In order to specify that a posting to an account is to be held at a specific cost, include the cost in curly brackets: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 10 IVV {183.07 USD} Assets:ETrade:Cash -1830.70 USD This is the subject of a deeper discussion. Refer to \u201c How Inventories Work \u201d and \u201c Trading with Beancount \u201d documents for an in-depth discussion of these topics. Finally, you can include both a cost and a price on a posting: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains The price will only be used to insert a price entry in the prices database (see Price section below). This is discussed in more details in the Balancing Postings section of this document. Important Note. Amounts specified as either per-share or total prices or costs are always unsigned . It is an error to use a negative sign or a negative cost and Beancount will raise an error if you attempt to do so.","title":"Costs and Prices"},{"location":"beancount_language_syntax.html#balancing-rule-the-weight-of-postings","text":"A crucial aspect of the double-entry method is ensuring that the sum of all the amounts on its postings equals ZERO, in all currencies. This is the central, non-negotiable condition that engenders the accounting equation, and makes it possible to filter any subset of transactions and drawing balance sheets that balance to zero. But with different types of units, the previously introduced price conversions and units \u201cheld at cost\u201d, what does it all mean? It\u2019s simple: we have devised a simple and clear rule for obtaining an amount and a currency from each posting, to be used for balancing them together. We call this the \u201cweight\u201d of a posting, or the balancing amount. Here is a short example of weights derived from postings using the four possible types of cost/price combinations: YYYY-MM-DD Account 10.00 USD -> 10.00 USD Account 10.00 CAD @ 1.01 USD -> 10.10 USD Account 10 SOME {2.02 USD} -> 20.20 USD Account 10 SOME {2.02 USD} @ 2.50 USD -> 20.20 USD Here is the explanation of how it is calculated: If the posting has only an amount and no cost, the balance amount is just the amount and currency on that posting. Using the first example from the previous section, the amount is -400.00 USD, and that is balanced against the 400.00 USD of the second leg. If the posting has only a price , the price is multiplied by the number of units and the price currency is used. In the second example from the preceding section, that is -400.00 USD x 1.09 CAD(/USD) = -436.00 CAD, and that is balanced against the other posting of 436.00 CAD 2 . If the posting has a cost , the cost is multiplied by the number of units and the cost currency is used. In the third example from the preceding section, that is 10 IVV x 183.08 USD(/IVV) = 1830.70 USD. That is balanced against the cash leg of -1830.70 USD, so all is good. Finally, if a posting has both a cost and a price , we simply ignore the price. This optional price is used later on to generate an entry in the in-memory prices database, but it is not used in balancing at all. With this rule, you should be able to easily balance all your transactions. Moreover, this rule makes it possible to let Beancount automatically calculate capital gains for you (see Trading with Beancount for details).","title":"Balancing Rule - The \u201cweight\u201d of postings"},{"location":"beancount_language_syntax.html#reducing-positions","text":"When you post a reduction to a position in an account, the reduction must always match an existing lot. For example, if an account holds 3200 USD and a transaction posts a -1200 USD change to that account, the 1200 USD match against the existing 3200 USD, and the result is a single position of 2000 USD. This also works for negative values. For example, if an account has a -1300 USD balance and you post a +2000 USD change to it, you obtain a 700 USD balance. A change posted to an account, regardless of the account type, can result in a positive or negative balance; there are no limitations on the balances of simple commodity amounts (that is, those with no cost associated to them). For example, while Assets accounts normally have a positive balance and Liabilities accounts usually a negative one, you can legally credit an Assets account to a negative balance, or debit a Liabilities account to a positive balance. This is because in the real world these things do happen: you might write a check too many and obtain temporary credit from your bank\u2019s checking account (usually along with an outrageous \u201coverdraft\u201d fee), or pay that credit card balance twice by mistake. For commodities held at cost, the cost specification of the posting must match one of the lots held in the inventory before the transaction is applied. The list of lots is gathered, and matched against the specification in the {...} part of the posting. For example, if you provide a cost, only those lots whose cost match that will remain. If you provide a date, only those lots which match that date will remain. And you can use a label as well. If you provide a cost and a date, both of these are matched against the list of candidate lots to reduce. This is essentially a filter on the list of lots. If the filtered list results in a single lot, that lot is chosen to be reduced. If the list results in multiple lots, but the total amount being reduced equals the total amount in the lots, all those lots are reduced by that posting. For example, if in the past you had the following transactions: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 20 IVV {183.07 USD, \"ref-001\"} \u2026 2014-03-22 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 15 IVV {187.12 USD} \u2026 Each of the following reductions would be unambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {183.07 USD} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {2014-02-11} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {\"ref-001\"} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -35 IVV {} \u2026 However, the following would be ambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {} \u2026 If multiple lots match against the reducing posting and their number is not the total number, we are in a situation of ambiguous matches . What happens then, is that the account\u2019s booking method is invoked. There are multiple booking methods, but by default, all accounts are set to use the \u201cSTRICT\u201d booking method. This method simply issues an error in an ambiguous situation. You may set the account\u2019s booking method to \u201cFIFO\u201d to instruct Beancount to select the oldest of the lots. Or \u201cLIFO\u201d for the latest (youngest) of the lots. This will automatically select all the necessary matching lots to fulfill the reduction. PLEASE NOTE! Requiring the dates to match will be dealt with more sensibly in the near future. See A Proposal for an Improvement on Inventory Booking for details of this upcoming change. For such postings, a change that results in a negative number of units is usually impossible. Beancount does not currently allow holding a negative number of a commodity held at cost. For example, an input with just this transaction will fail: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD If it did not, this would result in a balance of -10 units of MSFT. On the other hand, if the account had a balance of 12 units of MSFT held at 43.40 USD on 5/23, the transaction would book just fine, reducing the existing 12 units to 2. Most often, the error that will occur is that the account will be holding a balance of 10 or more units of MSFT at a different cost , and the user will specify an incorrect value for the cost. For instance, if the account had a positive balance of 20 MSFT {42.10 USD}, the transaction above would still fail, because there aren\u2019t 10 or more units of MSFT at 43.40 USD to remove from. This constraint is enforced for a few reasons: Mistakes in data entry for stock units are not uncommon, they have an important negative impact on the correctness of your Ledger\u2014the amounts are usually large\u2014and they will typically trigger an error from this constraint. Therefore, the error check is a useful way to detect these types of errors. Negative numbers of units held at cost are fairly rare. Chances are you don\u2019t need them at all. Exceptions include: short sales of stock, holding spreads on futures contracts, and depending on how you account for them, short positions in currency trading. This is why this check is enabled by default. PLEASE NOTE! In a future version of Beancount, we will relax this constraint somewhat. We will allow an account to hold a negative number of units of a commodity if and only if there are no other units of that commodity held in the account. Either that, or we will allow you to mark an account as having no such constraints at all. The purpose is to allow the account of short positions in commodities. The only blocking factor is this constraint. For more details of the inventory booking algorithm, see the How Inventories Work document.","title":"Reducing Positions"},{"location":"beancount_language_syntax.html#amount-interpolation","text":"Beancount is able to fill in some of the details of a transaction automatically. You can currently elide the amount of at most one posting within a transaction: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard In the example above, the amount of the credit card posting has been elided. It is automatically calculated by Beancount at 400.00 USD to balance the transaction. This also works with multiple postings, and with postings with costs: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains In this case, the units of IVV are sold at a higher price ($197.90) than they were bought for ($183.07). The cash first posting has a weight of -10 x 183.07 = -1830.70 and the second posting a straightforward $1979.90. The last posting will be ascribed the difference, that is, a balance of -149.20 USD , which is to say, a gain of $149.20. When calculating the amount to be balanced, the same balance amounts that are used to check that the transaction balances to zero are used to fill in the missing amounts. For example, the following would not trigger an error: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash The cash account would receive 1830.70 USD automatically, because the balance amount of the IVV posting is -1830.70 USD (if a posting has both a cost and a price, the cost basis is always used and the optional price ignored). While this is accepted and correct from a balancing perspective, this would be incomplete from an accounting perspective: the capital gain on a sale needs to be accounted for separately and besides, the amount deposited to the cash account if you did as above would fall short of the real deposit (1979.00 USD) and hopefully a subsequent balance assertion in the cash account would indicate this oversight by triggering an error. Finally, this also works when the balance includes multiple commodities: 2014-07-12 * \"Uncle Bob gave me his foreign currency collection!\" Income:Gifts -117.00 ILS Income:Gifts -3000.00 INR Income:Gifts -800.00 JPY Assets:ForeignCash Multiple postings (one for each commodity required to balance) will be inserted to replace the elided one.","title":"Amount Interpolation"},{"location":"beancount_language_syntax.html#tags","text":"Transactions can be tagged with arbitrary strings: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 Expenses:Flights -1230.27 USD Liabilities:CreditCard This is similar to the popular idea of \u201chash tagging\u201d on Twitter and such. These tags essentially allow you to mark a subset of transactions. They can then be used as a filter to generate reports on only this subset of transactions. They have numerous uses. I like to use them to mark all my trips. Multiple tags can be specified as well: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 #germany Expenses:Flights -1230.27 USD Liabilities:CreditCard (If you want to store key-value pairs on directives, see the section on metadata below.)","title":"Tags"},{"location":"beancount_language_syntax.html#the-tag-stack","text":"Oftentimes multiple transactions related to a single tag will be entered consecutively in a file. As a convenience, the parser can automatically tag transactions within a block of text. How this works is simple: the parser has a \u201cstack\u201d of current tags which it applies to all transactions as it reads them one-by-one. You can push and pop tags onto/from this stack, like this: pushtag #berlin-trip-2014 2014-04-23 * \"Flight to Berlin\" Expenses:Flights -1230.27 USD Liabilities:CreditCard poptag #berlin-trip-2014 This way, you can also push multiple tags onto a long, consecutive set of transactions without having to type them all in.","title":"The Tag Stack"},{"location":"beancount_language_syntax.html#links","text":"Transactions can also be linked together. You may think of the link as a special kind of tag that can be used to group together a set of financially related transactions over time. For example you may use links to group together transactions that are each related with a specific invoice. This allows to track payments (or write-offs) associated with the invoice: 2014-02-05 * \"Invoice for January\" ^invoice-pepe-studios-jan14 Income:Clients:PepeStudios -8450.00 USD Assets:AccountsReceivable 2014-02-20 * \"Check deposit - payment from Pepe\" ^invoice-pepe-studios-jan14 Assets:BofA:Checking 8450.00 USD Assets:AccountsReceivable Or track multiple transfers related to a single nefarious purpose: 2014-02-05 * \"Moving money to Isle of Man\" ^transfers-offshore-17 Assets:WellsFargo:Savings -40000.00 USD Assets:WellsFargo:Checking 40000.00 USD 2014-02-09 * \"Wire to FX broker\" ^transfers-offshore-17 Assets:WellsFargo:Checking -40025.00 USD Expenses:Fees:WireTransfers 25.00 USD Assets:OANDA:USDollar 40000.00 2014-03-16 * \"Conversion to offshore beans\" ^transfers-offshore-17 Assets:OANDA:USDollar -40000.00 USD Assets:OANDA:GBPounds 23391.81 GBP @ 1.71 USD 2014-03-16 * \"God save the Queen (and taxes)\" ^transfers-offshore-17 Assets:OANDA:GBPounds -23391.81 GBP Expenses:Fees:WireTransfers 15.00 GBP Assets:Brittania:PrivateBanking 23376.81 GBP Linked transactions can be rendered by the web interface in their own dedicated journal, regardless of the current view/filtered set of transactions (the list of links is a global page).","title":"Links"},{"location":"beancount_language_syntax.html#balance-assertions","text":"A balance assertion is a way for you to input your statement balance into the flow of transactions. It tells Beancount to verify that the number of units of a particular commodity in some account should equal some expected value at some point in time. For instance, this 2014-12-26 balance Liabilities:US:CreditCard -3492.02 USD says \u201cCheck that the number of USD units in account \u201c Liabilities:US:CreditCard \u201d on the morning of December 26th, 2014 is -3492.02 USD.\u201d When processing the list of entries, if Beancount encounters a different balance than this for USD it will report an error. If no error is reported, you should have some confidence that the list of transactions that precedes it in this account is highly likely to be correct. This is useful in practice, because in many cases some transactions can get imported separately from the accounts of each of their postings (see the de-duping problem ). This can result in you booking the same transaction twice without noticing, and regularly inserting a balance assertion will catch that problem every time. The general format of the Balance directive is: YYYY-MM-DD balance Account Amount Note that a balance assertion, like all other non-transaction directives, applies at the beginning of its date (i.e., midnight at the start of day). Just imagine that the balance check occurs right after midnight on that day. Balance assertions only make sense on balance sheet accounts (Assets and Liabilities). Because the postings on Income and Expenses accounts are only interesting because of their transient value, i.e., for these accounts we\u2019re interested in sums of changes over a period of time (not the absolute value), it makes little sense to use a balance assertion on income statement accounts. Also, take note that each account benefits from an implicit balance assertion that the account is empty after it is opened at the date of its Open directive. You do not need to explicitly assert a zero balance when opening accounts.","title":"Balance Assertions"},{"location":"beancount_language_syntax.html#multiple-commodities","text":"A Beancount account may contain more than one commodity (although in practice, you will find that this does not occur often, it is sensible to create dedicated sub-accounts to hold each commodity, for example, holding a portfolio of stocks). A balance assertion applies only to the commodity of the assertion; it leaves the other commodities in the balance unchecked. If you want to check multiple commodities, use multiple balance assertions, like this: ; Check cash balances from wallet 2014-08-09 balance Assets:Cash 562.00 USD 2014-08-09 balance Assets:Cash 210.00 CAD 2014-08-09 balance Assets:Cash 60.00 EUR There is currently no way to exhaustively check the full list of commodities in an account ( a proposal is underway ). Note that in this example if an exhaustive check really matters to you, you could circumvent by defining a subaccount of the cash account to segregate each commodity separately, like this Assets:Cash:USD , Assets:Cash:CAD .","title":"Multiple Commodities"},{"location":"beancount_language_syntax.html#lots-are-aggregated","text":"The balance assertion applies to the sum of units of a particular commody, irrespective of their cost. For example, if you hold three lots of the same commodity in an account, for example, 5 HOOL {500 USD} and 6 HOOL {510 USD}, the following balance check should succeed: 2014-08-09 balance Assets:Investing:HOOL 11 HOOL All the lots are aggregated together and you can verify their number of units.","title":"Lots Are Aggregated"},{"location":"beancount_language_syntax.html#checks-on-parent-accounts","text":"Balance assertions may be performed on parent accounts, and will include the balances of theirs and their sub-accounts: 2014-01-01 open Assets:Investing 2014-01-01 open Assets:Investing:Apple AAPL 2014-01-01 open Assets:Investing:Amazon AMZN 2014-01-01 open Assets:Investing:Microsoft MSFT 2014-01-01 open Equity:Opening-Balances 2014-06-01 * Assets:Investing:Apple 5 AAPL {578.23 USD} Assets:Investing:Amazon 5 AMZN {346.20 USD} Assets:Investing:Microsoft 5 MSFT {42.09 USD} Equity:Opening-Balances 2014-07-13 balance Assets:Investing 5 AAPL 2014-07-13 balance Assets:Investing 5 AMZN 2014-07-13 balance Assets:Investing 5 MSFT Note that this does require that a parent account have been declared as Open, in order to be legitimately used in the balance assertions directive.","title":"Checks on Parent Accounts"},{"location":"beancount_language_syntax.html#before-close","text":"It is useful to insert a balance assertion for 0 units just before closing an account, just to make sure its contents are empty as you close it. The Close directive does not insert that for you automatically (we may eventually build a plug-in for it).","title":"Before Close"},{"location":"beancount_language_syntax.html#local-tolerance","text":"It's pretty common that sometimes one needs to override the tolerance on the balance check to loosen it on that balance assertion. This can be done using a local tolerance amount off of the balance amount, like this: 2013-09-20 balance Assets:Investing:Funds 319.020 ~ 0.002 RGAGX","title":"Local Tolerance"},{"location":"beancount_language_syntax.html#pad","text":"A padding directive automatically inserts a transaction that will make the subsequent balance assertion succeed, if it is needed. It inserts the difference needed to fulfill that balance assertion. (What \u201crubber space\u201d is in LaTeX, Pad directives are to balances in Beancount.) Note that by \u201csubsequent,\u201d I mean in date order , not in the order of the declarations in the file. This is the conceptual equivalent of a transaction that will automatically expand or contract to fill the difference between two balance assertions over time. It looks like this: 2014-06-01 pad Assets:BofA:Checking Equity:Opening-Balances The general format of the Pad directive is: YYYY-MM-DD pad Account AccountPad The first account is the account to credit the automatically calculated amount to. This is the account that should have a balance assertion following it (if the account does not have a balance assertion, the pad entry is benign and does nothing). The second account is for the other leg of the transaction, it is the source where the funds will come from, and this is almost always some Equity account. The reason for this is that this directive is generally used for initializing the balances of new accounts, to save us from having to either insert such a directive manually, or from having to enter the full past history of transactions that will bring the account to its current balance. Here is a realistic example usage scenario: ; Account was opened way back in the past. 2002-01-17 open Assets:US:BofA:Checking 2002-01-17 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD This will result in the following transaction being inserted right after the Pad directive, on the same date: 2002-01-17 P \"(Padding inserted for balance of 987.34 USD)\" Assets:US:BofA:Checking 987.34 USD Equity:Opening-Balances -987.34 USD This is a normal transaction\u2014you will see it appear in the rendered journals along with the other ones. (Note the special \u201cP\u201d flag, which can be used by scripts to find these.) Observe that without balance assertions, Pad directives make no sense. Therefore, like balance assertions, they are normally only used on balance sheet accounts (Assets and Liabilities). You could also insert Pad entries between balance assertions, it works too. For example: 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD \u2026 more transactions\u2026 2014-08-08 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-08-09 balance Assets:US:BofA:Checking 1137.23 USD Without intervening transactions, this would insert the following padding transaction: 2014-08-08 P \"(Padding inserted for balance of 1137.23 USD)\" Assets:US:BofA:Checking 149.89 USD Equity:Opening-Balances -149.89 USD In case that\u2019s not obvious, 149.89 USD is the difference between 1137.23 USD and 987.34 USD. If there were more intervening transactions posting amounts to the checking account, the amount would automatically have been adjusted to make the second assertion pass.","title":"Pad"},{"location":"beancount_language_syntax.html#unused-pad-directives","text":"You may not currently leave unused Pad directives in your input file. They will trigger an error: 2014-01-01 open Assets:US:BofA:Checking 2014-02-01 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-06-01 * \"Initializing account\" Assets:US:BofA:Checking 212.00 USD Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 212.00 USD (Being this strict is a matter for debate, I suppose, and it could eventually be moved to an optional plugin.)","title":"Unused Pad Directives"},{"location":"beancount_language_syntax.html#commodities","text":"Note that the Pad directive does not specify any commodities at all. All commodities with corresponding balance assertions in the account are affected. For instance, the following code would have a padding directive insert a transaction with separate postings for USD and CAD: 2002-01-17 open Assets:Cash 2002-01-17 pad Assets:Cash Equity:Opening-Balances 2014-07-09 balance Assets:Cash 987.34 USD 2014-07-09 balance Assets:Cash 236.24 CAD If the account contained other commodities that aren\u2019t balance asserted, no posting would be inserted for those.","title":"Commodities"},{"location":"beancount_language_syntax.html#cost-basis","text":"At the moment, Pad directives do not work with accounts holding positions held at cost. The directive is really only useful for cash accounts. (This is mainly because balance assertions do not yet allow specifying a cost basis to assert. It is possible that in the future we decide to support asserting the total cost basis, and that point we could consider supporting padding with cost basis.)","title":"Cost Basis"},{"location":"beancount_language_syntax.html#multiple-paddings","text":"You cannot currently insert multiple padding entries for the same account and commodity: 2002-01-17 open Assets:Cash 2002-02-01 pad Assets:Cash Equity:Opening-Balances 2002-03-01 pad Assets:Cash Equity:Opening-Balances 2014-04-19 balance Assets:Cash 987.34 USD (There is a proposal pending to allow this and spread the padding amount evenly among all the intervening Pad directives, but it is as of yet unimplemented.)","title":"Multiple Paddings"},{"location":"beancount_language_syntax.html#notes","text":"A Note directive is simply used to attach a dated comment to the journal of a particular account, like this: 2013-11-03 note Liabilities:CreditCard \"Called about fraudulent card.\" When you render the journal, the note should be rendered in context. This can be useful to record facts and claims associated with a financial event. I often use this to record snippets of information that would otherwise not make their way to a transaction. The general format of the Note directive is: YYYY-MM-DD note Account Description The description string may be split among multiple lines.","title":"Notes"},{"location":"beancount_language_syntax.html#documents","text":"A Document directive can be used to attach an external file to the journal of an account: 2013-11-03 document Liabilities:CreditCard \"/home/joe/stmts/apr-2014.pdf\" The filename gets rendered as a browser link in the journals of the web interface for the corresponding account and you should be able to click on it to view the contents of the file itself. This is useful to integrate account statements and other downloads into the flow of accounts, so that they\u2019re easily accessible from a few clicks. Scripts could also be written to obtain this list of documents from an account name and do something with them. The general format of the Document directive is: YYYY-MM-DD document Account PathToDocument","title":"Documents"},{"location":"beancount_language_syntax.html#documents-from-a-directory","text":"A more convenient way to create these entries is to use a special option to specify directories that contain a hierarchy of sub-directories that mirrors that of the chart of accounts. For example, the Document directive shown in the previous section could have been created from a directory hierarchy that looks like this: stmts `-- Liabilities `-- CreditCard `-- 2014-04-27.apr-2014.pdf By simply specifying the root directory as an option (note the absence of a trailing slash): option \"documents\" \"/home/joe/stmts\" The files that will be picked up are those that begin with a date as above, in the YYYY-MM-DD format. You may specify this option multiple times if you have many such document archives. In the past I have used one directory for each year (if you scan all your documents, the directory can grow to a large size, scanned documents tend to be large files). Organizing your electronic document statements and scans using the hierarchy of your ledger\u2019s accounts is a fantastic way to organize them and establish a clear, unambiguous place to find these documents later on, when they\u2019re needed.","title":"Documents from a Directory"},{"location":"beancount_language_syntax.html#prices","text":"Beancount sometimes creates an in-memory data store of prices for each commodity, that is used for various reasons. In particular, it is used to report unrealized gains on account holdings. Price directives can be used to provide data points for this database. A Price directive establishes the rate of exchange between one commodity (the base currency) and another (the quote currency): 2014-07-09 price HOOL 579.18 USD This directive says: \u201cThe price of one unit of HOOL on July 9th, 2014 was 579.18 USD.\u201d Price entries for currency exchange rates work the same way: 2014-07-09 price USD 1.08 CAD The general format of the Price directive is: YYYY-MM-DD price Commodity Price Remember that Beancount knows nothing about what HOOL, USD or CAD are. They are opaque \u201cthings.\u201d You attach meaning to them. So setting a price per hour for your vacation hours is perfectly valid and useful too, if you account for your unused vacations on such terms: 2014-07-09 price VACHR 38.46 USD ; Equiv. $80,000 year","title":"Prices"},{"location":"beancount_language_syntax.html#prices-from-postings","text":"If you use the beancount.plugins.implicit_prices plugin, every time a Posting appears that has a cost or an optional price declared, it will use that cost or price to automatically synthesize a Price directive. For example, this transaction: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD automatically becomes this after parsing: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD 2014-05-23 price MSFT 43.40 USD This is convenient and if you enable it, will probably be where most of the price points in your ledger\u2019s price database will come from. You can print a table of the parsed prices from a ledger (it is just another type of report).","title":"Prices from Postings"},{"location":"beancount_language_syntax.html#prices-on-the-same-day","text":"Notice that there is no notion of time; Beancount is not designed to solve problems for intra-day traders, though of course, it certainly is able to handle multiple trades per day. It just stores its prices per day. (Generally, if you need many features that require a notion of intra-day time, you\u2019re better off using another system, this is not the scope of a bookkeeping system.) When multiple Price directives do exist for the same day, the last one to appear in the file will be selected for inclusion in the Price database.","title":"Prices on the Same Day"},{"location":"beancount_language_syntax.html#events","text":"Event directives 3 are used to track the value of some variable of your choice over time. For example, your location: 2014-07-09 event \"location\" \"Paris, France\" The above directive says: \u201cChange the value of the \u2018location\u2019 event to \u2018Paris, France\u2019 as of the 9th of July 2014 and onwards.\u201d A particular event type only has a single value per day. The general format of the Event directive is: YYYY-MM-DD event Name Value The event\u2019s Name string does not have to be declared anywhere, it just begins to exist the first time you use the directive. The Value string can be anything you like; it has no prescribed structure. How to use these is best explained by providing examples: Location : You can use events for tracking the city or country you\u2019re in. This is sensible usage within a ledger because the nature of your expenses are heavily correlated to where you are. It\u2019s convenient: if you use Beancount regularly, it is very little effort to add this bit of information to your file. I usually have a \u201ccash daybook\u201d section where I put miscellaneous cash expenses, and that\u2019s where I enter those directives. Address : If you move around a lot, it\u2019s useful to keep a record of your past home addresses. This is sometimes requested on government forms for immigration. The Green Card application process in the US, for instance, Employer : You can record your date of employment and departure for each job this way. Then you can count the number of days you worked there. Trading window : If you\u2019re an employee of a public company, you can record the dates that you\u2019re allowed to trade its stock. This can then be used to ensure you did not trade that stock when the window is closed. Events are often used to report on numbers of days. For example, in the province of Quebec (Canada) you are insured for free health care coverage if \u201c you spend 183 days of the calendar year or more, excluding trips of 21 days or less .\u201d If you travel abroad a lot, you could easily write a script to warn you of remaining day allocations to avoid losing your coverage. Many expats are in similar situations. In the same vein, the IRS recognizes US immigrants as \u201cresident alien\u201d\u2014and thus subject to filing taxes, yay!\u2014if you pass the Substantial Presence Test , which is defined as \u201cbeing physically present in the US on at least 31 days during the current year, and 193 days during the 3 year period that includes the current year and the 2 years immediately before that, counting: all the days you were present in the current year, and \u2153 of the days of the year before that, and \u2159 of the year preceding that one.\u201d Ouch\u2026 my head hurts. This gets complicated. Armed with your location over time, you could report it automatically. Events will also be usable in the filtering language, to specify non-contiguous periods of time. For example, if you are tracking your location using Event directives, you could produce reports for transactions that occur only when you are in a specific location, e.g., \u201cShow me my expenses on all my trips to Germany,\u201d or \u201cGive me a list of payees for restaurant transactions when I\u2019m in Montreal.\u201d PLEASE NOTE! Filters haven\u2019t been implemented yet. Also, reports on events have not yet been re-implemented in Beancount 2.0. They will be reintroduced again soon, as well as filtering. It is worth noticing that the Price and Event directives are the only ones not associated to an account.","title":"Events"},{"location":"beancount_language_syntax.html#query","text":"It can be convenient to be able to associate SQL queries in a Beancount file to be able to run these as a report automatically. This is still an early development / experimental directive. In any case, you can insert queries in the stream of transactions like this: 2014-07-09 query \"france-balances\" \" SELECT account, sum(position) WHERE \u2018trip-france-2014\u2019 in tags\" The grammar is YYYY-MM-DD query Name SqlContents Each query has a name, which will probably be used to invoke its running as a report type. Also, the date of the query is intended to be the date at which the query is intended to be run for, that is, transactions following it should be ignored. If you\u2019re familiar with the SQL syntax, it\u2019s an implicit CLOSE.","title":"Query"},{"location":"beancount_language_syntax.html#custom","text":"The long-term plan for Beancount is to allow plugins and external clients to define their own directive types, to be declared and validated by the Beancount input language parser. In the meantime, a generic directive is provided for clients to prototype new features, e.g., budgeting. 2014-07-09 custom \"budget\" \"...\" TRUE 45.30 USD The grammar for this directive is flexible: YYYY-MM-DD custom TypeName Value1 ... The first argument is a string and is intended to be unique to your directive. Think of this as the type of your directive. Following it, you can put an arbitrary list of strings, dates, booleans, amounts, and numbers. Note that there is no validation that checks that the number and types of arguments following the TypeName is consistent for a particular type. (See this thread for the origin story around this feature.)","title":"Custom"},{"location":"beancount_language_syntax.html#metadata_1","text":"You may attach arbitrary data to each of your entries and postings. The syntax for it looks like this: 2013-03-14 open Assets:BTrade:HOOLI category: \"taxable\" 2013-08-26 * \"Buying some shares of Hooli\" statement: \"confirmation-826453.pdf\" Assets:BTrade:HOOLI 10 HOOL @ {498.45 USD} decision: \"scheduled\" Assets:BTrade:Cash In this example, a \u201ccategory\u201d attribute is attached to an account\u2019s Open directive, a \u201cstatement\u201d attribute is attached to a Transaction directive (with a string value that represents a filename) and a \u201cdecision\u201d attribute has been attached to the first posting of the transaction (the additional indentation from the posting is not strictly necessary but it helps with readability). Metadata can be attached to any directive type. Keys must begin with a lowercase character from a-z and may contain (uppercase or lowercase) letters, numbers, dashes and underscores. Moreover, the values can be any of the following data types: Strings Accounts Currency Dates (datetime.date) Tags Numbers (Decimal) Amount (beancount.core.amount.Amount) There are two ways in which this data can be used: Query tools that come with Beancount (such as bean-query) will allow you to make use of the metadata values for filtering and aggregation. You can access and use the metadata in custom scripts. The metadata values are accessible as a \u201c .meta \u201d attribute on all directives and is a Python dict. There are no special meanings attached to particular attributes, these are intended for users to define. However, all directives are guaranteed to contain a \u2018 filename \u2019 (string) and a \u2018 lineno \u2019 (integer) attribute which reflect the location they were created from. Finally, attributes without a value will be parsed and have a value of 'None'. If an attribute is repeated multiple times, only the first value for this attribute will be parsed and retained and the following values ignored.","title":"Metadata"},{"location":"beancount_language_syntax.html#options","text":"The great majority of a Beancount input file consists in directives, as seen in the previous section. However, there are a few global options that can be set in the input file as well, by using a special undated \u201coption\u201d directive: option \"title\" \"Ed\u2019s Personal Ledger\" The general format of the Option directive is: option Name Value where Name and Value are both strings. Note that depending the option, the effect may be to set a single value, or add to a list of existing values. In other words, some options are lists. There are three ways to view the list of options: In this document , which I update regularly. To view the definitive list of options supported by your installed version, use the following command: bean-doctor list-options Finally, you can peek at the source code as well.","title":"Options"},{"location":"beancount_language_syntax.html#operating-currencies","text":"One notable option is \u201c operating_currency \u201d. By default Beancount does not treat any of the commodities any different from each other. In particular, it doesn\u2019t know that there\u2019s anything special about the most common of commodities one uses: their currencies. For example, if you live in New Zealand, you\u2019re going to have an overwhelming number of NZD commodities in your transactions. But useful reports try to reduce all non-currency commodities into one of the main currencies used. Also, it\u2019s useful to break out the currency units into their own dedicated columns. This may also be useful for exporting in order to avoid having to specify the units for that column and import to a spreadsheet with numbers you can process. For this reason, you are able to declare the most common currencies you use in an option: option \"operating_currency\" \"USD\" You may declare more than one. In any case, this option is only ever used by reporting code, it never changes the behavior of Beancount\u2019s processing or semantics.","title":"Operating Currencies"},{"location":"beancount_language_syntax.html#plugins","text":"In order to load plugin Python modules, use the dedicated \u201cplugin\u201d directive: plugin \"beancount.plugins.module_name\" The name of a plugin should be the name of a Python module in your PYTHONPATH. Those modules will be imported by the Beancount loader and run on the list of parsed entries in order for the plugins to transform the entries or output errors. This allows you to integrate some of your code within Beancount, making arbitrary transformations on the entries. See Scripting & Plugins for details. Plugins also optionally accept some configuration parameters. These can be provided by an optional final string argument, like this: plugin \"beancount.plugins.module_name\" \"configuration data\" The general format of the Option directive is: plugin ModuleName StringConfig The format of the configuration data is plugin-dependent. At the moment, an arbitrary string is passed provided to the plugin. See the plugins\u2019 documentation for specific detail on what it can accept. Also see the \u201cplugin processing mode\u201d option which affects the list of built-in plugins that get run.","title":"Plugins"},{"location":"beancount_language_syntax.html#includes","text":"Include directives are supported. This allows you to split up large input files into multiple files. The syntax looks like this: include \"path/to/include/file.beancount\" The general format is include Filename The specified path can be an absolute or a relative filename. If the filename is relative, it is relative to the including filename\u2019s directory. This makes it easy to put relative includes in a hierarchy of directories that can be placed under source control and checked out anywhere. Include directives are not processed strictly (as in C, for example). The include directives are accumulated by the Beancount parser and processed separately by the loader. This is possible because the order of declarations of a Beancount input file is not relevant. However, for the moment, options are parsed per-file. The options-map that is kept for post-parse processing is the options-map returned for the top-level file. This is probably subject to review in the future.","title":"Includes"},{"location":"beancount_language_syntax.html#whats-next","text":"This document described all the possible syntax of the Beancount language. If you haven\u2019t written any Beancount input yet, you can head to the Getting Started guide, or browse through a list of practical use cases in the Command-line Accounting Cookbook . Note that there exists an \u201cOpen\u201d directive that is used to provide the start date of each account. That can be located anywhere in the file, it does not have to appear in the file somewhere before you use an account name. You can just start using account names in transactions right away, though all account names that receive postings to them will eventually have to have a corresponding Open directive with a date that precedes all transactions posted to the account in the input file. \u21a9 Note that this is valid whether the price is specified as a per-unit price with the @ syntax or as a total price using the @@ syntax. \u21a9 I really dislike the name \u201cevent\u201d for this directive. I\u2019ve been trying to find a better alternative, so far without success. The name \u201cregister\u201d might be more appropriate, as it resembles that of a processor\u2019s register, but that could be confused with an account register report. \u201cvariable\u201d might work, but somehow that just sounds too computer-sciency and out of context. If you can think of something better, please make a suggestion and I\u2019ll seriously entertain a complete rename (with legacy support for \u201cevent\u201d). \u21a9","title":"What\u2019s Next?"},{"location":"beancount_options_reference.html","text":"option \"title\" \"Joe Smith's Personal Ledger\" The title of this ledger / input file. This shows up at the top of every page. option \"name_assets\" \"Assets\" option \"name_liabilities\" \"Liabilities\" option \"name_equity\" \"Equity\" option \"name_income\" \"Income\" option \"name_expenses\" \"Expenses\" Root names of every account. This can be used to customize your category names, so that if you prefer \"Revenue\" over \"Income\" or \"Capital\" over \"Equity\", you can set them here. The account names in your input files must match, and the parser will validate these. You should place these options at the beginning of your file, because they affect how the parser recognizes account names. option \"account_previous_balances\" \"Opening-Balances\" Leaf name of the equity account used for summarizing previous transactions into opening balances. option \"account_previous_earnings\" \"Earnings:Previous\" Leaf name of the equity account used for transferring previous retained earnings from income and expenses accrued before the beginning of the exercise into the balance sheet. option \"account_previous_conversions\" \"Conversions:Previous\" Leaf name of the equity account used for inserting conversions that will zero out remaining amounts due to transfers before the opening date. This will essentially \"fixup\" the basic accounting equation due to the errors that priced conversions introduce. option \"account_current_earnings\" \"Earnings:Current\" Leaf name of the equity account used for transferring current retained earnings from income and expenses accrued during the current exercise into the balance sheet. This is most often called \"Net Income\". option \"account_current_conversions\" \"Conversions:Current\" Leaf name of the equity account used for inserting conversions that will zero out remaining amounts due to transfers during the exercise period. option \"account_rounding\" \"Rounding\" The name of an account to be used to post to and accumulate rounding error. This is unset and this feature is disabled by default; setting this value to an account name will automatically enable the addition of postings on all transactions that have a residual amount. option \"conversion_currency\" \"NOTHING\" The imaginary currency used to convert all units for conversions at a degenerate rate of zero. This can be any currency name that isn't used in the rest of the ledger. Choose something unique that makes sense in your language. option \"inferred_tolerance_default\" \"CHF:0.01\" option \"default_tolerance\" \"CHF:0.01\" THIS OPTION IS DEPRECATED: This option has been renamed to 'inferred_tolerance_default' Mappings of currency to the tolerance used when it cannot be inferred automatically. The tolerance at hand is the one used for verifying (1) that transactions balance, (2) explicit balance checks from 'balance' directives balance, and (3) in the precision used for padding (from the 'pad' directive). The values must be strings in the following format: : for example, 'USD:0.005'. By default, the tolerance used for currencies without an inferred value is zero (which means infinite precision). As a special case, this value, that is, the fallabck value used for all currencies without an explicit default can be overridden using the '*' currency, like this: '*:0.5'. Used by itself, this last example sets the fallabck tolerance as '0.5' for all currencies. (Note: The new value of this option is \"inferred_tolerance_default\"; it renames the option which used to be called \"default_tolerance\". The latter name was confusing.) For detailed documentation about how precision is handled, see this doc: http://furius.ca/beancount/doc/tolerances (This option may be supplied multiple times.) option \"inferred_tolerance_multiplier\" \"1.1\" A multiplier for inferred tolerance values. When the tolerance values aren't specified explicitly via the 'inferred_tolerance_default' option, the tolerance is inferred from the numbers in the input file. For example, if a transaction has posting with a value like '32.424 CAD', the tolerance for CAD will be inferred to be 0.001 times some multiplier. This is the muliplier value. We normally assume that the institution we're reproducing this posting from applies rounding, and so the default value for the multiplier is 0.5, that is, half of the smallest digit encountered. You can customize this multiplier by changing this option, typically expanding it to account for amounts slightly beyond the usual tolerance, for example, if you deal with institutions with bad of unexpected rounding behaviour. For detailed documentation about how precision is handled, see this doc: http://furius.ca/beancount/doc/tolerances option \"infer_tolerance_from_cost\" \"True\" Enable a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 * M = 0.045 USD (where M is the inferred_tolerance_multiplier) and this is added to the mix to enlarge the tolerance allowed for units of USD on that transaction. All the normally inferred tolerances (see http://furius.ca/beancount/doc/tolerances) are still taken into account. Enabling this flag only makes the tolerances potentially wider. option \"tolerance\" \"0.015\" THIS OPTION IS DEPRECATED: The 'tolerance' option has been deprecated and has no effect. The tolerance allowed for balance checks and padding directives. In the real world, rounding occurs in various places, and we need to allow a small (but very small) amount of tolerance in checking the balance of transactions and in requiring padding entries to be auto- inserted. This is the tolerance amount, which you can override. option \"use_legacy_fixed_tolerances\" \"True\" Restore the legacy fixed handling of tolerances. Balance and Pad directives have a fixed tolerance of 0.015 units, and Transactions balance at 0.005 units. For any units. This is intended as a way for people to revert the behavior of Beancount to ease the transition to the new inferred tolerance logic. See http://furius.ca/beancount/doc/tolerances for more details. option \"documents\" \"/path/to/your/documents/archive\" A list of directory roots, relative to the CWD, which should be searched for document files. For the document files to be automatically found they must have the following filename format: YYYY-MM-DD.(.*) (This option may be supplied multiple times.) option \"operating_currency\" \"USD\" A list of currencies that we single out during reporting and create dedicated columns for. This is used to indicate the main currencies that you work with in real life. (Refrain from listing all the possible currencies here, this is not what it is made for; just list the very principal currencies you use daily only.) Because our system is agnostic to any unit definition that occurs in the input file, we use this to display these values in table cells without their associated unit strings. This allows you to import the numbers in a spreadsheet (e.g, \"101.00 USD\" does not get parsed by a spreadsheet import, but \"101.00\" does). If you need to enter a list of operating currencies, you may input this option multiple times, that is, you repeat the entire directive once for each desired operating currency. (This option may be supplied multiple times.) option \"render_commas\" \"TRUE\" A boolean, true if the number formatting routines should output commas as thousand separators in numbers. option \"plugin_processing_mode\" \"raw\" A string that defines which set of plugins is to be run by the loader: if the mode is \"default\", a preset list of plugins are automatically run before any user plugin. If the mode is \"raw\", no preset plugins are run at all, only user plugins are run (the user should explicitly load the desired list of plugins by using the 'plugin' option. This is useful in case the user wants full control over the ordering in which the plugins are run). option \"plugin\" \"beancount.plugins.module_name\" THIS OPTION IS DEPRECATED: The 'plugin' option is deprecated; it should be replaced by the 'plugin' directive A list of Python modules containing transformation functions to run the entries through after parsing. The parser reads the entries as they are, transforms them through a list of standard functions, such as balance checks and inserting padding entries, and then hands the entries over to those plugins to add more auto-generated goodies. The list is a list of pairs/tuples, in the format (plugin-name, plugin-configuration). The plugin-name should be the name of a Python module to import, and within the module we expect a special '__plugins__' attribute that should list the name of transform functions to run the entries through. The plugin-configuration argument is an optional string to be provided by the user. Each function accepts a pair of (entries, options_map) and should return a pair of (new entries, error instances). If a plugin configuration is provided, it is provided as an extra argument to the plugin function. Errors should not be printed out the output, they will be converted to strings by the loader and displayed as dictated by the output medium. (This option may be supplied multiple times.) option \"long_string_maxlines\" \"64\" The number of lines beyond which a multi-line string will trigger a overly long line warning. This warning is meant to help detect a dangling quote by warning users of unexpectedly long strings. option \"experiment_explicit_tolerances\" \"True\" Enable an EXPERIMENTAL feature that supports an explicit tolerance value on Balance assertions. If enabled, the balance amount supports a tolerance in the input, with this syntax: ~ , for example, \"532.23 ~ 0.001 USD\". See the document on tolerances for more details: http://furius.ca/beancount/doc/tolerances WARNING: This feature may go away at any time. It is an exploration to see if it is truly useful. We may be able to do without. option \"booking_method\" \"SIMPLE\" The booking method to apply, for interpolation and for matching lot specifications to the available lots in an inventory at the moment of the transaction. Values may be 'SIMPLE' for the original method used in Beancount, or 'FULL' for the newer method that does fuzzy matching against the inventory and allows multiple amounts to be interpolated (see http://furius.ca/beancount/doc/proposal-booking for details).","title":"Beancount Options Reference"},{"location":"beancount_query_language.html","text":"Beancount Query Language \uf0c1 Martin Blais, January 2015 http://furius.ca/beancount/doc/query Introduction \uf0c1 The purpose of Beancount is to allow the user to create an accurate and error-free representation of financial transactions, typically those occurring in a user or in an institution\u2019s associated set of accounts, to then extract various reports from this list of transactions. Beancount provides a few tools to extract reports from the corpus of transactions: custom reports (using the Beancount bean-report tool), a web interface (using the bean-web tool) and the ability for the user to write their own scripts to output anything they want. The repository of financial transactions is always read from the text file input, but once parsed and loaded in memory, extracting information from Beancount could be carried out much like you would another database, that is, instead of using custom code to generate output from the data structures, a query language could be compiled and run over the relatively regular list of transactions. In practice, you could flatten out the list of postings to an external SQL database and make queries using that database\u2019s own tools, but the results of this approach are quite disappointing, mainly due to the lack of operations on inventories which is the basis of balancing rules in Beancount. By providing a slightly specialized query engine that takes advantage of the structure of the double-entry transactions we can easily generate custom reports specific to accounting purposes. This document describes our specialized SQL-like query client. It assumes you have at least a passing knowledge of SQL syntax. If not, you may want to first read something about it. Motivation \uf0c1 So one might ask: Why create another SQL client? Why not output the data to an SQLite database and allow the user to use that SQL client? Well, we have done that (see the bean-sql script which converts your Beancount ledger into an SQLite database) and the results are not great. Writing queries is painful and carrying out operations on lots that are held at cost is difficult. By taking advantage of a few aspects of our in-memory data structures, we can do better. So Beancount comes with its own SQL-like query client called \u201c bean-query \u201d. The clients implements the following \u201cextras\u201d that are essential to Beancount: It allows to easily filter at two levels simultaneously: You can filter whole transactions, which has the benefit of respecting the accounting equation, and then, usually for presentation purposes, you can also filter at the postings level. The client supports the semantics of inventory booking implemented in Beancount. It also supports aggregation functions on inventory objects and rendering functions (e.g., COST() to render the cost of an inventory instead of its contents). The client allows you to flatten multiple lots into separate postings to produce lists of holdings each with their associated cost basis. Transactions can be summarized in a manner useful to produce balance sheets and income statements. For example, our SQL variant explicitly supports a \u201cclose\u201d operation with an effect similar to closing the year, which inserts transactions to clear income statement accounts to equity and removes past history. See this post as well for a similar answer. Warning & Caveat \uf0c1 Approximately 70% of the features desired in the original design doc were implemented in the query language in late 2014. More work will be needed to cover the full feature set, but the current iteration supports most of the use cases covered by Ledger, and I suspect by Beancount users. More feedback is desired on the current version before moving on, and I would like to move forward improving some of the more fundamental aspects of Beancount (namely, inventory booking) before spending more time on the query language. It is functional as it is, but a second revision will be made later on, informed by user feedback and prolonged use. Therefore, a first release of the query language has been merged in the default stable branch. This document presents this first iteration on the Beancount query language. Making Queries \uf0c1 The custom query client that we provide is called bean-query . Run it on your ledger file, like this: $ bean-query myfile.beancount Input file: \"My Ledger\u2019s Title\" Ready with 13996 directives (21112 postings in 8833 transactions). beancount> _ This launches the query tool in interactive mode, where you can enter multiple commands on the dataset loaded in memory. bean-query parses the input file, spits out a few basic statistics about your ledger, and provides a command prompt for you to enter query commands. You can type \u201c help \u201d here to view the list of available commands. If any errors in your ledger are incurred, they are printed before the prompt. To suppress error printing, run the tool with the \u201cno-errors\u201d option: $ bean-query -q myfile.beancount Batch Mode Queries \uf0c1 If you\u2019d like to run queries directly from the command-line, without an interactive prompt, you can provide the query directly following your filename: $ bean-query myfile.beancount 'balances from year = 2014' account balance ---------------------------------------------------------------------- \u2026 \u2026 All the interactive commands are supported. \uf0c1 Shell Variables \uf0c1 The interactive shell has a few \u201c set \u201d variables that you can customize to change some of the behavior of the shell. These are like environment variables. Type the \u201c set \u201d command to see the list of available variables and their current value. The variables are: format (string): The output format. Currently, only \u201ctext\u201d is supported. boxed (boolean): Whether we should draw a box around the output table. spaced (boolean): Whether to insert an empty line between every result row. This is only relevant because postings with multiple lots may require multiple lines to be rendered, and inserting an empty line helps delineate those as separate. pager (string): The name of the pager program to pipe multi-page output to when the output is larger than the screen. The initial value is copied from the PAGER environment variable. expand (boolean): If true, expand columns that render to lists on multiple rows. Transactions and Postings \uf0c1 The structure of transactions and entries can be explained by the following simplified diagram: \uf0c1 The contents of a ledger is parsed into a list of directives, most of which are \u201cTransaction\u201d objects which contain two or more \u201cPosting\u201d objects. Postings are always linked only to a single transaction (they are never shared between transactions). Each posting refers to its parent transaction but has a unique account name, amount and associated lot (possibly with a cost), a price and some other attributes. The parent transaction itself contains a few useful attributes as well, such as a date, the name of a payee, a narration string, a flag, links, tags, etc. If we ignore the list of directives other than transactions, you can view the dataset as a single table of all postings joined with their parent transaction. It is mainly on this joined table of postings that we want to perform filtering and aggregation operations. However, because of the double-entry bookkeeping constraint, that is, the sum of amounts on postings attached to a transaction is zero, it is also quite useful to perform filtering operations at the transaction level. Because any isolated transaction has a total impact of zero on the global balance, any subset of transactions will also respect the accounting equation (Assets + Liabilities + Equity + Income + Expenses = 0), and producing balance sheets and income statements on subset of transactions provides meaningful views, for example, \u201call asset changes and expenses incurred during a trip to the Bahamas\u201d which could be selected by a tag. For this reason, we modify the SQL SELECT syntax to provide a two-level filtering syntax: since we have a single table of data, we replace the table name in FROM by a filtering expression which applies over transactions, and the WHERE clause applies to data pulled from the resulting list of postings: SELECT , , \u2026 FROM WHERE ; Both filtering expressions are optional. If no filtering expressions are provided, all postings will be enumerated over. Note that since the transactions are always filtered in date order, the results will be processed and returned in this order by default. Posting Data Columns \uf0c1 The list of targets refers to attributes of postings or of their parent transaction. The same list of \u201ccolumns\u201d is made available in the , to filter by posting attributes. For example, you could write the following query: SELECT date, narration, account, position WHERE account ~ \u201c.*:Vacation\u201d AND year >= 2014; Here, the \u201cdate\u201d, \u201cyear\u201d and \u201cnarration\u201d columns refer to attributes of the parent transaction, the \u201caccount\u201d and \u201cposition\u201d columns refer to attributes of the posting itself. You may name targets explicitly with the familiar AS operator: SELECT last(date) as last_date, cost(sum(position)) as cost; The full list of posting columns and functions available on them is best viewed by querying your actual client using \u201c help targets \u201c or \u201c help where \u201d, which prints out the list and data type of each available data column. You may also refer to the following diagram of the structure of a Posting object for the correspondence between the columns and the data structure attributes. Entry Data Columns \uf0c1 A different list of column names is available on the of the FROM clause. These columns refer to attributes of the Transaction objects. This clause is intended to filter whole transactions (i.e., all their postings or none at all). Available attributes include the date, transaction flag, the optional payee, narration, set of tags and links. Use the \u201c help from \u201d command to find the complete list of columns and functions available in this clause. A Beancount input file consists of many different types of entries, not just transactions. Some of these other types of entries (such as Open, Close, Balance, etc.) may also provide attributes that can be accessed from the FROM clause. This is embryonic at this point. (It\u2019s unclear yet how these will be used in the future, but I suspect we will find some interesting applications for them eventually. The FROM clause provides access to the type of the data entry via column \u201c type \u201d. It\u2019s still an exploration how much we can make pragmatic use of the SQL language for other types of directives.) The \u201cid\u201d Column A special column exists that identifies each transaction uniquely: \u201c id \u201d. It is a unique hash automatically computed from the transaction and should be stable between runs. SELECT DISTINCT id; This hash is derived from the contents of the transaction object itself (if you change something about the transaction, e.g. you edit the narration, the id will change). You can print and select using this column. It can be used for debugging, e.g. PRINT FROM id = '8e7c47250d040ae2b85de580dd4f5c2a'; The \u201cbalance\u201d Column One common desired output is a journal of entries over time (also called a \u201cregister\u201d in Ledger): SELECT date, account, position WHERE account ~ \"Chase:Slate\"; For this type of report, it is convenient to also render a column of the cumulative balance of the selected postings rows. Access to the previous row is not a standard SQL feature, so we get a little creative and provide a special column called \u201c balance \u201d which is automatically calculated based on the previous selected rows: SELECT date, account, position, balance WHERE account ~ \"Chase:Slate\"; This provides the ability to render typical account statements such as those mailed to you by a bank. Output might look like this: $ bean-query $T \"select date, account, position, balance where account ~ 'Expenses:Food:Restaurant';\" date account position balance ---------- ------------------------ --------- ---------- 2012-01-02 Expenses:Food:Restaurant 31.02 USD 31.02 USD 2012-01-04 Expenses:Food:Restaurant 25.33 USD 56.35 USD 2012-01-08 Expenses:Food:Restaurant 67.88 USD 124.23 USD 2012-01-09 Expenses:Food:Restaurant 35.28 USD 159.51 USD 2012-01-14 Expenses:Food:Restaurant 25.84 USD 185.35 USD 2012-01-17 Expenses:Food:Restaurant 36.73 USD 222.08 USD 2012-01-21 Expenses:Food:Restaurant 28.11 USD 250.19 USD 2012-01-22 Expenses:Food:Restaurant 21.12 USD 271.31 USD Wildcard Targets \uf0c1 Using a wildcard as the target list (\u201c*\u201d) select a good default list of columns: SELECT * FROM year = 2014; To view the actual list of columns selected, you can use the EXPLAIN prefix: EXPLAIN SELECT * FROM year = 2014; Data Types \uf0c1 The data attributes extracted from the postings or transactions have particular types. Most of the data types are regular types as provided by the underlying Python implementation language, types such as String (Python str) Date (a datetime.date instance). You can parse a date with the #\"...\" syntax; this uses Python\u2019s dateutil module and is pretty liberal in the formats it accepts. Integer (Python int) Boolean (Python bool object), as TRUE , FALSE Number (a decimal.Decimal object) Set of Strings (a Python set of str objects) Null objects ( NULL ) Positions and Inventories \uf0c1 However, one reason that our SQL-like client exists in the first place is for its ability to carry out aggregation operations on inventories of positions, the data structures at the core of Beancount, that implements its balancing semantics. Internally, Beancount defines Position and Inventory objects and is able to aggregate them together in an instance of Inventory. On each Posting, the \u201cposition\u201d column extracts an object of type Position, which when summed over produces an instance of Inventory. The shell is able to display those appropriately. More specifically, Inventory objects can contain multiple different lots of holdings, and each of these will get rendered on a separate line. Quantities of Positions and Inventories \uf0c1 Objects of type Position are rendered in their full detail by default, including not just their number and currency, but the details of their lot. Inventories consist of a list of lots, and as such are rendered similarly, as a list of positions (one per line) each with their full detail by default. This is generally too much detail. The shell provides functions that allow the user to summarize the positions into one of the various derived quantities. The types of derived quantities are: \u201c raw \u201d: render the position in its full detail, including cost and lot date \u201c units \u201d: render just the number and currency of the position \u201c cost \u201d: render the total cost of the position, that is the number of units x the per-unit cost \u201c weight \u201d: render the amount that is used to balance the postings of a transaction. The main distinction between cost and weight is for postings with a price conversion. \u201c value \u201d: render the amount at the market value of the last entry rendered. Functions with the same names are available to operate on position or inventory objects. For example, one could generate a table of final balances for each account like this: SELECT account, units(sum(position)), cost(sum(position)) GROUP BY 1; Refer to the table below for explicit examples of each type of posting and how it would get converted and rendered. posting raw (full detail) units cost weight market Simple 50.00 USD 50.00 USD 50.00 USD 50.00 USD 50.00 USD With Price Conversion 50.00 USD @ 1.35 CAD 50.00 USD 50.00 USD 67.50 CAD 50.00 USD Held at Cost 50 VEA {1.35 CAD} 50 VEA 67.50 CAD 67.50 CAD 67.50 CAD Held at Cost with Price 50 VEA {1.35 CAD} @ 1.45 CAD 50 VEA 67.50 CAD 67.50 CAD 72.50 CAD Operators \uf0c1 Common comparison and logical operators are provided to operate on the available data columns: = (equality), != (inequality) < (less than), <= (less than or equal) (greater than), >= (greater than or equal) AND (logical conjunction) OR (logical disjunction) NOT (logical negation) IN (set membership) We also provide a regular expression search operator into a string object: ~ (search regexp) At the moment, matching groups are ignored. You can use string, number and integer constants with those operators, and parentheses to explicitly state precedence. You can use the #\u201d...\u201d literal syntax to input dates (valid contents for the string are pretty liberal, it supports anything Python\u2019s dateutil.parser supports). Here is an example query that uses a few of these: SELECT date, payee WHERE account ~ 'Expenses:Food:Restaurant' AND 'trip-new-york' IN tags AND NOT payee = 'Uncle Boons' Unlike SQL, bean-query does not implement three-valued logic for NULL . This means that e.g. the expression NULL = NULL yields TRUE instead of NULL , which simplifies things, but may come as a surprise to veteran SQL users. Simple Functions \uf0c1 The shell provides a list of simple function that operate on a single data column and return a new value. These functions operate on particular types. The shell implements rudimentary type verification and should be able to warn you on incompatible types. Some example functions follow: COST(Inventory), COST(Position): Return an Amount, the cost of the position or inventory. UNITS(Inventory), UNITS(Position): Return the units of the position or inventory. DAY(date), MONTH(date), YEAR(date): Return an integer, the day, month or year of the posting or entry\u2019s date. LENGTH(list): Computes the length of a list or a set, e.g. on tags. PARENT(account-string): Returns the name of the parent account. These are just examples; for the complete list, see \u201c help targets \u201d, \u201c help where \u201d, \u201c help from \u201d. Note that it is exceedingly easy to add new functions to this list. As of December 2014, we are just beginning using the shell widely and we expect to be adding new functions as needed. If you need a function, please add a comment here or log a ticket and we will consider adding it to the list (we understand that the current list is limited). I intend to be liberal about adding new functions; as long as they have generic application, I don\u2019t think it should be a problem. Otherwise, I may be able to provide a mechanism for user to register new functions as part of Python plugins that could live outside the Beancount codebase. Aggregate Functions \uf0c1 Some functions operate on more than a single row. These functions aggregate and summarize the multiple values for the data column that they operate on. A prototypical usage of such a function is to sum the positions in an inventory: SELECT account, sum(position) WHERE account ~ 'Income' GROUP BY account; If a query target has at least one aggregating function, the query becomes an aggregated query (see relevant section for details). Note that you cannot use aggregation functions in the FROM or WHERE clauses. Examples of aggregate functions include: COUNT(...): Computes the number of postings selected (an integer). FIRST(...), LAST(...): Returns first or last value seen. MIN(...), MAX(...): Computes the minimum or maximum value seen. SUM(...): Sums up the values of each set. This works on amounts, positions, inventories, numbers, etc. As for simple functions, this is just a starting list. We will be adding more as needed. Use \u201c help targets \u201d to access the full list of available aggregate functions. Note: You cannot filter (using a WHERE clause) the results of aggregation functions; this requires the implementation offering a HAVING clause, and at the moment, HAVING filtering is not yet implemented. Simple vs. Aggregated Queries \uf0c1 There are two types of queries: Simple queries , which produce a row of results for each posting that matches the restricts in the WHERE clause. Aggregate queries , which produce a row of results for each group of postings that match the restricts in the WHERE clause. A query is \u201caggregate\u201d if it has at least one aggregate function in its list of targets. In order to identify the aggregation keys, all the non-aggregate columns have to be flagged using the GROUP BY clause, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY payee, account; You may also use the positional order of the targets to declare the group key, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY 1, 2; Furthermore, if you name your targets, you can use the explicit target names: SELECT payee, account as acc, COST(SUM(position)), LAST(date) GROUP BY 1, acc; This should all feel familiar if you have preliminary knowledge of SQL. Finally, because we implement a limited version of SQL, and that the simple columns must always be specified, omitting the GROUP BY clause should also eventually work and we should group by those columns implicitly, as a convenience. Distinct \uf0c1 There is a post-filtering phase that supports uniquifying result rows. You can trigger this unique filter with the DISTINCT flag after SELECT , as is common in SQL, e.g. SELECT DISTINCT account; Controlling Results \uf0c1 Order By \uf0c1 Analogous to the GROUP BY clause is an ORDER BY clause that controls the final ordering of the result rows: SELECT \u2026 GROUP BY account, payee ORDER BY payee, date; The clause is optional. If you do not specify it, the default order of iteration of selected postings is used to output the results (that is, the order of transactions-sorted by date- and then their postings). As in SQL, you may reverse the order of sorting by a DESC suffix (the default is the same as specifying ASC ): SELECT \u2026 GROUP BY account, payee ORDER BY payee, date DESC; Limit \uf0c1 Our query language also supports a LIMIT clause to interrupt output row generation: SELECT \u2026 LIMIT 100; This would output the first 100 result rows and then stop. While this is a common clause present in the SQL language, in the context of double-entry bookkeeping it is not very useful: we always have relatively small datasets to work from. Nevertheless, we provide it for completeness. Format \uf0c1 For SELECT , JOURNAL and BALANCES queries, the output format is a table of text by default. We support CSV output. ( We could easily add support for XLS or Google Sheets output.) However, for PRINT queries, the output format is Beancount input text format. Statement Operators \uf0c1 The shell provides a few operators designed to facilitate the generation of balance sheets and income statements. The particular methodology used to define these operations should be described in detail in the \u201c introduction to double-entry bookkeeping \u201d document that accompanies Beancount and is mostly located in the source code in the summarize module. These special operators are provided on the FROM clause that is made available on the various forms of query commands in the shell. These further transform the set of entries selected by the FROM expression at the transaction levels (not postings). Please note that these are not from standard SQL; these are extensions provided by this shell language only. Opening a Period \uf0c1 Opening an exercise period at a particular date replaces all entries before that date by summarization entries that book the expected balance against an Equity \u201copening balances\u201d account and implicitly clears the income and expenses to zero by transferring their balances to an Equity \u201cprevious earnings\u201d account (see beancount.ops.summarize.open() for implementation details). It is invoked like this: SELECT \u2026 FROM OPEN ON \u2026 For example: SELECT * FROM has_account(\"Invest\") OPEN ON 2014-01-01; If you want, you can view just the inserted summarization entries like this: PRINT FROM flag = \"S\" AND account ~ \"Invest\" OPEN ON 2014-01-01; Closing a Period \uf0c1 Closing an exercise period involves mainly truncating all entries that come after the given date and ensuring that currency conversions are correctly corrected for (see beancount.ops.summarize.close() for implementation details). It is invoked like this: SELECT \u2026 FROM CLOSE [ON ] \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01; Note that the closing date should be one day after the last transaction you would like to include (this is in line with the convention we use everywhere in Beancount whereby starting dates are inclusive and ending dates exclusive). The closing date is optional. If the date is not specified, the date one day beyond the date of the last entry is used. Closing a period leaves the Income and Expenses accounts as they are, that is, their balances are not cleared to zero to Equity. This is because closing is also used to produce final balances for income statements. \u201cClearing\u201d, as described in the next section, is only needed for balance sheets. Clearing Income & Expenses \uf0c1 In order to produce a balance sheet, we need to transfer final balances of the Income and Expenses to an Equity \u201ccurrent earnings\u201d account (sometimes called \u201cretained earnings\u201d or \u201cnet income\u201d; you can select the specific account name to use using options in the input file). The resulting balances of income statement accounts should be zero (see beancount.ops.summarize.clear() for implementation details). You can clear like this: SELECT \u2026 FROM CLEAR \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01 CLEAR; This is a statement suitable to produce a list of accounts to build a balance sheet. The \u201c Equity:Earnings:Current \u201d (by default) will contain the net income accumulated during the preceding period. No balances for the Income nor Expenses accounts should appear in the output. Example Statements \uf0c1 The statement operators of course may be combined. For instance, if you wanted to output data for an income statement for year 2013, you could issue the following statement: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 WHERE account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; This would produce a list of balances for Income and Expenses accounts. To generate a balance sheet, you would add the CLEAR option and select the other accounts: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 CLEAR WHERE not account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; Note that if you added the CLEAR operator to the statement of income statement accounts, all the balances would show at zero because of the inserted transactions that move those balances to the Equity net income account at the end of the period. It is relevant to notice that the examples above do not filter the transactions any further. If you are selecting a subset of transactions you may want to leave the accounts unopened, unclosed and uncleared because applying only some of the transactions on top of the opening balances of Assets and Liabilities accounts will not produce correct balances for those accounts. It would be more useful to leave them all opened, and to interpret the balances of the balance sheet accounts as the changes in those accounts for the subset of transactions selected. For example, if you selected all transactions from a trip (using a tag), you would obtain a list of changes in Expenses (and possibly Income) tagged as being included in this trip, and the Assets and Liabilities accounts would show where the funds for those Expenses came from. Consult the \u201cintroduction to double-entry method\u201d document for a pictorial representation of this. (Granted, this is probably worth a dedicated document and I might produce one at some point.) Example Fetching Cost Basis \uf0c1 \u201c... is there currently an easy way to determine what my cost basis is for an account on a given date (other than manually adding up UNITS * COST for every contribution, which is kind of a pain)? I'm trying to estimate the tax implications of potential stock sales.\u201d [Question from Matthew Harris] For a detailed report of share movements: SELECT account, currency, position, COST(position) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" Or this for the sum total of the cost bases: SELECT sum(cost(position)) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" High-Level Shortcuts \uf0c1 There are two types of queries that are very common for accounting applications: journals and balances reports. While we have explicit implementations of such reports that can be produced using the bean-report tool, we are also able to synthesize good approximations of such reports using SELECT statements. This section describes a few additional selection commands that translate directly into SELECT statements and which are then run with the same query code. These are intended as convenient shortcuts. Selecting Journals \uf0c1 A common type of query is one that generates a linear journal of entries (Ledger calls this a \u201cregister\u201d). This roughly corresponds to an account statement, but with our language, such a statement can be generated for any subset of postings. You can generate a journal with the following syntax: JOURNAL [AT ] [FROM \u2026] The regular expression account-regexp is used to select which subset of accounts to generate a journal for. The optional \u201cAT \u201d clause is used to specify an aggregation function for the amounts rendered (typically UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. Here is an example journal-generating query: JOURNAL \"Invest\" AT COST FROM HAS_ACCOUNT(\"Assets:US\"); Selecting Balances \uf0c1 The other most common type of report is a table of the balances of various accounts at a particular date. This can be viewed as a SELECT query aggregating positions grouping by account. You can generate a balances report with the following syntax: BALANCES [AT ] [FROM \u2026] The optional \u201cAT \u201d clause is used to specify an aggregation function for the balances rendered (usually UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. To generate your balances at a particular date, close your set of entries using the \u201c FROM\u2026 CLOSE ON \u201d form described above. Observe that typical balance sheets and income statements seen in an accounting context are subsets of tables of balances such as reported by this query. An income statement reports on just the transactions that appears during a period of time, and a balance sheet summarizes transactions before its reporting before and clears the income & expenses accumulated during the period to an equity account. Then some minor reformatting is carried out. Please consult the introduction document on double-entry bookkeeping for more details, and the section above that discusses the \u201copen\u201d, \u201cclose\u201d and \u201cclear\u201d operations. We will also be providing a separate text processing tool that can accept balance reports and reformat them in a two-column format similar to that you would see balance sheets and income statements. Print \uf0c1 It can be useful to generate output in Beancount format, so that subsets of transactions can be saved to files, for example. The shell provides that ability via the PRINT command: PRINT [FROM \u2026] The FROM clause obeys the usual semantics as described elsewhere in this document. The resulting filtered stream of Beancount entries is then printed out on the output in Beancount syntax. In particular, just running the \u201c PRINT \u201d command will spit out the parsed and loaded contents of a Beancount file. You can use this for troubleshooting if needed, or to expand transactions generated from a plugin you may be in the process of developing. Debugging / Explain \uf0c1 If you\u2019re having trouble getting a particular statement to compile and run,. you can prefix any query statement with the EXPLAIN modifier, e.g.: EXPLAIN SELECT \u2026 This will not run the statement, but rather print out the intermediate AST and compiled representation as well as the list of computed statements. This can be useful to report bugs on the mailing-list. Also, this shows you the translated form of the JOURNAL and BALANCES statements. Future Features \uf0c1 The following list of features were planned for the first release but I\u2019ve decided to make a first cut without them. I\u2019ll be adding those during a revision. Flattening Inventories \uf0c1 If you provide the FLATTEN option after a query, it tells the query engine to flatten inventories with multiple lots into separate rows for each lot. For example, if you have an inventory balance with the following contents: 3 AAPL {102.34 USD} 4 AAPL {104.53 USD} 5 AAPL {106.23 USD} Using the following query: SELECT account, sum(position) GROUP BY account; It should return a single row of results, rendered over three lines. However, adding the option: SELECT account, sum(position) GROUP BY account FLATTEN; This should return three separate rows, with all the selected attributes, as if there were that many postings. Sub-Selects \uf0c1 The ability to select from the result of another SELECT is not currently supported, but the internals of the query code are prepared to do so. More Information \uf0c1 This document attempts to provide a good high-level summary of the features supported in our query language. However, should you find you need more information, you may take a look at the original proposal , or consult the source code under the beancount.query directory. In particular, the parser will provide insight into the specifics of the syntax, and the environments will shed some light on the supported data columns and functions. Feel free to rummage in the source code and ask questions on the mailing-list. Appendix \uf0c1 Future Features \uf0c1 This section documents ideas for features to be implemented in a future version. Pivot By \uf0c1 A special PIVOT BY clause will eventually be usable to convert the output from a one-dimensional list of results to a two-dimensional table. For example, the following query: SELECT account, YEAR(date) AS year, SUM(COST(position)) AS balance WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1,2; Might generate the following table of results: account year balance ------------------------ ---- ----------- Expenses:Food:Alcohol 2012 57.91 USD Expenses:Food:Alcohol 2013 33.45 USD Expenses:Food:Coffee 2012 42.07 USD Expenses:Food:Coffee 2013 124.69 USD Expenses:Food:Coffee 2014 38.74 USD Expenses:Food:Groceries 2012 2172.97 USD Expenses:Food:Groceries 2013 2161.90 USD Expenses:Food:Groceries 2014 2072.36 USD Expenses:Food:Restaurant 2012 4310.60 USD Expenses:Food:Restaurant 2013 5053.61 USD Expenses:Food:Restaurant 2014 4209.06 USD If you add a PIVOT clause to the query, like this: \u2026 PIVOT BY account, year; You would get a table like this: account/year 2012 2013 2014 ------------------------ ----------- ----------- ----------- Expenses:Food:Alcohol 57.91 USD 33.45 USD Expenses:Food:Coffee 42.07 USD 124.69 USD 38.74 USD Expenses:Food:Groceries 2172.97 USD 2161.90 USD 2072.36 USD Expenses:Food:Restaurant 4310.60 USD 5053.61 USD 4209.06 USD If your table has more than three columns, the non-pivoted column would get expanded horizontally, like this: SELECT account, YEAR(date), SUM(COST(position)) AS balance, LAST(date) AS updated WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1, 2; You would get a table like this: account year balance updated ------------------------ ---- ----------- ---------- Expenses:Food:Alcohol 2012 57.91 USD 2012-07-17 Expenses:Food:Alcohol 2013 33.45 USD 2013-12-13 Expenses:Food:Coffee 2012 42.07 USD 2012-07-19 Expenses:Food:Coffee 2013 124.69 USD 2013-12-16 Expenses:Food:Coffee 2014 38.74 USD 2014-09-21 Expenses:Food:Groceries 2012 2172.97 USD 2012-12-30 Expenses:Food:Groceries 2013 2161.90 USD 2013-12-31 Expenses:Food:Groceries 2014 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 2012 4310.60 USD 2012-12-30 Expenses:Food:Restaurant 2013 5053.61 USD 2013-12-29 Expenses:Food:Restaurant 2014 4209.06 USD 2014-11-28 Pivoting, this would generate this table: account/balance,updated 2012/balanc 2012/updat 2013/balanc 2013/updat 2014/balanc 2014/updat ------------------------ ----------- ---------- ----------- ---------- ----------- ---------- Expenses:Food:Alcohol 57.91 USD 2012-07-17 33.45 USD 2013-12-13 Expenses:Food:Coffee 42.07 USD 2012-07-19 124.69 USD 2013-12-16 38.74 USD 2014-09-21 Expenses:Food:Groceries 2172.97 USD 2012-12-30 2161.90 USD 2013-12-31 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 4310.60 USD 2012-12-30 5053.61 USD 2013-12-29 4209.06 USD 2014-11-28","title":"Beancount Query Language"},{"location":"beancount_query_language.html#beancount-query-language","text":"Martin Blais, January 2015 http://furius.ca/beancount/doc/query","title":"Beancount Query Language"},{"location":"beancount_query_language.html#introduction","text":"The purpose of Beancount is to allow the user to create an accurate and error-free representation of financial transactions, typically those occurring in a user or in an institution\u2019s associated set of accounts, to then extract various reports from this list of transactions. Beancount provides a few tools to extract reports from the corpus of transactions: custom reports (using the Beancount bean-report tool), a web interface (using the bean-web tool) and the ability for the user to write their own scripts to output anything they want. The repository of financial transactions is always read from the text file input, but once parsed and loaded in memory, extracting information from Beancount could be carried out much like you would another database, that is, instead of using custom code to generate output from the data structures, a query language could be compiled and run over the relatively regular list of transactions. In practice, you could flatten out the list of postings to an external SQL database and make queries using that database\u2019s own tools, but the results of this approach are quite disappointing, mainly due to the lack of operations on inventories which is the basis of balancing rules in Beancount. By providing a slightly specialized query engine that takes advantage of the structure of the double-entry transactions we can easily generate custom reports specific to accounting purposes. This document describes our specialized SQL-like query client. It assumes you have at least a passing knowledge of SQL syntax. If not, you may want to first read something about it.","title":"Introduction"},{"location":"beancount_query_language.html#motivation","text":"So one might ask: Why create another SQL client? Why not output the data to an SQLite database and allow the user to use that SQL client? Well, we have done that (see the bean-sql script which converts your Beancount ledger into an SQLite database) and the results are not great. Writing queries is painful and carrying out operations on lots that are held at cost is difficult. By taking advantage of a few aspects of our in-memory data structures, we can do better. So Beancount comes with its own SQL-like query client called \u201c bean-query \u201d. The clients implements the following \u201cextras\u201d that are essential to Beancount: It allows to easily filter at two levels simultaneously: You can filter whole transactions, which has the benefit of respecting the accounting equation, and then, usually for presentation purposes, you can also filter at the postings level. The client supports the semantics of inventory booking implemented in Beancount. It also supports aggregation functions on inventory objects and rendering functions (e.g., COST() to render the cost of an inventory instead of its contents). The client allows you to flatten multiple lots into separate postings to produce lists of holdings each with their associated cost basis. Transactions can be summarized in a manner useful to produce balance sheets and income statements. For example, our SQL variant explicitly supports a \u201cclose\u201d operation with an effect similar to closing the year, which inserts transactions to clear income statement accounts to equity and removes past history. See this post as well for a similar answer.","title":"Motivation"},{"location":"beancount_query_language.html#warning-caveat","text":"Approximately 70% of the features desired in the original design doc were implemented in the query language in late 2014. More work will be needed to cover the full feature set, but the current iteration supports most of the use cases covered by Ledger, and I suspect by Beancount users. More feedback is desired on the current version before moving on, and I would like to move forward improving some of the more fundamental aspects of Beancount (namely, inventory booking) before spending more time on the query language. It is functional as it is, but a second revision will be made later on, informed by user feedback and prolonged use. Therefore, a first release of the query language has been merged in the default stable branch. This document presents this first iteration on the Beancount query language.","title":"Warning & Caveat"},{"location":"beancount_query_language.html#making-queries","text":"The custom query client that we provide is called bean-query . Run it on your ledger file, like this: $ bean-query myfile.beancount Input file: \"My Ledger\u2019s Title\" Ready with 13996 directives (21112 postings in 8833 transactions). beancount> _ This launches the query tool in interactive mode, where you can enter multiple commands on the dataset loaded in memory. bean-query parses the input file, spits out a few basic statistics about your ledger, and provides a command prompt for you to enter query commands. You can type \u201c help \u201d here to view the list of available commands. If any errors in your ledger are incurred, they are printed before the prompt. To suppress error printing, run the tool with the \u201cno-errors\u201d option: $ bean-query -q myfile.beancount","title":"Making Queries"},{"location":"beancount_query_language.html#batch-mode-queries","text":"If you\u2019d like to run queries directly from the command-line, without an interactive prompt, you can provide the query directly following your filename: $ bean-query myfile.beancount 'balances from year = 2014' account balance ---------------------------------------------------------------------- \u2026 \u2026","title":"Batch Mode Queries"},{"location":"beancount_query_language.html#all-the-interactive-commands-are-supported","text":"","title":"All the interactive commands are supported."},{"location":"beancount_query_language.html#shell-variables","text":"The interactive shell has a few \u201c set \u201d variables that you can customize to change some of the behavior of the shell. These are like environment variables. Type the \u201c set \u201d command to see the list of available variables and their current value. The variables are: format (string): The output format. Currently, only \u201ctext\u201d is supported. boxed (boolean): Whether we should draw a box around the output table. spaced (boolean): Whether to insert an empty line between every result row. This is only relevant because postings with multiple lots may require multiple lines to be rendered, and inserting an empty line helps delineate those as separate. pager (string): The name of the pager program to pipe multi-page output to when the output is larger than the screen. The initial value is copied from the PAGER environment variable. expand (boolean): If true, expand columns that render to lists on multiple rows.","title":"Shell Variables"},{"location":"beancount_query_language.html#transactions-and-postings","text":"The structure of transactions and entries can be explained by the following simplified diagram:","title":"Transactions and Postings"},{"location":"beancount_query_language.html#_1","text":"The contents of a ledger is parsed into a list of directives, most of which are \u201cTransaction\u201d objects which contain two or more \u201cPosting\u201d objects. Postings are always linked only to a single transaction (they are never shared between transactions). Each posting refers to its parent transaction but has a unique account name, amount and associated lot (possibly with a cost), a price and some other attributes. The parent transaction itself contains a few useful attributes as well, such as a date, the name of a payee, a narration string, a flag, links, tags, etc. If we ignore the list of directives other than transactions, you can view the dataset as a single table of all postings joined with their parent transaction. It is mainly on this joined table of postings that we want to perform filtering and aggregation operations. However, because of the double-entry bookkeeping constraint, that is, the sum of amounts on postings attached to a transaction is zero, it is also quite useful to perform filtering operations at the transaction level. Because any isolated transaction has a total impact of zero on the global balance, any subset of transactions will also respect the accounting equation (Assets + Liabilities + Equity + Income + Expenses = 0), and producing balance sheets and income statements on subset of transactions provides meaningful views, for example, \u201call asset changes and expenses incurred during a trip to the Bahamas\u201d which could be selected by a tag. For this reason, we modify the SQL SELECT syntax to provide a two-level filtering syntax: since we have a single table of data, we replace the table name in FROM by a filtering expression which applies over transactions, and the WHERE clause applies to data pulled from the resulting list of postings: SELECT , , \u2026 FROM WHERE ; Both filtering expressions are optional. If no filtering expressions are provided, all postings will be enumerated over. Note that since the transactions are always filtered in date order, the results will be processed and returned in this order by default.","title":""},{"location":"beancount_query_language.html#posting-data-columns","text":"The list of targets refers to attributes of postings or of their parent transaction. The same list of \u201ccolumns\u201d is made available in the , to filter by posting attributes. For example, you could write the following query: SELECT date, narration, account, position WHERE account ~ \u201c.*:Vacation\u201d AND year >= 2014; Here, the \u201cdate\u201d, \u201cyear\u201d and \u201cnarration\u201d columns refer to attributes of the parent transaction, the \u201caccount\u201d and \u201cposition\u201d columns refer to attributes of the posting itself. You may name targets explicitly with the familiar AS operator: SELECT last(date) as last_date, cost(sum(position)) as cost; The full list of posting columns and functions available on them is best viewed by querying your actual client using \u201c help targets \u201c or \u201c help where \u201d, which prints out the list and data type of each available data column. You may also refer to the following diagram of the structure of a Posting object for the correspondence between the columns and the data structure attributes.","title":"Posting Data Columns"},{"location":"beancount_query_language.html#entry-data-columns","text":"A different list of column names is available on the of the FROM clause. These columns refer to attributes of the Transaction objects. This clause is intended to filter whole transactions (i.e., all their postings or none at all). Available attributes include the date, transaction flag, the optional payee, narration, set of tags and links. Use the \u201c help from \u201d command to find the complete list of columns and functions available in this clause. A Beancount input file consists of many different types of entries, not just transactions. Some of these other types of entries (such as Open, Close, Balance, etc.) may also provide attributes that can be accessed from the FROM clause. This is embryonic at this point. (It\u2019s unclear yet how these will be used in the future, but I suspect we will find some interesting applications for them eventually. The FROM clause provides access to the type of the data entry via column \u201c type \u201d. It\u2019s still an exploration how much we can make pragmatic use of the SQL language for other types of directives.) The \u201cid\u201d Column A special column exists that identifies each transaction uniquely: \u201c id \u201d. It is a unique hash automatically computed from the transaction and should be stable between runs. SELECT DISTINCT id; This hash is derived from the contents of the transaction object itself (if you change something about the transaction, e.g. you edit the narration, the id will change). You can print and select using this column. It can be used for debugging, e.g. PRINT FROM id = '8e7c47250d040ae2b85de580dd4f5c2a'; The \u201cbalance\u201d Column One common desired output is a journal of entries over time (also called a \u201cregister\u201d in Ledger): SELECT date, account, position WHERE account ~ \"Chase:Slate\"; For this type of report, it is convenient to also render a column of the cumulative balance of the selected postings rows. Access to the previous row is not a standard SQL feature, so we get a little creative and provide a special column called \u201c balance \u201d which is automatically calculated based on the previous selected rows: SELECT date, account, position, balance WHERE account ~ \"Chase:Slate\"; This provides the ability to render typical account statements such as those mailed to you by a bank. Output might look like this: $ bean-query $T \"select date, account, position, balance where account ~ 'Expenses:Food:Restaurant';\" date account position balance ---------- ------------------------ --------- ---------- 2012-01-02 Expenses:Food:Restaurant 31.02 USD 31.02 USD 2012-01-04 Expenses:Food:Restaurant 25.33 USD 56.35 USD 2012-01-08 Expenses:Food:Restaurant 67.88 USD 124.23 USD 2012-01-09 Expenses:Food:Restaurant 35.28 USD 159.51 USD 2012-01-14 Expenses:Food:Restaurant 25.84 USD 185.35 USD 2012-01-17 Expenses:Food:Restaurant 36.73 USD 222.08 USD 2012-01-21 Expenses:Food:Restaurant 28.11 USD 250.19 USD 2012-01-22 Expenses:Food:Restaurant 21.12 USD 271.31 USD","title":"Entry Data Columns"},{"location":"beancount_query_language.html#wildcard-targets","text":"Using a wildcard as the target list (\u201c*\u201d) select a good default list of columns: SELECT * FROM year = 2014; To view the actual list of columns selected, you can use the EXPLAIN prefix: EXPLAIN SELECT * FROM year = 2014;","title":"Wildcard Targets"},{"location":"beancount_query_language.html#data-types","text":"The data attributes extracted from the postings or transactions have particular types. Most of the data types are regular types as provided by the underlying Python implementation language, types such as String (Python str) Date (a datetime.date instance). You can parse a date with the #\"...\" syntax; this uses Python\u2019s dateutil module and is pretty liberal in the formats it accepts. Integer (Python int) Boolean (Python bool object), as TRUE , FALSE Number (a decimal.Decimal object) Set of Strings (a Python set of str objects) Null objects ( NULL )","title":"Data Types"},{"location":"beancount_query_language.html#positions-and-inventories","text":"However, one reason that our SQL-like client exists in the first place is for its ability to carry out aggregation operations on inventories of positions, the data structures at the core of Beancount, that implements its balancing semantics. Internally, Beancount defines Position and Inventory objects and is able to aggregate them together in an instance of Inventory. On each Posting, the \u201cposition\u201d column extracts an object of type Position, which when summed over produces an instance of Inventory. The shell is able to display those appropriately. More specifically, Inventory objects can contain multiple different lots of holdings, and each of these will get rendered on a separate line.","title":"Positions and Inventories"},{"location":"beancount_query_language.html#quantities-of-positions-and-inventories","text":"Objects of type Position are rendered in their full detail by default, including not just their number and currency, but the details of their lot. Inventories consist of a list of lots, and as such are rendered similarly, as a list of positions (one per line) each with their full detail by default. This is generally too much detail. The shell provides functions that allow the user to summarize the positions into one of the various derived quantities. The types of derived quantities are: \u201c raw \u201d: render the position in its full detail, including cost and lot date \u201c units \u201d: render just the number and currency of the position \u201c cost \u201d: render the total cost of the position, that is the number of units x the per-unit cost \u201c weight \u201d: render the amount that is used to balance the postings of a transaction. The main distinction between cost and weight is for postings with a price conversion. \u201c value \u201d: render the amount at the market value of the last entry rendered. Functions with the same names are available to operate on position or inventory objects. For example, one could generate a table of final balances for each account like this: SELECT account, units(sum(position)), cost(sum(position)) GROUP BY 1; Refer to the table below for explicit examples of each type of posting and how it would get converted and rendered. posting raw (full detail) units cost weight market Simple 50.00 USD 50.00 USD 50.00 USD 50.00 USD 50.00 USD With Price Conversion 50.00 USD @ 1.35 CAD 50.00 USD 50.00 USD 67.50 CAD 50.00 USD Held at Cost 50 VEA {1.35 CAD} 50 VEA 67.50 CAD 67.50 CAD 67.50 CAD Held at Cost with Price 50 VEA {1.35 CAD} @ 1.45 CAD 50 VEA 67.50 CAD 67.50 CAD 72.50 CAD","title":"Quantities of Positions and Inventories"},{"location":"beancount_query_language.html#operators","text":"Common comparison and logical operators are provided to operate on the available data columns: = (equality), != (inequality) < (less than), <= (less than or equal) (greater than), >= (greater than or equal) AND (logical conjunction) OR (logical disjunction) NOT (logical negation) IN (set membership) We also provide a regular expression search operator into a string object: ~ (search regexp) At the moment, matching groups are ignored. You can use string, number and integer constants with those operators, and parentheses to explicitly state precedence. You can use the #\u201d...\u201d literal syntax to input dates (valid contents for the string are pretty liberal, it supports anything Python\u2019s dateutil.parser supports). Here is an example query that uses a few of these: SELECT date, payee WHERE account ~ 'Expenses:Food:Restaurant' AND 'trip-new-york' IN tags AND NOT payee = 'Uncle Boons' Unlike SQL, bean-query does not implement three-valued logic for NULL . This means that e.g. the expression NULL = NULL yields TRUE instead of NULL , which simplifies things, but may come as a surprise to veteran SQL users.","title":"Operators"},{"location":"beancount_query_language.html#simple-functions","text":"The shell provides a list of simple function that operate on a single data column and return a new value. These functions operate on particular types. The shell implements rudimentary type verification and should be able to warn you on incompatible types. Some example functions follow: COST(Inventory), COST(Position): Return an Amount, the cost of the position or inventory. UNITS(Inventory), UNITS(Position): Return the units of the position or inventory. DAY(date), MONTH(date), YEAR(date): Return an integer, the day, month or year of the posting or entry\u2019s date. LENGTH(list): Computes the length of a list or a set, e.g. on tags. PARENT(account-string): Returns the name of the parent account. These are just examples; for the complete list, see \u201c help targets \u201d, \u201c help where \u201d, \u201c help from \u201d. Note that it is exceedingly easy to add new functions to this list. As of December 2014, we are just beginning using the shell widely and we expect to be adding new functions as needed. If you need a function, please add a comment here or log a ticket and we will consider adding it to the list (we understand that the current list is limited). I intend to be liberal about adding new functions; as long as they have generic application, I don\u2019t think it should be a problem. Otherwise, I may be able to provide a mechanism for user to register new functions as part of Python plugins that could live outside the Beancount codebase.","title":"Simple Functions"},{"location":"beancount_query_language.html#aggregate-functions","text":"Some functions operate on more than a single row. These functions aggregate and summarize the multiple values for the data column that they operate on. A prototypical usage of such a function is to sum the positions in an inventory: SELECT account, sum(position) WHERE account ~ 'Income' GROUP BY account; If a query target has at least one aggregating function, the query becomes an aggregated query (see relevant section for details). Note that you cannot use aggregation functions in the FROM or WHERE clauses. Examples of aggregate functions include: COUNT(...): Computes the number of postings selected (an integer). FIRST(...), LAST(...): Returns first or last value seen. MIN(...), MAX(...): Computes the minimum or maximum value seen. SUM(...): Sums up the values of each set. This works on amounts, positions, inventories, numbers, etc. As for simple functions, this is just a starting list. We will be adding more as needed. Use \u201c help targets \u201d to access the full list of available aggregate functions. Note: You cannot filter (using a WHERE clause) the results of aggregation functions; this requires the implementation offering a HAVING clause, and at the moment, HAVING filtering is not yet implemented.","title":"Aggregate Functions"},{"location":"beancount_query_language.html#simple-vs-aggregated-queries","text":"There are two types of queries: Simple queries , which produce a row of results for each posting that matches the restricts in the WHERE clause. Aggregate queries , which produce a row of results for each group of postings that match the restricts in the WHERE clause. A query is \u201caggregate\u201d if it has at least one aggregate function in its list of targets. In order to identify the aggregation keys, all the non-aggregate columns have to be flagged using the GROUP BY clause, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY payee, account; You may also use the positional order of the targets to declare the group key, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY 1, 2; Furthermore, if you name your targets, you can use the explicit target names: SELECT payee, account as acc, COST(SUM(position)), LAST(date) GROUP BY 1, acc; This should all feel familiar if you have preliminary knowledge of SQL. Finally, because we implement a limited version of SQL, and that the simple columns must always be specified, omitting the GROUP BY clause should also eventually work and we should group by those columns implicitly, as a convenience.","title":"Simple vs. Aggregated Queries"},{"location":"beancount_query_language.html#distinct","text":"There is a post-filtering phase that supports uniquifying result rows. You can trigger this unique filter with the DISTINCT flag after SELECT , as is common in SQL, e.g. SELECT DISTINCT account;","title":"Distinct"},{"location":"beancount_query_language.html#controlling-results","text":"","title":"Controlling Results"},{"location":"beancount_query_language.html#order-by","text":"Analogous to the GROUP BY clause is an ORDER BY clause that controls the final ordering of the result rows: SELECT \u2026 GROUP BY account, payee ORDER BY payee, date; The clause is optional. If you do not specify it, the default order of iteration of selected postings is used to output the results (that is, the order of transactions-sorted by date- and then their postings). As in SQL, you may reverse the order of sorting by a DESC suffix (the default is the same as specifying ASC ): SELECT \u2026 GROUP BY account, payee ORDER BY payee, date DESC;","title":"Order By"},{"location":"beancount_query_language.html#limit","text":"Our query language also supports a LIMIT clause to interrupt output row generation: SELECT \u2026 LIMIT 100; This would output the first 100 result rows and then stop. While this is a common clause present in the SQL language, in the context of double-entry bookkeeping it is not very useful: we always have relatively small datasets to work from. Nevertheless, we provide it for completeness.","title":"Limit"},{"location":"beancount_query_language.html#format","text":"For SELECT , JOURNAL and BALANCES queries, the output format is a table of text by default. We support CSV output. ( We could easily add support for XLS or Google Sheets output.) However, for PRINT queries, the output format is Beancount input text format.","title":"Format"},{"location":"beancount_query_language.html#statement-operators","text":"The shell provides a few operators designed to facilitate the generation of balance sheets and income statements. The particular methodology used to define these operations should be described in detail in the \u201c introduction to double-entry bookkeeping \u201d document that accompanies Beancount and is mostly located in the source code in the summarize module. These special operators are provided on the FROM clause that is made available on the various forms of query commands in the shell. These further transform the set of entries selected by the FROM expression at the transaction levels (not postings). Please note that these are not from standard SQL; these are extensions provided by this shell language only.","title":"Statement Operators"},{"location":"beancount_query_language.html#opening-a-period","text":"Opening an exercise period at a particular date replaces all entries before that date by summarization entries that book the expected balance against an Equity \u201copening balances\u201d account and implicitly clears the income and expenses to zero by transferring their balances to an Equity \u201cprevious earnings\u201d account (see beancount.ops.summarize.open() for implementation details). It is invoked like this: SELECT \u2026 FROM OPEN ON \u2026 For example: SELECT * FROM has_account(\"Invest\") OPEN ON 2014-01-01; If you want, you can view just the inserted summarization entries like this: PRINT FROM flag = \"S\" AND account ~ \"Invest\" OPEN ON 2014-01-01;","title":"Opening a Period"},{"location":"beancount_query_language.html#closing-a-period","text":"Closing an exercise period involves mainly truncating all entries that come after the given date and ensuring that currency conversions are correctly corrected for (see beancount.ops.summarize.close() for implementation details). It is invoked like this: SELECT \u2026 FROM CLOSE [ON ] \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01; Note that the closing date should be one day after the last transaction you would like to include (this is in line with the convention we use everywhere in Beancount whereby starting dates are inclusive and ending dates exclusive). The closing date is optional. If the date is not specified, the date one day beyond the date of the last entry is used. Closing a period leaves the Income and Expenses accounts as they are, that is, their balances are not cleared to zero to Equity. This is because closing is also used to produce final balances for income statements. \u201cClearing\u201d, as described in the next section, is only needed for balance sheets.","title":"Closing a Period"},{"location":"beancount_query_language.html#clearing-income-expenses","text":"In order to produce a balance sheet, we need to transfer final balances of the Income and Expenses to an Equity \u201ccurrent earnings\u201d account (sometimes called \u201cretained earnings\u201d or \u201cnet income\u201d; you can select the specific account name to use using options in the input file). The resulting balances of income statement accounts should be zero (see beancount.ops.summarize.clear() for implementation details). You can clear like this: SELECT \u2026 FROM CLEAR \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01 CLEAR; This is a statement suitable to produce a list of accounts to build a balance sheet. The \u201c Equity:Earnings:Current \u201d (by default) will contain the net income accumulated during the preceding period. No balances for the Income nor Expenses accounts should appear in the output.","title":"Clearing Income & Expenses"},{"location":"beancount_query_language.html#example-statements","text":"The statement operators of course may be combined. For instance, if you wanted to output data for an income statement for year 2013, you could issue the following statement: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 WHERE account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; This would produce a list of balances for Income and Expenses accounts. To generate a balance sheet, you would add the CLEAR option and select the other accounts: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 CLEAR WHERE not account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; Note that if you added the CLEAR operator to the statement of income statement accounts, all the balances would show at zero because of the inserted transactions that move those balances to the Equity net income account at the end of the period. It is relevant to notice that the examples above do not filter the transactions any further. If you are selecting a subset of transactions you may want to leave the accounts unopened, unclosed and uncleared because applying only some of the transactions on top of the opening balances of Assets and Liabilities accounts will not produce correct balances for those accounts. It would be more useful to leave them all opened, and to interpret the balances of the balance sheet accounts as the changes in those accounts for the subset of transactions selected. For example, if you selected all transactions from a trip (using a tag), you would obtain a list of changes in Expenses (and possibly Income) tagged as being included in this trip, and the Assets and Liabilities accounts would show where the funds for those Expenses came from. Consult the \u201cintroduction to double-entry method\u201d document for a pictorial representation of this. (Granted, this is probably worth a dedicated document and I might produce one at some point.)","title":"Example Statements"},{"location":"beancount_query_language.html#example-fetching-cost-basis","text":"\u201c... is there currently an easy way to determine what my cost basis is for an account on a given date (other than manually adding up UNITS * COST for every contribution, which is kind of a pain)? I'm trying to estimate the tax implications of potential stock sales.\u201d [Question from Matthew Harris] For a detailed report of share movements: SELECT account, currency, position, COST(position) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" Or this for the sum total of the cost bases: SELECT sum(cost(position)) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\"","title":"Example Fetching Cost Basis"},{"location":"beancount_query_language.html#high-level-shortcuts","text":"There are two types of queries that are very common for accounting applications: journals and balances reports. While we have explicit implementations of such reports that can be produced using the bean-report tool, we are also able to synthesize good approximations of such reports using SELECT statements. This section describes a few additional selection commands that translate directly into SELECT statements and which are then run with the same query code. These are intended as convenient shortcuts.","title":"High-Level Shortcuts"},{"location":"beancount_query_language.html#selecting-journals","text":"A common type of query is one that generates a linear journal of entries (Ledger calls this a \u201cregister\u201d). This roughly corresponds to an account statement, but with our language, such a statement can be generated for any subset of postings. You can generate a journal with the following syntax: JOURNAL [AT ] [FROM \u2026] The regular expression account-regexp is used to select which subset of accounts to generate a journal for. The optional \u201cAT \u201d clause is used to specify an aggregation function for the amounts rendered (typically UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. Here is an example journal-generating query: JOURNAL \"Invest\" AT COST FROM HAS_ACCOUNT(\"Assets:US\");","title":"Selecting Journals"},{"location":"beancount_query_language.html#selecting-balances","text":"The other most common type of report is a table of the balances of various accounts at a particular date. This can be viewed as a SELECT query aggregating positions grouping by account. You can generate a balances report with the following syntax: BALANCES [AT ] [FROM \u2026] The optional \u201cAT \u201d clause is used to specify an aggregation function for the balances rendered (usually UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. To generate your balances at a particular date, close your set of entries using the \u201c FROM\u2026 CLOSE ON \u201d form described above. Observe that typical balance sheets and income statements seen in an accounting context are subsets of tables of balances such as reported by this query. An income statement reports on just the transactions that appears during a period of time, and a balance sheet summarizes transactions before its reporting before and clears the income & expenses accumulated during the period to an equity account. Then some minor reformatting is carried out. Please consult the introduction document on double-entry bookkeeping for more details, and the section above that discusses the \u201copen\u201d, \u201cclose\u201d and \u201cclear\u201d operations. We will also be providing a separate text processing tool that can accept balance reports and reformat them in a two-column format similar to that you would see balance sheets and income statements.","title":"Selecting Balances"},{"location":"beancount_query_language.html#print","text":"It can be useful to generate output in Beancount format, so that subsets of transactions can be saved to files, for example. The shell provides that ability via the PRINT command: PRINT [FROM \u2026] The FROM clause obeys the usual semantics as described elsewhere in this document. The resulting filtered stream of Beancount entries is then printed out on the output in Beancount syntax. In particular, just running the \u201c PRINT \u201d command will spit out the parsed and loaded contents of a Beancount file. You can use this for troubleshooting if needed, or to expand transactions generated from a plugin you may be in the process of developing.","title":"Print"},{"location":"beancount_query_language.html#debugging-explain","text":"If you\u2019re having trouble getting a particular statement to compile and run,. you can prefix any query statement with the EXPLAIN modifier, e.g.: EXPLAIN SELECT \u2026 This will not run the statement, but rather print out the intermediate AST and compiled representation as well as the list of computed statements. This can be useful to report bugs on the mailing-list. Also, this shows you the translated form of the JOURNAL and BALANCES statements.","title":"Debugging / Explain"},{"location":"beancount_query_language.html#future-features","text":"The following list of features were planned for the first release but I\u2019ve decided to make a first cut without them. I\u2019ll be adding those during a revision.","title":"Future Features"},{"location":"beancount_query_language.html#flattening-inventories","text":"If you provide the FLATTEN option after a query, it tells the query engine to flatten inventories with multiple lots into separate rows for each lot. For example, if you have an inventory balance with the following contents: 3 AAPL {102.34 USD} 4 AAPL {104.53 USD} 5 AAPL {106.23 USD} Using the following query: SELECT account, sum(position) GROUP BY account; It should return a single row of results, rendered over three lines. However, adding the option: SELECT account, sum(position) GROUP BY account FLATTEN; This should return three separate rows, with all the selected attributes, as if there were that many postings.","title":"Flattening Inventories"},{"location":"beancount_query_language.html#sub-selects","text":"The ability to select from the result of another SELECT is not currently supported, but the internals of the query code are prepared to do so.","title":"Sub-Selects"},{"location":"beancount_query_language.html#more-information","text":"This document attempts to provide a good high-level summary of the features supported in our query language. However, should you find you need more information, you may take a look at the original proposal , or consult the source code under the beancount.query directory. In particular, the parser will provide insight into the specifics of the syntax, and the environments will shed some light on the supported data columns and functions. Feel free to rummage in the source code and ask questions on the mailing-list.","title":"More Information"},{"location":"beancount_query_language.html#appendix","text":"","title":"Appendix"},{"location":"beancount_query_language.html#future-features_1","text":"This section documents ideas for features to be implemented in a future version.","title":"Future Features"},{"location":"beancount_query_language.html#pivot-by","text":"A special PIVOT BY clause will eventually be usable to convert the output from a one-dimensional list of results to a two-dimensional table. For example, the following query: SELECT account, YEAR(date) AS year, SUM(COST(position)) AS balance WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1,2; Might generate the following table of results: account year balance ------------------------ ---- ----------- Expenses:Food:Alcohol 2012 57.91 USD Expenses:Food:Alcohol 2013 33.45 USD Expenses:Food:Coffee 2012 42.07 USD Expenses:Food:Coffee 2013 124.69 USD Expenses:Food:Coffee 2014 38.74 USD Expenses:Food:Groceries 2012 2172.97 USD Expenses:Food:Groceries 2013 2161.90 USD Expenses:Food:Groceries 2014 2072.36 USD Expenses:Food:Restaurant 2012 4310.60 USD Expenses:Food:Restaurant 2013 5053.61 USD Expenses:Food:Restaurant 2014 4209.06 USD If you add a PIVOT clause to the query, like this: \u2026 PIVOT BY account, year; You would get a table like this: account/year 2012 2013 2014 ------------------------ ----------- ----------- ----------- Expenses:Food:Alcohol 57.91 USD 33.45 USD Expenses:Food:Coffee 42.07 USD 124.69 USD 38.74 USD Expenses:Food:Groceries 2172.97 USD 2161.90 USD 2072.36 USD Expenses:Food:Restaurant 4310.60 USD 5053.61 USD 4209.06 USD If your table has more than three columns, the non-pivoted column would get expanded horizontally, like this: SELECT account, YEAR(date), SUM(COST(position)) AS balance, LAST(date) AS updated WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1, 2; You would get a table like this: account year balance updated ------------------------ ---- ----------- ---------- Expenses:Food:Alcohol 2012 57.91 USD 2012-07-17 Expenses:Food:Alcohol 2013 33.45 USD 2013-12-13 Expenses:Food:Coffee 2012 42.07 USD 2012-07-19 Expenses:Food:Coffee 2013 124.69 USD 2013-12-16 Expenses:Food:Coffee 2014 38.74 USD 2014-09-21 Expenses:Food:Groceries 2012 2172.97 USD 2012-12-30 Expenses:Food:Groceries 2013 2161.90 USD 2013-12-31 Expenses:Food:Groceries 2014 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 2012 4310.60 USD 2012-12-30 Expenses:Food:Restaurant 2013 5053.61 USD 2013-12-29 Expenses:Food:Restaurant 2014 4209.06 USD 2014-11-28 Pivoting, this would generate this table: account/balance,updated 2012/balanc 2012/updat 2013/balanc 2013/updat 2014/balanc 2014/updat ------------------------ ----------- ---------- ----------- ---------- ----------- ---------- Expenses:Food:Alcohol 57.91 USD 2012-07-17 33.45 USD 2013-12-13 Expenses:Food:Coffee 42.07 USD 2012-07-19 124.69 USD 2013-12-16 38.74 USD 2014-09-21 Expenses:Food:Groceries 2172.97 USD 2012-12-30 2161.90 USD 2013-12-31 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 4310.60 USD 2012-12-30 5053.61 USD 2013-12-29 4209.06 USD 2014-11-28","title":"Pivot By"},{"location":"beancount_scripting_plugins.html","text":"Beancount Scripting & Plugins \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/scripting Introduction Load Pipeline Writing Plug-ins Writing Scripts Loading from File Loading from String Printing Errors Printing Entries & Round-Tripping Going Further Introduction \uf0c1 This document provides examples and guidelines on how to write scripts that use the contents of your ledger. It also provides information on how to write your own \u201cplugins,\u201d which are just Python functions that you can configure to transform your transactions or synthesize ones programmatically. These are the main two methods for extending Beancount\u2019s features and for writing your own custom reports. You simply use Python to do this. Load Pipeline \uf0c1 You need to know a little bit about how Beancount processes its input files. Internally, the single point of entry to load an input file is the beancount.loader.load_file() function, which accepts an input file and carries out a list of transformation steps, as in this diagram: The stages of loading are as follows: Parser. Run the input file through the parser. The output of this stage is entries: A list of tuples (defined in beancount.core.data) corresponding to each directive exactly as it appeared in the file, and sorted by date and line number. Moreover, Transaction directives that occur on the same date as other directives are always guaranteed to be sorted after them. This prepares the entries for processing. This list of entries will get transformed and refined by the various subsequent stages. options_map: A Python dict of the option values from the input file. See beancount.parser.options for details. Once created, this will never be modified thereafter. errors: A list of error objects, if any occurred. At every stage, new errors generated are collected. Process plugins. For each plugin, load the plugin module and call its functions with the list of entries and the options_map from the previous stage, replacing the current list by the ones returned by the plugin. This effectively allows the plugin to filter the entries. The list of plugins to run is composed of a set of default plugin modules that implement some of the built-in features of Beancount, followed by the list provided by the user from the \u201cplugin\u201d options in the input file. Validation. Run the resulting entries through a validation stage, to ensure that directives synthesized or modified by the plugins conform to some invariants that the codebase depends on. This mainly exists to generate errors. The list of entries generated by this pipeline are of the various types defined in beancount.core.data , and in a typical input file, most of them will be of type Transaction . Beancount\u2019s own filtering and reporting programs directly process those, and so can you too. These entries are dumb read-only objects (Python namedtuples ) and have no methods that modify their contents explicitly. All processing within Beancount is performed functional-style by processing lists of entries that are assumed immutable 1 . The list of user plugins to run is part of the load stage because that allows programs that monitor the file for changes to reload it and reapply the same list of plugins. It also allows the author of the input file to selectively enable various optional features that way. Writing Plug-ins \uf0c1 As you saw in the previous section, loading a Beancount file essentially produces a list of directives. Many syntax extensions can be carried out by transforming the list of directives into a new list in the plug-ins processing stage. Here are some examples of transformations that you might want to carry out on some of the directives: Add some postings automatically Link some transactions with a common tag Synthesize new transactions Remove or replace some sets of transactions Modify the various fields There is no limit to what you can do, as long as the entries your plugin produces fulfill certain constraints (all postings balance, all data types are as expected). A plugin is added to the input file via the option syntax , for example, like this: plugin \"accounting.wash_sales\" With this directive, the loader will attempt to import the accounting.wash_sales Python module (the code must be Python-3.3 or above), look for a special __plugins__ attribute which should be a sequence of functions to run, and then run those functions. For running the plugins, see the Executing Plugins section below. As an example, you would place code like this in a \u201c accounting/wash_sales.py \u201d file: __plugins__ = ['wash_sales'] def wash_sales(entries, options_map): errors = [] for entry in entries: print(type(entry)) return entries, errors This is a minimal example which does not modify the entries and prints them on the console. In practice, to do something useful, you would modify some of the entries in the list and output them. You then invoke the usual tools provided by Beancount on your input file. The various filters and reports will then operate on the list of entries output by your plugin. Refer to the source code in beancount.core for details and examples of how to manipulate entries. Plugin Configuration \uf0c1 Some plugins will require configuration. In order to provide a plugin some data specific to your file, you can provide a configuration string: plugin \"accounting.wash_sales\" \"days=31\" The plugin function will then receive an extra parameter, the configuration string. It is up to the plugin itself to define how it gets interpreted. Writing Scripts \uf0c1 If you need to produce some custom analysis or visualization that cannot be achieved using the built-in filtering and reporting capabilities, you can just write a script that loads the directives explicitly. This gives you control over the flow of the program and you can do anything you want. Loading from File \uf0c1 You can simply call the beancount.loader.load_file() loader function yourself. Here is an example minimal script: #!/usr/bin/env python3 from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) \u2026 At this point you can process the entries as you like, print them out, generate HTML, call out to Python libraries, etc. (I recommend that you use best programming practices and use docstrings on your script and a main function; the script above is meant to be minimal). Once again, refer to the source code in beancount.core for details and examples of how to manipulate entries. Loading from String \uf0c1 You can also parse a string directly. Use beancount.loader.load_string() : #!/usr/bin/env python3 from beancount import loader entries, errors, options = loader.load_string(\"\"\" 2014-02-02 open Assets:TestAccount USD \u2026 \"\"\") The stdlib textwrap.dedent function comes in handy if you want to indent the Beancount directives and have it automatically remove indentation. For a source of many examples, see the various tests in the Beancount source code. Printing Errors \uf0c1 By default, the loader will not print any errors upon loading; we prefer loading not to have any side-effect by default. You can provide an optional argument to print errors, which is the function to call to write error strings: #!/usr/bin/env python3 import sys from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename, log_errors=sys.stderr) \u2026 Or if you prefer to do it yourself explicitly, you can call the beancount.parser.printer.print_errors() helper function: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) printer.print_errors(errors) \u2026 Printing Entries & Round-Tripping \uf0c1 Printing namedtuple entries directly will output some readable though relatively poorly formatted output. It\u2019s best to use the beancount.parser.printer.print_entry() utility function to print out an entry in a readable way: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) for entry in entries: printer.print_entry(entry) In particular, Beancount offers the guarantee that the output of the printer should always be parseable and should result in the same data structure when read back in. (It should be considered a bug if that is not the case.) See the beancount.parser.printer module source code for more utility functions. Executing Plugins \uf0c1 All that is required for the plug-in module to be found, is that it must be present in your PYTHONPATH environment variable (you need to make sure that the relevant __init__.py files exist for import). It can live in your own code: you don\u2019t have to modify Beancount itself. There is also an option, which can be added to your beancount file: option \"insert_pythonpath\" \"True\" This will add the folder which contains the beancount file to the PYTHONPATH . The result is that you can place the plugins along the beancount file and have them execute when you use this file. Here is a brief example, using the wash_sales.py plugin we wrote above. Your beancount file would include the following lines: option \"insert_pythonpath\" \"True\" plugin \"wash_sales\" The Python file wash_sales.py would be stored in the same folder as the .beancount file. Going Further \uf0c1 To understand how to manipulate entries, you should refer to the source code, and probably learn more about the following modules: beancount.core.data beancount.core.account beancount.core.number beancount.core.amount beancount.core.position beancount.core.inventory Refer to the Design Doc for more details. Enjoy! Technically, Python does not prevent the modifications of namedtuple attributes that are themselves mutable such as lists and sets, but in practice, by convention, once an entry is created we never modify it in any way. Avoiding side-effects and using a functional style provides benefits in any language. \u21a9","title":"Beancount Scripting Plugins"},{"location":"beancount_scripting_plugins.html#beancount-scripting-plugins","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/scripting Introduction Load Pipeline Writing Plug-ins Writing Scripts Loading from File Loading from String Printing Errors Printing Entries & Round-Tripping Going Further","title":"Beancount Scripting & Plugins"},{"location":"beancount_scripting_plugins.html#introduction","text":"This document provides examples and guidelines on how to write scripts that use the contents of your ledger. It also provides information on how to write your own \u201cplugins,\u201d which are just Python functions that you can configure to transform your transactions or synthesize ones programmatically. These are the main two methods for extending Beancount\u2019s features and for writing your own custom reports. You simply use Python to do this.","title":"Introduction"},{"location":"beancount_scripting_plugins.html#load-pipeline","text":"You need to know a little bit about how Beancount processes its input files. Internally, the single point of entry to load an input file is the beancount.loader.load_file() function, which accepts an input file and carries out a list of transformation steps, as in this diagram: The stages of loading are as follows: Parser. Run the input file through the parser. The output of this stage is entries: A list of tuples (defined in beancount.core.data) corresponding to each directive exactly as it appeared in the file, and sorted by date and line number. Moreover, Transaction directives that occur on the same date as other directives are always guaranteed to be sorted after them. This prepares the entries for processing. This list of entries will get transformed and refined by the various subsequent stages. options_map: A Python dict of the option values from the input file. See beancount.parser.options for details. Once created, this will never be modified thereafter. errors: A list of error objects, if any occurred. At every stage, new errors generated are collected. Process plugins. For each plugin, load the plugin module and call its functions with the list of entries and the options_map from the previous stage, replacing the current list by the ones returned by the plugin. This effectively allows the plugin to filter the entries. The list of plugins to run is composed of a set of default plugin modules that implement some of the built-in features of Beancount, followed by the list provided by the user from the \u201cplugin\u201d options in the input file. Validation. Run the resulting entries through a validation stage, to ensure that directives synthesized or modified by the plugins conform to some invariants that the codebase depends on. This mainly exists to generate errors. The list of entries generated by this pipeline are of the various types defined in beancount.core.data , and in a typical input file, most of them will be of type Transaction . Beancount\u2019s own filtering and reporting programs directly process those, and so can you too. These entries are dumb read-only objects (Python namedtuples ) and have no methods that modify their contents explicitly. All processing within Beancount is performed functional-style by processing lists of entries that are assumed immutable 1 . The list of user plugins to run is part of the load stage because that allows programs that monitor the file for changes to reload it and reapply the same list of plugins. It also allows the author of the input file to selectively enable various optional features that way.","title":"Load Pipeline"},{"location":"beancount_scripting_plugins.html#writing-plug-ins","text":"As you saw in the previous section, loading a Beancount file essentially produces a list of directives. Many syntax extensions can be carried out by transforming the list of directives into a new list in the plug-ins processing stage. Here are some examples of transformations that you might want to carry out on some of the directives: Add some postings automatically Link some transactions with a common tag Synthesize new transactions Remove or replace some sets of transactions Modify the various fields There is no limit to what you can do, as long as the entries your plugin produces fulfill certain constraints (all postings balance, all data types are as expected). A plugin is added to the input file via the option syntax , for example, like this: plugin \"accounting.wash_sales\" With this directive, the loader will attempt to import the accounting.wash_sales Python module (the code must be Python-3.3 or above), look for a special __plugins__ attribute which should be a sequence of functions to run, and then run those functions. For running the plugins, see the Executing Plugins section below. As an example, you would place code like this in a \u201c accounting/wash_sales.py \u201d file: __plugins__ = ['wash_sales'] def wash_sales(entries, options_map): errors = [] for entry in entries: print(type(entry)) return entries, errors This is a minimal example which does not modify the entries and prints them on the console. In practice, to do something useful, you would modify some of the entries in the list and output them. You then invoke the usual tools provided by Beancount on your input file. The various filters and reports will then operate on the list of entries output by your plugin. Refer to the source code in beancount.core for details and examples of how to manipulate entries.","title":"Writing Plug-ins"},{"location":"beancount_scripting_plugins.html#plugin-configuration","text":"Some plugins will require configuration. In order to provide a plugin some data specific to your file, you can provide a configuration string: plugin \"accounting.wash_sales\" \"days=31\" The plugin function will then receive an extra parameter, the configuration string. It is up to the plugin itself to define how it gets interpreted.","title":"Plugin Configuration"},{"location":"beancount_scripting_plugins.html#writing-scripts","text":"If you need to produce some custom analysis or visualization that cannot be achieved using the built-in filtering and reporting capabilities, you can just write a script that loads the directives explicitly. This gives you control over the flow of the program and you can do anything you want.","title":"Writing Scripts"},{"location":"beancount_scripting_plugins.html#loading-from-file","text":"You can simply call the beancount.loader.load_file() loader function yourself. Here is an example minimal script: #!/usr/bin/env python3 from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) \u2026 At this point you can process the entries as you like, print them out, generate HTML, call out to Python libraries, etc. (I recommend that you use best programming practices and use docstrings on your script and a main function; the script above is meant to be minimal). Once again, refer to the source code in beancount.core for details and examples of how to manipulate entries.","title":"Loading from File"},{"location":"beancount_scripting_plugins.html#loading-from-string","text":"You can also parse a string directly. Use beancount.loader.load_string() : #!/usr/bin/env python3 from beancount import loader entries, errors, options = loader.load_string(\"\"\" 2014-02-02 open Assets:TestAccount USD \u2026 \"\"\") The stdlib textwrap.dedent function comes in handy if you want to indent the Beancount directives and have it automatically remove indentation. For a source of many examples, see the various tests in the Beancount source code.","title":"Loading from String"},{"location":"beancount_scripting_plugins.html#printing-errors","text":"By default, the loader will not print any errors upon loading; we prefer loading not to have any side-effect by default. You can provide an optional argument to print errors, which is the function to call to write error strings: #!/usr/bin/env python3 import sys from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename, log_errors=sys.stderr) \u2026 Or if you prefer to do it yourself explicitly, you can call the beancount.parser.printer.print_errors() helper function: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) printer.print_errors(errors) \u2026","title":"Printing Errors"},{"location":"beancount_scripting_plugins.html#printing-entries-round-tripping","text":"Printing namedtuple entries directly will output some readable though relatively poorly formatted output. It\u2019s best to use the beancount.parser.printer.print_entry() utility function to print out an entry in a readable way: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) for entry in entries: printer.print_entry(entry) In particular, Beancount offers the guarantee that the output of the printer should always be parseable and should result in the same data structure when read back in. (It should be considered a bug if that is not the case.) See the beancount.parser.printer module source code for more utility functions.","title":"Printing Entries & Round-Tripping"},{"location":"beancount_scripting_plugins.html#executing-plugins","text":"All that is required for the plug-in module to be found, is that it must be present in your PYTHONPATH environment variable (you need to make sure that the relevant __init__.py files exist for import). It can live in your own code: you don\u2019t have to modify Beancount itself. There is also an option, which can be added to your beancount file: option \"insert_pythonpath\" \"True\" This will add the folder which contains the beancount file to the PYTHONPATH . The result is that you can place the plugins along the beancount file and have them execute when you use this file. Here is a brief example, using the wash_sales.py plugin we wrote above. Your beancount file would include the following lines: option \"insert_pythonpath\" \"True\" plugin \"wash_sales\" The Python file wash_sales.py would be stored in the same folder as the .beancount file.","title":"Executing Plugins"},{"location":"beancount_scripting_plugins.html#going-further","text":"To understand how to manipulate entries, you should refer to the source code, and probably learn more about the following modules: beancount.core.data beancount.core.account beancount.core.number beancount.core.amount beancount.core.position beancount.core.inventory Refer to the Design Doc for more details. Enjoy! Technically, Python does not prevent the modifications of namedtuple attributes that are themselves mutable such as lists and sets, but in practice, by convention, once an entry is created we never modify it in any way. Avoiding side-effects and using a functional style provides benefits in any language. \u21a9","title":"Going Further"},{"location":"beancount_v3.html","text":"Beancount Vnext: Goals & Design \uf0c1 Martin Blais , July 2020 http://furius.ca/beancount/doc/Vnext Motivation \uf0c1 It's time to give Beancount a refresh and to put down a concrete plan for what the next iteration of it ought to be. I've had these thoughts in the back of my mind for a long while\u2014at least a year\u2014and I'm putting these in writing partly to share a vision of what the product we all use to organize our finances can become, partly to solicit feedback, and partly to organize my thoughts and to prioritize well on the stuff that matters. Current status. The current state of Beancount is that development has been static for a while now, for a number of reasons. The software is in a state that's far from perfect (and I'll be enumerating the main problems in this document) but I've been resisting making too many changes in order to provide myself and others a really stable base to work from. More importantly, while I used to be able to spend a significant amount of weekend time on its development, life changes and a focus on my career of late has made it difficult for me to justify or find the extra time (it has been 10 years after all). A multitude of ideas have aged to this TODO file but it's too detailed to grok and a bit of a dump, this document should be more useful. Why rewrites happen. When I wrote version 2 of Beancount (a full rewrite of the first version), it was because of a confluence of ideas for improving my first draft; I resisted for a while, but eventually it made so much sense to me that it became simply impossible not to write it. Many of the ideas driving the redesign at the time are still axioms in today's design: removing order dependence, normalizing the syntax to be well-defined with a BNF grammar, converting custom processing to a sequence of plugins off of a simple stream of directives, the current design of booking selection and how cost basis works, and all the directives beyond \"Transaction\". These ideas largely shape what a lot of people like about using Beancount today. Goals. Now is the time for yet another wave of evolution for Beancount, and similarly, a set of new ideas I'm going to lay down in this document form as potent a change as the v1 to v2 transition. The vision I have for Vnext will simplify Beancount, by factoring into simpler, more isolated, more reusable, better defined parts, and not merely by adding new features on top of what's there. In many ways, Vnext will be a distillation of the current system. It will also make space to finally implement some of the core features most often desired by users. And those changes will enhance some organizational aspects: allow for more contributions, and also trim down the part that I'm handling myself to less code, so I can more effectively focus on just the core features. Current Problems \uf0c1 Performance \uf0c1 My personal ledger, and I know that the ledgers of many users, are simply too large to process instantly. My current file takes 6 seconds on the souped-up NUC I use for a desktop at home\u2014but that's just too long. I'm really quite attached to the idea of processing the entire set of inputs every time, instead of forcing users to cut-up their ledgers into multiple files with \"closing\" transitions at arbitrary points in the year, but I really do want that \"instant\" feeling that you get when you run two-letter UNIX programs, that it runs in well under half a second . It makes it a lot more interactive and fun to use. C++ rewrite. One of the reasons for the slow performance right now is the fact that Beancount is implemented in Python, even at the level of the parser (C code calling back into a Python driver). An obvious solution is to rewrite the core of the software in a language closer to the metal, and that will be C++. I'm selecting C++ for its control and because the current slate of tools around it is mature and widespread enough that it should be easy for most to build without too many problems, and I can leverage C libraries that I will need. Using a functional language could have been fun but many of the libraries I want simply would not be available or it would be too difficult for mere mortals to build. Simple, portable C++. It's important to mention that the C++ code I have in mind is not in the style of template-heavy modern C++ code you'd find in something like Boost. Rather, it's a lot more like the conservative \"almost C without exceptions\" subset of C++ that Google uses , with a base on Abseil-Cpp (for example and flavor, see tips ). The reasons for this are stability and portability, and while this rewrite is for faster performance, I believe that it will not be necessary to pull template tricks to make it run fast enough; just a straightforward port to avoid the Python runtime will likely be sufficient. Above all I want to keep the new code simple and \"functional-ish\" as much as possible (no classes if I can avoid it), relying on a trusted set of stable dependencies , built hermetically using the Bazel build tool. Python API. It's also important that the Python API remains for plugins and scripts, and that the full suite of unit tests be carried over to the newer version of the code. After all, the ability to write custom scripts using all that personal finance data is one of the most attractive features of the text-based approach. Code beyond the new core implementation will remain in Python, and existing code built on top of the Python API should be very easily portable to Vnext. This can be achieved by exposing the directives with wrappers written in pybind11. Other languages. The resolved output of the Beancount core will be a stream of protocol buffer objects, so processing from other languages (e.g., Go) will have first-class support. Processing model. An additional algorithmic improvement to performance, should it be necessary, would be to define plugins processing in terms of iterator functions that cascade and interleave the processing of the directive stream without making entirely disjoint passes over the full list of directives. While the freedom to have each plugin process all the directives on their own has been instrumental in keeping the system free of synchronized application state and has allowed me to isolate behavior of the plugins from each other, there are opportunities to join together multiple transformations in a single pass. Fewer passes = faster. Intermediate Parsed Data vs. Final List of Directives \uf0c1 In Beancount v2, I was never too careful to clearly distinguish between The list of directives coming out of the parser , missing interpolation and booking, using position.CostSpec instead of position.Cost on each lot, and The resolved and booked list of directives with booking algorithms applied to select matching lots, and interpolated values filled in, as well as transformations having been applied by the various plugins. These two lists of directives are really quite distinct in purpose, though they share many common data structures, and for the most part, the first list appears mostly in the parser module. There have been cases where it was confusing, even to me, which of the lists I was manipulating. Part of the reason is due to how I'm using mostly the same Python data structures for both, that allow me to bend the rules on typing. Perhaps more importantly is that because plugins run after booking and interpolation, and are required to put out fully interpolated and booked transactions, a plugin that wants to extend transactions that would run as invalid in the input syntax is difficult. See #541 for an example. The next iteration will see both the intermediate parser production and final resolved list of directives implemented as protocol buffer messages, with strictly distinct data types. This will replace beancount.core.data . The distinction between these two streams will be made very clear, and I will try to hide the former as much as possible. The goal is to avoid plugin writers to ever even see the intermediate list. It should become a hidden detail of the implementation of the core. Furthermore, there may be two types of plugins: a plugin that runs on the uninterpolated, unbooked output of the parser, and a plugin that runs on the resolved and booked stream. This would allow more creative use of partial input that might be invalid under the limitations of interpolation and booking. Updates: We could convert the plugin system to one that runs at booking/interpolation time. We should attempt to make the booking/interpolation atomic, in that one could write a Python loop with an accumulator and invoke it independently, so that in theory, booking could be used in the importers (like I do for Ameritrade). Make Rewriting the Input First Class \uf0c1 Added in Dec 2020 after comments and #586 . A number of frequently asked questions have to do with how to process the input data itself. Usually, a new user will attempt to load the contents of the ledger, modify the data structures, and print to update their file, not realizing that the printer includes all the interpolations, booking data, and modifications from plugins, so this cannot work. However, since we're rewriting the parser and ensuring a clean separation between intermediate ASI-like data and processed and finalized directives, we can implement a special printer for the AST intermediate data, so that users could run just the parser, modify the intermediate directives, and print them back out, perhaps losing just some of the formatting and whitespace. This formatting loss can be leveraged to reimplement bean-format more naturally: the output of that printer should always be formatted neatly. This would avoid users having to write ad-hoc parsers on their input file, sed-like conversions, and so on. They could do it properly by modifying the data structure instead. What's more, in order for this to work accurately, we'd have to delay processing of the arithmetic operations post-parsing, so that we can render them back out. This offers another advantage: if we process the calculations after parsing, we can afford to provide an option to let the user specify the precision configuration to use for mpdecimal. I really like that idea, because it avoids hard-coding calculation precision and better defines the outcome of these options, potentially opening the door to a more rational way to remove extra digits that often get rendered out. Finally, if a nice library function can be made to process transactions in-place and output them back out, preserving all comments around them, this can become another way\u2014perhaps the preferential way\u2014for us to clean payees and somesuch. At the moment, the solution is to write a plugin that will clean up the data, but the input file remains a bit of a mess. Making it easy to automatically clean up the input file is an appealing alternative and potentially will add an important new dimension to the Beancount workflow. I want to make all changes necessary to make this possible and well supported (I'm imagining my ledger file all cleaned up right now and it's appealing). I think it's not very much work, it involves: Storing begin/end line information on everything. Adding AST constructs for representing arithmetic calculations. Adding comments parsing to the renderer. Implementing a new renderer that can reproduce the AST, including handling missing data. Implementing a library to make modification of a file in-place as easy as writing plugins, while preserving all non-directive data in the file as is. Contributions \uf0c1 For most of the development of Beancount, I've been pretty reluctant to accept contributions. It has been a closely held pet project of mine since it has so much impact on my personal financial arrangements and I dread unplanned breakage. The main reservations I've had over contributions are two-fold: Not enough testing. Proposed changes that did not include enough testing, or none at all, sometimes even the kind testing that would prevent basic breakage. When I'd accept some proposals and commit to writing the tests myself it could sometimes take me down the rabbit hole for hours (if not days). This wasn't practical. Cascading design impact. Some of the proposals did not take into account broader design considerations that would affect other parts of the code, which I may not have communicated or documented well. I've had to reject some ideas in the interest of keeping things coherent and \"tight.\" Part of this is my fault: putting a high bar on contributions hasn't allowed potential contributors to acquire enough context and familiarity with the codebase to make changes compatible with its design. Allowing more contributions. Starting in Vnext I'd like to restructure the project so that more people are able to get involved directly, and to closely work 1-on-1 with some contributors on particular features. Clearly Beancount benefits from direct input from more people. The recent move to GitHub only compounds the urgency for making that easier. To this effect, I'd like to implement a few strategies: Break down the code in parts. This is a natural evolution that has been seen in many other projects; I'm no Linus nor Guido, but like their respective projects, as they grew in popularity, the original authors narrowed their focus on the core part and let other people expand on adjacent but also important functionality. I'd like to focus more of my time only on core functionality that will impact things like support for settlement dates, currency accounts, split transactions, trade reporting, etc. Letting other people deal with adding or updating price sources, making improvements to the ingestion framework, and making beautiful renderings and presentations of the data would be ideal. In time, I may eventually break down these libraries to separate repositories with looser contribution guidelines and/or add ACLs for others to push directly to those repos. Acquire \"lieutenants.\" I need to leave more space for trusted and frequent contributors to chip in more liberally. For example, Martin Michlmayr now has direct edit access to most documents and has been making numerous helpful contributions and updates to the docs. Kirill Goncharov's conversion of the documentation out of Google Docs is simply beautiful. RedStreet and many others are regularly pitching in answers on the mailing-list. Stefano Zacchiroli and Martin have built a standalone conversion tool from Ledger. Daniele Nicolodi is proposing some low-level changes to the scanner and parser. And of course, Dominik Aumayr and Jakob Schnitzer continue developing the Fava project adjacent to Beancount. There are many more people, there is a slowly-but-surely growing list of familiar recurring names. The question in my mind is: Is there a way to communicate with regular faces so that we're aligned in terms of design and can coordinate our efforts in the same direction? Does the newly acquired familiarity with video-conference meetings (thanks to the coronavirus crisis) afford us a new channel for coordination that wasn't there before? The Python community has thrived in no small part due to the annual face-to-face interactions between its participants and a number of core developers being in the same place for some time. Can we achieve the same thing online, as we are a smaller community? If so, better coordination may make it easier to accept proposed changes. I wonder if I should propose a monthly \"team\" meeting. 20% projects. I should provide a list of \"20% projects\" that are well aligned with the direction of the project for others to take up if they want to, and add profuse guidance and detail of downstream side-effects from those proposed features. The idea is to make it possible for newcomers to contribute changes that are likely to fit well and be easily integrated and accepted on the codebase.: Proposals. Beancount's equivalents of Python's \" PEPs \" are essentially the Google Docs proposal documents I started from threads where many others comment and add suggestions. A central list of those should be shared to a folder, identified as such, and allow others to write similar proposals. Maybe a little more structure and discipline around those would be useful. Restructuring the Code \uf0c1 At the very coarse level, the code restructuring for Vnext looks like this: C++ core, parser, and built-in plugins. The Beancount core, parser, booking algorithm and plugins get rewritten in simple C++, outputting its parsed and booked contents as a stream of protobuf objects. Query engine. Beancount query/SQL gets forked to a separate project operating on arbitrary data schemas, with a much broader scope than Beancount. See section below. The rest. Most of the rest gets cut up into separate projects or at least, at first, in a distinct place within the repository (until the core is rewritten). Note that because the core outputs the stream of directives as proto objects, any language supported by protobufs should be able to read those. This extends the reach of Beancount. Here's a simplified diagram showing how this might look: Here is a detailed breakdown of the various parts of the codebase today and what I think will happen to them: Core. This is the part of Beancount's code which will get rewritten in C++ and output a sequence of messages to a stream of directives. I'll continue keeping a tight focus on that part with a conservative eye toward stability, but in Vnext will be adding desired new capabilities that have been lacking so far as described in the next section of this document. The core will include the following packages: beancount/core beancount/ops beancount/parser beancount/utils beancount/loader.py beancount/plugins (some, see below) beancount/utils (most) Query. The query language will be factored out into a completely separate repo with a broader application domain (and hooks for customizing for Beancount). I suspect that over time that project will acquire a much broader range of contributors, many of which will not even be Beancount users. This includes the code from these packages: beancount/query beancount/tools Prices. This is a simple library and tool that helps users fetch prices from external sources. This should definitely move to another repo and I'd welcome a new owner building a competing solution. People are sending me patches for new price sources and I have too little time to maintain them over time, as the upstream sources change or even disappear. This requires very little from Beancount itself (in theory you could just print() the directives for output, without even loading library code) but I think the Beancount core should include and functions to enumerate a list of required date/instrument pairs at a particular date from a given ledger (and I'm happy to support that). Note that the internal price database core will remain in the core, because it's needed there. The affected packages are: beancount/prices Improvements should be made to this library after it moves out of the Beancount repository: we should isolate the Beancount code to just a few modules, and turn the scope of this project to something larger than Beancount: it's three things, really: a) an up-to-date Python library of price fetchers with unit tests and maintained by the community (i.e., when sources break, we update the library) and a common API interface (needs to be improved from what's there TBH, the API should support fetching time series in a single call); b) an accompanying command-line tool (currency \"bean-price\") for fetching those prices from the command-line. This requires the specification of \"a price in a particular currency from a particular source\" as a string. I'd like to improve that spec to make the USD: prefix optional, and maybe eliminate the chain of prices in the spec, which hasn't found much use in practice and move that upstream. c) Make the interfaces to fetch ledger-related information (e.g., list of missing/required prices and lists of instruments) onto modules: beancount v2, beancount Vnext, ledger, hledger, and rendering output formats to any of these. In other words, this library should be able to fetch prices even if Beancount isn't installed. To turn this project into something that can run independent of beancount. Ingest. The importers library will probably move to another repo and eventually could even find another owner. I think the most interesting part of it has been the establishment of clear phases: the identify, extract and file tasks, and a regression testing framework which works on real input files checking against expected converted outputs, which has worked well to minimize the pain of upgrading importers when they break (as they do break regularly, is a SNAFU). In the past I've had to pull some tricks to make command-line tools provided by the project support an input configuration as Python code but also possible to integrate in a script; I will remove the generic programs and users will be required to turn their configuration itself into a script that will just provide subcommands when run; the change will be very easy for existing users: it will require only a single line-of-code at the bottom of their existing files. The Bazel build may add some minor difficulties in loading a Python extension module built from within a Bazel workspace from an otherwise machine-wide Python installation, but I'm confident we'll figure it out. I'd also be happy for someone else to eventually take ownership of this framework, as long as the basic functionality and API remains stable. The example csv and ofx importers should be removed from it and live in their own repos, perhaps: ofx. the OFX importer should be replaced by something using ofxtools (the one I built is pretty bad), and the CSV importer really needs a thorough rewrite with lots of unit testing for the large list of options it now supports (these tests are sorely missing). csv. Furthermore, I think the CSV importer could be enhanced to be smarter and more flexible, to automatically detect from the column headers and inferred data types in the files which column should convert into which field. I'm not going to do that (I don't have time). Someone with the urge to make the ultimate automatic CSV parser ought to create a separate repository for that. The affected packages are: beancount/ingest : could eventually move to another repo. beancount/ingest/importers: someone could revive a repository of importer implementations, like what LedgerHub once aimed to become, and swallow those codes. See this document for details on what's to happen with the ingestion code. Custom reports and bean-web should be removed: the underlying bottle library seems unmaintained at this point, Fava subsumes bean-web, and I never liked the custom reports code anyway (they're a pain to modify). I never use them myself anymore (other than through bean-web). I really think it's possible to replace those with filters on top enhanced SQL query results. The conversion to Ledger and HLedger from Beancount now seems largely useless, I'm not sure anyone's using those. I'll probably move these to another repo, where they would eventually rot, or if someone cares, adopt them and maintain or evolve them. beancount/web : will be deleted or moved to another repo. beancount/reports : will be deleted or moved to another repo. Note that this includes deprecating beancount/scripts/bake , which depends heavily on bean-web. I have no substitute for bean-bake, but I think I'd like to eventually build something better, a tool that would directly render a user-provided list of specific SQL queries to PDF files and collate them, something you can print. Jupyter notebook support. A replacement for the lightweight interface bean-web used to provide could be Jupyter Notebook integration of the query engine, so that users can run SQL queries from cells and have them rendered as tables, or perhaps a super light web application which only supports rendering general SQL queries to tables. Built-in Plugins. Beancount provides a list of internal plugins under beancount/plugins . It's not indicated clearly, but there have evolved two groups of plugins in there: stable plugins used by the core, and experimental plugins showcasing ideas, which are often incomplete implementations of something that was proposed from a thread on the mailing-list. The former group will be ported to C++, and the latter group should probably move to another location with much looser acceptance constraints. First, there are \"meta-plugins\" which only include groups of other plugins: Only one of those should remain, and maybe be enabled by default (making Beancount pedantic by default): auto pedantic The following plugins should remain in the core and be ported to C++: auto_accounts check_closing check_commodity close_tree commodity_attr check_average_cost coherent_cost currency_accounts implicit_prices leafonly noduplicates nounused onecommodity sellgains unique_prices The following are the experimental implementations of ideas that should move to a dedicated repo where other people can chip in other plugin implementations: book_conversions divert_expenses exclude_tag fill_account fix_payees forecast ira_contribs mark_unverified merge_meta split_expenses tag_pending unrealized Because it's a really common occurrence, the new transfer_lots plugin should be part of the built-in ones. Projects. The beancount/projects directory contains the export script and a project to produce data for a will. The will script will be moved outside the core of Beancount, I'm not sure anyone's using that. Maybe the new external plugins repo could include that script and other scripts I shared under /experimental. The export script should be grouped together with beancount/scripts/sql and other methods to send / share data outside of a ledger; these could remain in the core (I'm using the export script regularly to sync my aggregates and stock exposure to a Google Sheets doc which reflects intraday changes). Scripts. Some of the scripts are completely unrelated to Beancount, they are companions. The scrape validator. The sheets upload. The treeify tool. These should be moved elsewhere. One of the advantages of having all the code in the same repo is that it makes it possible to synchronize API changes across the entire codebase with a single commit. As such, I may keep some of the codes in the same repo until the new C++ core has stabilized, and properly separate them only when Vnext releases. Universal Lightweight Query Engine (ulque) \uf0c1 The SQL query engine for Beancount was initially a prototype but has grown to become the main way to get data out of it. I've been pretty liberal about adding functionality to it when needed and it's time to clean this up and consider a more polished solution. In Vnext, the query/SQL code gets eventually forked to a separate project (and repo) operating on arbitrary data schemas (via protobufs as a common description for various sources of data) and has support for Beancount integration. Imagine if you could automatically infer a schema from an arbitrary CSV file, and run operations on it, either as a Python library function or as a standalone tool. Furthermore, this tool will support sources and/or sinks to/from Google Sheets, XLS spreadsheets, containers of binary streams of serialized protos, tables from HTML web pages, PDF files, directories of files, and many more. This is going to be a data analysis tool with a scope closer to that of the Pandas library rather than an accounting-focused project, but also a universal converter tool, that will include the functionality of the upload-to-sheets script (which will get removed). One of the lessons from the SQL query engine in Beancount is that with just a little bit of post-processing (such as treeify ), we can do most of the operations in Beancount (journals, balance sheet & income statements) as queries with filters and aggregations. The tool will be made extensible in the ways required to add some of the idiosyncrasies required by Beancount, which are: Native support for a Decimal type . The addition of custom types for aggregators with the semantics of beancount.core.Inventory/Position/Amount . The ability to automatically generate a dynamic column rendering a line-by-line aggregation of another column (or set thereof), that is, a \"balance\" column . The ability to render a \" bottom line \" of aggregates at the end of the results table. Functions for splitting of aggregated columns , for amounts and inventories into multiple columns (e..g, \"123.00 USD\" becomes two columns: (123.00, \"USD\") to be processable in spreadsheets, and also for splitting debits and credits to their own columns. In particular, printing multiple lots accumulated in an account should be made natural from the SQL query, replacing the \"flatten\" feature by a more standard splitting off an array type. Moreover, broadening the focus with a new project definition will make a change to testing it thoroughly (the current one is still in a bit of a prototype stage and does not have nearly the amount of required tests), and also include data type validation (no more exceptions at runtime), by implementing a typed SQL translator. I'll document this elsewhere. This is a much bigger project, but I suspect with the broader scope, it will be easier to test and take on a life of its own. I'm preparing a design doc on this. API Rework \uf0c1 I write a lot of custom scripts, and there are a number of things that bother me about today's Beancount API, which I want to radically improve: Consolidate symbols under \"bn\". The internal API calls for importing the symbols from each package separately, but now that I'll have split off the ingestion and reporting code, all of the public API, or at least the majority of the commonly used objects in the core should be available from a single package, a bit like numpy: import beancount as bn \u2026 bn.Inventory(...) bn.Amount(...) bn.Transaction(...) # etc. I'd like for \"bn\" to become the de-facto two-letter import on top of which we write all the scripts. Default values in constructors. The namedtuple containers are mighty fine, but their constructors never had optional arguments, and it's always a bit of a dance to create those containers with a ton of \"None\" options. I never liked it. We'll make this tidy in the next iteration. No API documentation. While there is a substantial amount of documentation around the project, there is no documentation showing people how to use the Python API, e.g. how to accumulate balances, how to create and use a realization tree, how to pull information out of an accumulated inventory object, etc. I think that documenting some of the most common operations will go a long way towards empowering people to make the most out of Beancount. Some of these operations include: Accumulating lots of an inventory and printing them. Converting to market value, and making corresponding account adjustments. \u2026. add more \u2026 Exposed, usable booking. Booking will be a simple loop that can be invoked from Python with an entry and some accumulated state. Moreover, the Inventory object should begin to implement some of the lower-level operations required for booking, such that iterating over a set of postings and doing e.g., average booking, can be done via method calls on an Inventory object. Inventory should take a more prominent place in the API. Data types. Well defined data types should be provided for all objects to make liberal use of the typing module over all new code. Maybe create a module called \"bn.types\" but they should be available directly from \"bn.*\" so that there is a single short-named import. Terminology. I'd like to stop using \"entries\" and consolidate over the name \"directives\" in Vnext. Realization. I've been using a collections.defaultdict(Inventory) and a \"realization\" interchangeably. Both of these are mappings from an account name (or some other key) to an Inventory state object. I'd like to unify both of these constructs into the realization and make it into a commonly used object, with some helper methods. Parser Rewrite \uf0c1 Since we will now depend on C++, the parser will get to be rewritten. Worry not: the input syntax will remain the same or at least compatible with the existing v2 parser. What will change is: Unicode UTF-8 support. The lexer will get rewritten with RE/flex instead of GNU flex. This scanner generator supports Unicode natively and any of the input tokens will support UTF-8 syntax. This should include account names, an oft-requested feature. Flags. The current scanner limits our ability to support any flag and supports only a small list of them. I think the list has proved sufficient for use, but since I'll be putting some work into a new scanner I'm hoping to clean up that story and support a broader, better defined subset of single-letter flags for transactions. Time. The parser will parse and provide a time field, in addition to the date. The time may be used as an extra key in sorting directives. The details for this are yet to be determined, but this is requested often enough at the very minimum the parser will output it as metadata, and at best, it may become a first-class feature. Caching. The pickle cache will be removed. Until very recently , there weren't great options for disabling it (env vars) and I'd rather remove the only two environment variables that Beancount honors as a side-effect. Since the C++ code should be fast enough, hopefully a cache will not be needed. Tags & links. In practice, those two features occupy a very similar role as that of metadata (used to filter transactions). I'm contemplating unseating the special place taken by tags and links in the favor of turning those into metadata; the input syntax would not be removed, but instead the values would be merged into the metadata fields. I'm not 100% sure yet about doing this and open for discussion. Furthermore, the parser should be made to accept #tag and ^link where metadata is declared today, which would be convenient syntax. Finally, users have expressed a desire for tags on postings. We should contemplate that. Plugins configuration as protos. The options for the various plugins have been loosely defined as eval'ed Python code. This is pretty loose and doesn't provide a great opportunity for plugins to do validation nor document their expected inputs. I'd like to formalize plugin configuration syntax a bit, by supporting text-formatted protos in the input syntax (for a message type which would be provided by the plugins themselves). Parser in C++. The parser will be rewritten in C++. In the process of writing Vnext, I'll try to maintain a single grammar for both for as long as possible by calling out to a C++ driver interface, which will have two distinct implementations: one for the V2 version calling into Python, and one for the Vnext parser generating protos. In the process I may be porting the lexer and grammar Python implementation to C, as discussed in this ticket . Better includes. Current includes fail to recognize options that aren't in the top-level file. This caused many surprises in the past and should be fixed. At the minimum, an error should be raised. Code Quality Improvements \uf0c1 Rename \"augmentation\" and \"reduction\" to \"opening\" and \"closing\" everywhere. This is just more common terminology and will be more familiar and understandable to people outside of our context. Type annotations. The use of mypy or pytype with type annotations in Python 3 is by now a very common sight, and works quite well. As part of Vnext, all of the core libraries will be modified to include type annotations and the build should be running pytype automatically. I'll need to add this to our Bazel rules (Google doesn't currently provide external support for this). While doing this, I may relax some of the Args/Returns documentation convention, because in many cases (but not all) the type annotations are sufficient to get a good interpretation of a function's API. PyLint in build. Similarly, the linter should be run as an integral part of the build. I'd like to find a way to selectively and explicitly have to disable it during development, but otherwise be set up such that lint errors would be equivalent to build failures. Flexible constructors for Python API. The types generated by collections.namedtuple() or typing.NamedTuple don't offer flexible constructors with named parameters. I think all codes that create transaction objects today would benefit from having constructors with default values, and I'll be providing those to create corresponding proto objects. Tolerances & Precision \uf0c1 The story around how precision and tolerances are dealt with hasn't been great, for two reasons: Explicit tolerance option. I've tried to design the tolerance (used for balancing transactions) to be automatic and automatically inferred from statistics from the numbers in the input. The results aren't great. In Vnext I aim to provide an explicit option for setting the tolerance per currency. Precision. There are various places where numbers get rendered in v2: the reports code, the SQL query, and debugging scripts, and the way precision is set hasn't been used consistently. The precision also needs to be explicitly settable by the user. Rounding. There is another quantity that's used during interpolation: the precision used to round calculated numbers. Moreover, there is a need to distinguish between the precision and tolerances for numbers when used as prices vs. when used as units (see here ). One way is to store the display context per currency PAIR, not per currency itself. The distinction between these quantities hasn't been documented well; I'll keep in mind to clearly annotate those codes in Vnext and add suitable docs for this. Mostly the precision will be a rendering concern and a quantity that will be relevant for the new universal SQL query tool. Some prior design documentation exists here . Core Improvements \uf0c1 Some desiderata of new features are discussed below. These are all relevant to the core. Note that the changes should not interfere with current usage much, if at all. I expect that v2 users will be largely unaffected and won't have to change their ledger files. Booking Rules Redesign \uf0c1 Main document One of the current problems with booking is that entering an augmenting leg and a reducing leg have to be different by nature. The augmentation leg has to provide the cost basis via {...} syntax, and the reducing leg has to enter the price in the annotation and not in the cost basis. For example: 2021-02-24 * \"BOT +1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @143.75\" Assets:US:Ameritrade:Futures:Options 1 QNEG21C13100 {2875.00 USD} contract: 143.75 USD ... 2021-02-24 * \"SOLD -1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @149.00\" Assets:US:Ameritrade:Futures:Options -1 QNEG21C13100 {} @ 2980.00 USD contract: 149.00 USD ... Notice how the selling transaction has to be written down differently from the perspective of the user. The thing is, this makes it difficult from the perspective of the importer writer. It also ties the required syntax with the state of the inventory it's applied to, as it assumes something about this inventory. Moreover, this makes it difficult to write an importer that would handle a crossing of the absolute position, like this: 2021-02-19 * \"BOT +1 /NQH21:XCME @13593.00\" Assets:US:Ameritrade:Futures:Contracts 1 NQH21 {271860.00 USD} contract: 13593.00 USD Assets:US:Ameritrade:Futures:Margin -271860.00 USD Expenses:Financial:Commissions 2.25 USD Expenses:Financial:Fees 1.25 USD Assets:US:Ameritrade:Futures:Cash -3.50 USD 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -2 NQH21 {271815.00 USD} contract: 13590.75 USD Assets:US:Ameritrade:Futures:Margin 543630.00 USD Income:US:Ameritrade:Futures:PnL 45.00 USD Expenses:Financial:Commissions 4.50 USD Expenses:Financial:Fees 2.50 USD Assets:US:Ameritrade:Futures:Cash -52.00 USD The issue here is that we're crossing the flat line, in other words, we go from long one to short one. There are only two ways to do that properly right now: Disable booking and use the cost only, as per above. This is not great \u2014 booking is terribly useful. Track the position in your importer and separate the reducing and augmenting legs: 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {271815.00 USD} Both solutions aren't great. So I came up with something new: a complete reevaluation of how the syntax is to be interpreted. In fact, it's a simplification. What we can do is the following: use only the price annotation syntax for both augmentation and reduction and currency conversions, with a new booking rule \u2014 Match lots without cost basis in priority. If the lots have no cost basis, the weight of this posting is simply the converted amount, as before. If a match has been made against a lot with cost basis, the weight of this posting is that implied by the matched lots. Make the {...} used solely for disambiguating lots to match, and nothing else. If you have unambiguous matches, or a flexible booking strategy, e.g. FIFO, you'd pretty much never have to use the cost matching reduction. With this, the futures transaction above would simply use the @ price annotation syntax for both transactions. It would Make importers substantially simpler to write Supports futures naturally Be backward compatible with existing inputs for both currency conversions and investments. It is also more generally consistent and friendlier to Ledger users, without sacrificing any of the tighter constraints Beancount provides. I think it's even simpler to think about. Furthermore, this: Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD would be interpreted as \"match this lot, but only those with a cost basis attached to them.\" One question that remains is to decide whether an augmentation \u2014 now written down simply with @ price annotation \u2014 would store the cost basis in the inventory or not. I think we could make this determination per-commodity, or per-account. This would impose a new constraint: a commodity (or \"in an account\") would always be stored with cost basis, or not. Posting vs. Settlement Dates \uf0c1 When you import a transaction between multiple accounts within a single ledger, e.g. a credit card payment from one's checking account, the dates at which the transaction posts in each account may differ. One side is called the \"transaction date\" or \"posting date\" and the other side the \"settlement date.\" Where the money lives in between is somewhere in limbo (well in practice there is no money at all, just differences in accounting between institutions, things are never reflected instantly). One of the major shortcomings of the current core code is that the ability to insert a single transaction with postings at different dates is missing. Users are recommended to select a single date and fudge the other one. Some prior discussion on this topic exists here . Unfortunately, this method makes it impossible to represent the precise posting history on at least one of the two accounts. A good solution needs to be provided in Vnext, because this is a very common problem and I'd like to provide a system that allows you to precisely mirror your actual account history. The automatic insertion of transfer accounts to hold the commodities can be implemented as a feature, and it should live in the core. One possible idea would be to allow optional posting dates, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD which would result in two transactions behind the scenes, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD Equity:Transfer 2020-01-21 * \"ONLINE PAYMENT - THANK YOU\" \"\" Liabilities:US:Amex:BlueCash 2397.72 USD Equity:Transfer The lack of symmetry here raises the question of whether we should allow a transaction without a date or not: * \"ONLINE PAYMENT - THANK YOU\" \"\" 2020-01-19 Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD I think we can figure this out and the first solution is very doable. Input Split Transactions Some users like to organize their inputs in different files, or in different sections that strictly contain all of an account's transactions in order. This is related in spirit to the posting and settlement dates problem: at the moment the user is required to choose one of the two locations to insert their transaction. This should not be necessary. We should provide a mechanism that would allow users to insert the halves of a transaction into two different locations in their file, and a robust merging mechanism that would ensure that the two related transactions have been matched and merged (so that no unmerged half remains) and otherwise report errors clearly. The two halves could look like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD \u2026 2020-01-21 * \"AMEX EPAYMENT ACH PMT; DEBIT\" Liabilities:US:Amex:BlueCash 2397.72 USD The matching could be done via explicit insertion of special links, or by heuristics to match all such related transactions (perhaps declaring valid account pairs, thresholding on date differences and exactly matching amounts). When impossible to match, an error should be raised. Those merged transactions should be checked for balancing. Note how each of the transactions has a differing date; this would integrate with the transfer account solution proposed in the previous section. I haven't designed something yet, but this should be easy to implement and should be provided as a core feature, since it's so closely related to the input syntax. Currency Accounts instead of a Single Conversion \uf0c1 The current implementation of multiple currency transactions relies on a special \"conversion transaction\" that is automatically inserted at reporting time (when closing the year) to account for the sum total of imbalances between currencies. The goal of this transaction is to ensure that if you just sum up all the postings in the book, the result is purely an empty inventory (and not some residual amount of profit or loss incurred during currency exchange across different rates \u2014 note that we're talking only of the @price syntax, not investments). This is a bit of a kludge (the transaction itself does not balance, it converts to zero amounts of a fictional currency in order to keep itself quietly passing the balance test). What's more, its actual value is dependent on a subset of filtered transactions being summed up so it's a reporting-level construct, see here . There exists a method for dealing with multiple currencies without compromising on the hermeticity of individual transactions, described online, here . Using that method, you can filter any subset of transactions and summing them up will cleanly cancel out all lots. You don't need to insert any extra weight to fix up the balance. Also, you can explicitly book profits against the accrued gains in the currency accounts and zero them out and take advantage of this when you report them (and track them over time). The downside is that any currency conversion would see extra postings being inserted, etc. 2020-06-02 * \"Bought document camera\" Expenses:Work:Conference 59.98 EUR @ USD Liabilities:CreditCard -87.54 USD Equity:CurrencyAccounts:EUR -59.98 EUR Equity:CurrencyAccounts:USD 87.54 USD The problem is that it's a pain to use this method manually, it requires too much extra input. It's possible to have Beancount do that for us behind the scenes, completely automatically. I coded a proof-of-concept implementation here , but it's incomplete . In Vnext: The insertion of the kludgey conversions transactions should be removed. The currency accounts should become the norm. The fact that the two streams of directives will be very clearly separated should help, by distinguishing even more clearly between the parsing representation and the fully booked one, which will show these extra legs on transactions The prototype should be completed and issues fixed completely (not that much work involved). Strict Payees \uf0c1 I'm not sure if this makes sense yet, but I'd like to clean up the mess that payee strings are today. Payees are free-form, and if the user does not take care to clean them up\u2014and I'm one of those who doesn't\u2014the memos from imported sources are messy. It could be interesting to create a new directive to declare payee names ahead of time and an optional model that would require payees to be found in the list of declared payee names. Payees would have to have open and close dates, dates which would define the valid duration of the relationship with that payee (thereby adding more error verification capability). Price Inference from Database \uf0c1 Interpolation from price database. One of the oft-requested features is the ability to automatically interpolate prices from the internal price database history. I think that this should be doable unambiguously and deterministically and added to the interpolation algorithm. Price validation. Since a lot of the conversions at price (i.e., using \"@\") are inferred by leaving out one number, we should validate that the effective price is within some tolerance of a pre-existing price point near the date. This would provide yet another level of checking. Quantizing Operators \uf0c1 Another useful addition to the syntax would be operators that automatically quantize their result to a precision that depends on the particular target currency. For example, 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 / 1.19 EUR Expenses:Food:Taxes 2.13 / 1.19 * 0.19 EUR ; for example to calculate tax Assets:Cash That would become: 1970-01-01 * \"coffee\" Expenses:Food:Net 1.789915966386555 EUR Expenses:Food:Taxes 0.340084033613445 EUR Assets:Cash If instead an operator like this were provided, it would fix the issue: 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 /. 1.19 EUR Expenses:Food:Taxes (2.13 / 1.19 * 0.19). EUR Assets:Cash Or somesuch. Or maybe we'll want to add an option such that every evaluation of an arithmetic expression is automatically quantized as such. Constraints System & Budgeting \uf0c1 Beancount does not support budgeting constraints explicitly, but I think it would be possible to extend the balance assertion semantics to cover this. The current balance assertions check (a) a single commodity, and (b) that the amount is precisely equal to an expected one. Balance assertions should be extended to support inequalities, e.g., 2020-06-02 balance Liabilities:CreditCard > 1000.00 USD and perhaps we could check for the total inventory like this 2020-06-02 balanceall Assets:Cash 200.00 USD, 300.00 CAD I'd be curious to hear what would work best from users who do budgeting and design a minimalistic expression language to support that use case (though I'd try to keep it as simple as possible to avoid feature creep). Also, if the syntax is getting changed, adding a syntax that allows checking multiple currencies at once, and possibly a complete assertion that checks there aren't other commodities in the account could also make sense. Average Cost Booking \uf0c1 Average cost booking has been discussed and a good solution sketched out a very long time ago. Vnext should sport that method natively; a lot of users want to have this feature for dealing with their tax-deferred accounts. It takes a bit of work to handle the precision of the various automated conversions right. The way it would work is by automatically merging all related lots of the same commodity on a reduction, and optionally on an augmentation. Some constraints may be required (e.g. only a single commodity in that account). Trade Matching & Reporting \uf0c1 A few core tasks related to P/L and trading still need to be implemented. Trade list. A problem that I've really been wanting to solve for a very long time but never found the time for is to save crumbs from the booking process so that a correct list of trade pairs could be easily extracted from the list of directives. I wrote some notes here and here a long time ago. Essentially, for each booked reduction, insert a reference to the corresponding augmenting posting. I've prototyped this as metadata but it should be made something more official. A single linear scan can pick up these references, build a mapping and recover the (buy, sell) pairs to produce a table of trades. There's a precedent I once wrote in a plugin . Needless to say, producing a list of trades is a pretty basic function that Beancount does not provide out of the box today; it really should. Right now users write their own scripts. This needs to be supported out-of-the-box. Harmonize balance and gains validation. Checking that transactions balance and that income from gains balance with a transaction's prices (the sellgains plugin) are done in completely separate places. Those two codes occupy similar roles, and should be implemented next to each other. Commissions in P/L. Properly counting profits & losses by taking off the fraction of buying commission of an original lot and the selling commission into account is not possible at the moment. I think it could be done with a plugin that moves some of the (computed) income leg into a separate negative income account to do this properly for reporting purposes. Self-Reductions \uf0c1 Currently the application of reductions operates on the inventory preceding the transaction. This prevents the common case of self-reductions, and both I and some users have come across this problem before, e.g. this recent thread ( ticket ). This comes off as unintuitive to some users and ought to have a better solution than requiring splitting of transactions. Since we're rewriting the booking code entirely in Vnext, contemplate a new definition that would provide a well-defined behavior in this case. I remember from prior experiments attempting to implement this that it wasn't a trivial thing to define. Revisit. This would be a nice improvement. Stock Splits \uf0c1 Some discussion and perhaps a strategy for handling stock splits should be devised in Vnext. Right now, Beancount ignores the issue. At the minimum this could be just adding the information to the price database. See this document for more details. Multipliers \uf0c1 Options have a standard contract size of 100. Futures have a contract size that depends on the particular instrument (e.g., /NQ with a multiplier of 20). I've been handling this for options by multiplying the units by 100, and for futures by multiplying the contract size by the per-contract multipliers (ditto for options on futures). I do this in the importers. For options, it works and it isn't too bad (e.g. positions of -300 instead of -3), but for futures, it's ugly. The result is not representative of the actual transaction. I'd like to add a per-currency multiplier, as well as a global dictionary of regexps-to-multiplier to apply, and for this to be applied everywhere consistently. One challenge is that everywhere there's a cost or price calculation, this has to be applied. In the current version, those are just multiplications so in many parts of the codebase these haven't been wrapped up in a function that could easily be modified. This needs to happen in a big rewrite \u2014 this is the opportunity to do this. Example here. Returns Calculations \uf0c1 If you look at investment brokers out there, no one calculates returns correctly. Brokers provide one of two features: No cash transfers. Total value of account today vs. total value of account at some point in the past (i.e., account inception or beginning of the year). This isn't very useful because they never account for the addition or removal of cash to the account. For example, say you open an account with $100,000 and invest, and mid-year add another $20,000, say the original investments are now worth $95,000, the report would show a gain of $15,000, whereas you really incurred a loss. Better brokers like Vanguard will show a plot that includes two overlaid bars, one with cash added and profit overlaid, like this: Lack of interest or dividends. Other brokers will report P/L over time from the investments, but they fail to account for actual interest or dividends received (they only look at the price of the underlying) so that's not useful for bonds or for stocks with significant dividends, or when grouping them, they fail to account for the addition of new positions over time. Counterfactual performance. Finally, all of them fail to compare your actual annualized performance with that of a benchmark portfolio with equivalent cash infusions. For example, instead of your actual investments made, compare with the performance you would have obtained if you had invested in some standardized portfolio of investments over that particular time period, given the actual historical prices of those instruments. Ideally one should be able to define any alternative portfolio to compare against using their particular cash transfers. More fancy analyses aren't even contemplated, e.g., what would have been the impact of changing my rebalancing strategy (or actually implementing a more strict one)? There are well known methods for both time-based and value-based returns reporting. The right thing to do is to extract a time-series of cash flows and compute the annualized or IRR returns, or value-weights. I started this work at some point and ran against some difficulties and eventually removed it. The results remain here. I'd really love to build this, and eventually perhaps this could grow into its own project, with associated support functions in the Beancount core. This will possibly be a project of its own, but this requires similar support for enumerating instruments and price sources as that which is needed for fetching prices, as well as functions for isolating cash flows for specific subsets of accounts; these should probably live in the core. UPDATE September 2020: This has mostly been implemented. See this document for details. Unsigned Debits and Credits \uf0c1 A useful idea that's nearly trivial to implement is to allow users to input all positive unit numbers and to automatically flip the signs on input, and to output them all as positive numbers as well splitting them up between Debit and Credit columns. This would make Beancount's rendering a lot easier to grok for people with an existing background in accounting. This feature will introduce no complexity, easy to add for free. See TODO here . Holdings \uf0c1 One of the libraries I built at some point was this notion of a \"holding\", here . At the time I wasn't sure if that data would contain much more than what's in a posting, but as it turns out, representing holdings as just the posting is good enough, all you need is the market value, and compute the total values from the units is trivial. In fact, I've haven't been using that code for years, I've been using the export script instead, which writes out a table that gets uploaded to a Google Sheets doc. This proves to me aggregating the positions in an Inventory is plenty sufficient in practice, along with a mapping of latest market prices. I'm going to delete that code. It's only been used in the reports code anyway, which will be removed anyway, and in the experimental \" unrealized gains \" plug, which was only a proof-of-concept that convinced me booking such gains as transactions is not a good idea and which will get removed and live in a separate experimental repository anyway. Tooling for Debugging \uf0c1 Context should recover erroneous transactions. One of the major annoyances of error recovery is that if a transaction involves some types of errors, the postings aren't produced in the stream of directives. This problem is related to the lack of clarity between the merely parsed data structure and the fully resolved one. In particular, this means that the \"bean-doctor context\" debugging command often cannot provide useful context around a failing transaction. This really needs to be fixed, to improve debuggability. Document debugging tools. In general, I should write a better exposition of how to use the various transaction debugging tools; a lot of the questions on the mailing-list would disappear if users knew better how to leverage those. Interactive context in Emacs. If the performance allows it, we could build an Emacs mode which renders the context around a partially written transaction, including inventories of the affected accounts before and after the transaction, as well as interpolated values, to a different buffer updated interactively. This would make it much more fun to input data and provide immediate feedback about the newly inserted transaction. Documentation Improvements \uf0c1 Remove dependency on furius.ca. The current Google Docs based documentation links to other documents via a global redirect (the definition is found here ). While it does not happen often that my web server goes down (perhaps a few times per year), when it does it takes a few days to rectify the situation. That server is hosted graciously in the company of some friends of mine. Kirill has proved that it would be possible to replace all the links to redirects on github, that would look like this: beancount.github.io/ instead of furius.ca/beancount/doc/ . In order to do this, I'll need to run a script using the Docs API on all the Google Docs to change them automatically. Conclusion \uf0c1 There are other items in the TODO file . These are just the main, big issues that I think matter the most and I'd like to address them in a Vnext rewrite. Development branches will look like this: v2 : Current master will be branched to \"v2\", which will track the stable current version. That branch will build with both the current setup.py system and Bazel. Fixes will be implemented on that branch where possible, and merged to Vnext. master : Current master will become Vnext. Only the Bazel build will be supported on that branch. Any comments appreciated. Appendix \uf0c1 More core ideas for Vnext that came about during discussions after the fact. Customizable Booking \uf0c1 For transfer lots with cost basis\u2026 an idea would be to create a new kind of hook, one that is registered from a plugin, e.g. a callback of yours invoked by the booking code itself, and whose results applied to a transaction are immediately reflected on the state of the affected inventories. Maybe this is the right place to provide custom algorithms so that their impact is affecting the subsequent inventories correctly and immediately. Now, imagine generalizing this further to provide and implement all of the current booking mechanisms that are currently built in the core. Call this \"customizable booking.\" ( thread ). Ugly Little Things \uf0c1 print_entry() uses buffering that makes it impossible to use regular print() interspersed with the regular stdout without providing file= option. Fix this, make this regular instead, that's just annoying, just print to regular stdout. The default format for __str__ for inventories puts () around the rendering. When there's a single position, that looks like a negative number. That's dumb. Use {} instead, or something else. Add a flag to bean-check to make it run --auto plugins by default. This is great for imported files, which may not have a complete ledger to feed in. Incremental Booking/ Beancount Server / Emacs Companion \uf0c1 In order to make recomputation fast, the idea of creating a standalone \"Beancount server\" starts to make sense. The expensive part of the Beancout calculations on a large file is the booking and interpolation. The key to making things fast is thus to keep all the original unprocessed transactions in memory along with the booked and interpolated ones, and on a change, reparse the modified files and scan all the transactions, updating only the ones whose accounts have been affected. This could be problematic in theory: some plugins may rely on non-local effects in a way that affects what they output. I believe in practice it would work 99% of the time. But I think it may be worth a prototype. On the other hand, Vnext may turn out to be fast enough recomputing everything from scratch every single time (my own file went from 4s -> 0.3ms for the parsing stage of the largest file), so maybe this is irrelevant overall. Such a server would be a perfect companion to a running Emacs. We could build an Emacs mode which communicates with the server. Tags & Links Merge with MetaData \uf0c1 TODO(blais): Add colon syntax","title":"Goals & Design"},{"location":"beancount_v3.html#beancount-vnext-goals-design","text":"Martin Blais , July 2020 http://furius.ca/beancount/doc/Vnext","title":"Beancount Vnext: Goals & Design"},{"location":"beancount_v3.html#motivation","text":"It's time to give Beancount a refresh and to put down a concrete plan for what the next iteration of it ought to be. I've had these thoughts in the back of my mind for a long while\u2014at least a year\u2014and I'm putting these in writing partly to share a vision of what the product we all use to organize our finances can become, partly to solicit feedback, and partly to organize my thoughts and to prioritize well on the stuff that matters. Current status. The current state of Beancount is that development has been static for a while now, for a number of reasons. The software is in a state that's far from perfect (and I'll be enumerating the main problems in this document) but I've been resisting making too many changes in order to provide myself and others a really stable base to work from. More importantly, while I used to be able to spend a significant amount of weekend time on its development, life changes and a focus on my career of late has made it difficult for me to justify or find the extra time (it has been 10 years after all). A multitude of ideas have aged to this TODO file but it's too detailed to grok and a bit of a dump, this document should be more useful. Why rewrites happen. When I wrote version 2 of Beancount (a full rewrite of the first version), it was because of a confluence of ideas for improving my first draft; I resisted for a while, but eventually it made so much sense to me that it became simply impossible not to write it. Many of the ideas driving the redesign at the time are still axioms in today's design: removing order dependence, normalizing the syntax to be well-defined with a BNF grammar, converting custom processing to a sequence of plugins off of a simple stream of directives, the current design of booking selection and how cost basis works, and all the directives beyond \"Transaction\". These ideas largely shape what a lot of people like about using Beancount today. Goals. Now is the time for yet another wave of evolution for Beancount, and similarly, a set of new ideas I'm going to lay down in this document form as potent a change as the v1 to v2 transition. The vision I have for Vnext will simplify Beancount, by factoring into simpler, more isolated, more reusable, better defined parts, and not merely by adding new features on top of what's there. In many ways, Vnext will be a distillation of the current system. It will also make space to finally implement some of the core features most often desired by users. And those changes will enhance some organizational aspects: allow for more contributions, and also trim down the part that I'm handling myself to less code, so I can more effectively focus on just the core features.","title":"Motivation"},{"location":"beancount_v3.html#current-problems","text":"","title":"Current Problems"},{"location":"beancount_v3.html#performance","text":"My personal ledger, and I know that the ledgers of many users, are simply too large to process instantly. My current file takes 6 seconds on the souped-up NUC I use for a desktop at home\u2014but that's just too long. I'm really quite attached to the idea of processing the entire set of inputs every time, instead of forcing users to cut-up their ledgers into multiple files with \"closing\" transitions at arbitrary points in the year, but I really do want that \"instant\" feeling that you get when you run two-letter UNIX programs, that it runs in well under half a second . It makes it a lot more interactive and fun to use. C++ rewrite. One of the reasons for the slow performance right now is the fact that Beancount is implemented in Python, even at the level of the parser (C code calling back into a Python driver). An obvious solution is to rewrite the core of the software in a language closer to the metal, and that will be C++. I'm selecting C++ for its control and because the current slate of tools around it is mature and widespread enough that it should be easy for most to build without too many problems, and I can leverage C libraries that I will need. Using a functional language could have been fun but many of the libraries I want simply would not be available or it would be too difficult for mere mortals to build. Simple, portable C++. It's important to mention that the C++ code I have in mind is not in the style of template-heavy modern C++ code you'd find in something like Boost. Rather, it's a lot more like the conservative \"almost C without exceptions\" subset of C++ that Google uses , with a base on Abseil-Cpp (for example and flavor, see tips ). The reasons for this are stability and portability, and while this rewrite is for faster performance, I believe that it will not be necessary to pull template tricks to make it run fast enough; just a straightforward port to avoid the Python runtime will likely be sufficient. Above all I want to keep the new code simple and \"functional-ish\" as much as possible (no classes if I can avoid it), relying on a trusted set of stable dependencies , built hermetically using the Bazel build tool. Python API. It's also important that the Python API remains for plugins and scripts, and that the full suite of unit tests be carried over to the newer version of the code. After all, the ability to write custom scripts using all that personal finance data is one of the most attractive features of the text-based approach. Code beyond the new core implementation will remain in Python, and existing code built on top of the Python API should be very easily portable to Vnext. This can be achieved by exposing the directives with wrappers written in pybind11. Other languages. The resolved output of the Beancount core will be a stream of protocol buffer objects, so processing from other languages (e.g., Go) will have first-class support. Processing model. An additional algorithmic improvement to performance, should it be necessary, would be to define plugins processing in terms of iterator functions that cascade and interleave the processing of the directive stream without making entirely disjoint passes over the full list of directives. While the freedom to have each plugin process all the directives on their own has been instrumental in keeping the system free of synchronized application state and has allowed me to isolate behavior of the plugins from each other, there are opportunities to join together multiple transformations in a single pass. Fewer passes = faster.","title":"Performance"},{"location":"beancount_v3.html#intermediate-parsed-data-vs-final-list-of-directives","text":"In Beancount v2, I was never too careful to clearly distinguish between The list of directives coming out of the parser , missing interpolation and booking, using position.CostSpec instead of position.Cost on each lot, and The resolved and booked list of directives with booking algorithms applied to select matching lots, and interpolated values filled in, as well as transformations having been applied by the various plugins. These two lists of directives are really quite distinct in purpose, though they share many common data structures, and for the most part, the first list appears mostly in the parser module. There have been cases where it was confusing, even to me, which of the lists I was manipulating. Part of the reason is due to how I'm using mostly the same Python data structures for both, that allow me to bend the rules on typing. Perhaps more importantly is that because plugins run after booking and interpolation, and are required to put out fully interpolated and booked transactions, a plugin that wants to extend transactions that would run as invalid in the input syntax is difficult. See #541 for an example. The next iteration will see both the intermediate parser production and final resolved list of directives implemented as protocol buffer messages, with strictly distinct data types. This will replace beancount.core.data . The distinction between these two streams will be made very clear, and I will try to hide the former as much as possible. The goal is to avoid plugin writers to ever even see the intermediate list. It should become a hidden detail of the implementation of the core. Furthermore, there may be two types of plugins: a plugin that runs on the uninterpolated, unbooked output of the parser, and a plugin that runs on the resolved and booked stream. This would allow more creative use of partial input that might be invalid under the limitations of interpolation and booking. Updates: We could convert the plugin system to one that runs at booking/interpolation time. We should attempt to make the booking/interpolation atomic, in that one could write a Python loop with an accumulator and invoke it independently, so that in theory, booking could be used in the importers (like I do for Ameritrade).","title":"Intermediate Parsed Data vs. Final List of Directives"},{"location":"beancount_v3.html#make-rewriting-the-input-first-class","text":"Added in Dec 2020 after comments and #586 . A number of frequently asked questions have to do with how to process the input data itself. Usually, a new user will attempt to load the contents of the ledger, modify the data structures, and print to update their file, not realizing that the printer includes all the interpolations, booking data, and modifications from plugins, so this cannot work. However, since we're rewriting the parser and ensuring a clean separation between intermediate ASI-like data and processed and finalized directives, we can implement a special printer for the AST intermediate data, so that users could run just the parser, modify the intermediate directives, and print them back out, perhaps losing just some of the formatting and whitespace. This formatting loss can be leveraged to reimplement bean-format more naturally: the output of that printer should always be formatted neatly. This would avoid users having to write ad-hoc parsers on their input file, sed-like conversions, and so on. They could do it properly by modifying the data structure instead. What's more, in order for this to work accurately, we'd have to delay processing of the arithmetic operations post-parsing, so that we can render them back out. This offers another advantage: if we process the calculations after parsing, we can afford to provide an option to let the user specify the precision configuration to use for mpdecimal. I really like that idea, because it avoids hard-coding calculation precision and better defines the outcome of these options, potentially opening the door to a more rational way to remove extra digits that often get rendered out. Finally, if a nice library function can be made to process transactions in-place and output them back out, preserving all comments around them, this can become another way\u2014perhaps the preferential way\u2014for us to clean payees and somesuch. At the moment, the solution is to write a plugin that will clean up the data, but the input file remains a bit of a mess. Making it easy to automatically clean up the input file is an appealing alternative and potentially will add an important new dimension to the Beancount workflow. I want to make all changes necessary to make this possible and well supported (I'm imagining my ledger file all cleaned up right now and it's appealing). I think it's not very much work, it involves: Storing begin/end line information on everything. Adding AST constructs for representing arithmetic calculations. Adding comments parsing to the renderer. Implementing a new renderer that can reproduce the AST, including handling missing data. Implementing a library to make modification of a file in-place as easy as writing plugins, while preserving all non-directive data in the file as is.","title":"Make Rewriting the Input First Class"},{"location":"beancount_v3.html#contributions","text":"For most of the development of Beancount, I've been pretty reluctant to accept contributions. It has been a closely held pet project of mine since it has so much impact on my personal financial arrangements and I dread unplanned breakage. The main reservations I've had over contributions are two-fold: Not enough testing. Proposed changes that did not include enough testing, or none at all, sometimes even the kind testing that would prevent basic breakage. When I'd accept some proposals and commit to writing the tests myself it could sometimes take me down the rabbit hole for hours (if not days). This wasn't practical. Cascading design impact. Some of the proposals did not take into account broader design considerations that would affect other parts of the code, which I may not have communicated or documented well. I've had to reject some ideas in the interest of keeping things coherent and \"tight.\" Part of this is my fault: putting a high bar on contributions hasn't allowed potential contributors to acquire enough context and familiarity with the codebase to make changes compatible with its design. Allowing more contributions. Starting in Vnext I'd like to restructure the project so that more people are able to get involved directly, and to closely work 1-on-1 with some contributors on particular features. Clearly Beancount benefits from direct input from more people. The recent move to GitHub only compounds the urgency for making that easier. To this effect, I'd like to implement a few strategies: Break down the code in parts. This is a natural evolution that has been seen in many other projects; I'm no Linus nor Guido, but like their respective projects, as they grew in popularity, the original authors narrowed their focus on the core part and let other people expand on adjacent but also important functionality. I'd like to focus more of my time only on core functionality that will impact things like support for settlement dates, currency accounts, split transactions, trade reporting, etc. Letting other people deal with adding or updating price sources, making improvements to the ingestion framework, and making beautiful renderings and presentations of the data would be ideal. In time, I may eventually break down these libraries to separate repositories with looser contribution guidelines and/or add ACLs for others to push directly to those repos. Acquire \"lieutenants.\" I need to leave more space for trusted and frequent contributors to chip in more liberally. For example, Martin Michlmayr now has direct edit access to most documents and has been making numerous helpful contributions and updates to the docs. Kirill Goncharov's conversion of the documentation out of Google Docs is simply beautiful. RedStreet and many others are regularly pitching in answers on the mailing-list. Stefano Zacchiroli and Martin have built a standalone conversion tool from Ledger. Daniele Nicolodi is proposing some low-level changes to the scanner and parser. And of course, Dominik Aumayr and Jakob Schnitzer continue developing the Fava project adjacent to Beancount. There are many more people, there is a slowly-but-surely growing list of familiar recurring names. The question in my mind is: Is there a way to communicate with regular faces so that we're aligned in terms of design and can coordinate our efforts in the same direction? Does the newly acquired familiarity with video-conference meetings (thanks to the coronavirus crisis) afford us a new channel for coordination that wasn't there before? The Python community has thrived in no small part due to the annual face-to-face interactions between its participants and a number of core developers being in the same place for some time. Can we achieve the same thing online, as we are a smaller community? If so, better coordination may make it easier to accept proposed changes. I wonder if I should propose a monthly \"team\" meeting. 20% projects. I should provide a list of \"20% projects\" that are well aligned with the direction of the project for others to take up if they want to, and add profuse guidance and detail of downstream side-effects from those proposed features. The idea is to make it possible for newcomers to contribute changes that are likely to fit well and be easily integrated and accepted on the codebase.: Proposals. Beancount's equivalents of Python's \" PEPs \" are essentially the Google Docs proposal documents I started from threads where many others comment and add suggestions. A central list of those should be shared to a folder, identified as such, and allow others to write similar proposals. Maybe a little more structure and discipline around those would be useful.","title":"Contributions"},{"location":"beancount_v3.html#restructuring-the-code","text":"At the very coarse level, the code restructuring for Vnext looks like this: C++ core, parser, and built-in plugins. The Beancount core, parser, booking algorithm and plugins get rewritten in simple C++, outputting its parsed and booked contents as a stream of protobuf objects. Query engine. Beancount query/SQL gets forked to a separate project operating on arbitrary data schemas, with a much broader scope than Beancount. See section below. The rest. Most of the rest gets cut up into separate projects or at least, at first, in a distinct place within the repository (until the core is rewritten). Note that because the core outputs the stream of directives as proto objects, any language supported by protobufs should be able to read those. This extends the reach of Beancount. Here's a simplified diagram showing how this might look: Here is a detailed breakdown of the various parts of the codebase today and what I think will happen to them: Core. This is the part of Beancount's code which will get rewritten in C++ and output a sequence of messages to a stream of directives. I'll continue keeping a tight focus on that part with a conservative eye toward stability, but in Vnext will be adding desired new capabilities that have been lacking so far as described in the next section of this document. The core will include the following packages: beancount/core beancount/ops beancount/parser beancount/utils beancount/loader.py beancount/plugins (some, see below) beancount/utils (most) Query. The query language will be factored out into a completely separate repo with a broader application domain (and hooks for customizing for Beancount). I suspect that over time that project will acquire a much broader range of contributors, many of which will not even be Beancount users. This includes the code from these packages: beancount/query beancount/tools Prices. This is a simple library and tool that helps users fetch prices from external sources. This should definitely move to another repo and I'd welcome a new owner building a competing solution. People are sending me patches for new price sources and I have too little time to maintain them over time, as the upstream sources change or even disappear. This requires very little from Beancount itself (in theory you could just print() the directives for output, without even loading library code) but I think the Beancount core should include and functions to enumerate a list of required date/instrument pairs at a particular date from a given ledger (and I'm happy to support that). Note that the internal price database core will remain in the core, because it's needed there. The affected packages are: beancount/prices Improvements should be made to this library after it moves out of the Beancount repository: we should isolate the Beancount code to just a few modules, and turn the scope of this project to something larger than Beancount: it's three things, really: a) an up-to-date Python library of price fetchers with unit tests and maintained by the community (i.e., when sources break, we update the library) and a common API interface (needs to be improved from what's there TBH, the API should support fetching time series in a single call); b) an accompanying command-line tool (currency \"bean-price\") for fetching those prices from the command-line. This requires the specification of \"a price in a particular currency from a particular source\" as a string. I'd like to improve that spec to make the USD: prefix optional, and maybe eliminate the chain of prices in the spec, which hasn't found much use in practice and move that upstream. c) Make the interfaces to fetch ledger-related information (e.g., list of missing/required prices and lists of instruments) onto modules: beancount v2, beancount Vnext, ledger, hledger, and rendering output formats to any of these. In other words, this library should be able to fetch prices even if Beancount isn't installed. To turn this project into something that can run independent of beancount. Ingest. The importers library will probably move to another repo and eventually could even find another owner. I think the most interesting part of it has been the establishment of clear phases: the identify, extract and file tasks, and a regression testing framework which works on real input files checking against expected converted outputs, which has worked well to minimize the pain of upgrading importers when they break (as they do break regularly, is a SNAFU). In the past I've had to pull some tricks to make command-line tools provided by the project support an input configuration as Python code but also possible to integrate in a script; I will remove the generic programs and users will be required to turn their configuration itself into a script that will just provide subcommands when run; the change will be very easy for existing users: it will require only a single line-of-code at the bottom of their existing files. The Bazel build may add some minor difficulties in loading a Python extension module built from within a Bazel workspace from an otherwise machine-wide Python installation, but I'm confident we'll figure it out. I'd also be happy for someone else to eventually take ownership of this framework, as long as the basic functionality and API remains stable. The example csv and ofx importers should be removed from it and live in their own repos, perhaps: ofx. the OFX importer should be replaced by something using ofxtools (the one I built is pretty bad), and the CSV importer really needs a thorough rewrite with lots of unit testing for the large list of options it now supports (these tests are sorely missing). csv. Furthermore, I think the CSV importer could be enhanced to be smarter and more flexible, to automatically detect from the column headers and inferred data types in the files which column should convert into which field. I'm not going to do that (I don't have time). Someone with the urge to make the ultimate automatic CSV parser ought to create a separate repository for that. The affected packages are: beancount/ingest : could eventually move to another repo. beancount/ingest/importers: someone could revive a repository of importer implementations, like what LedgerHub once aimed to become, and swallow those codes. See this document for details on what's to happen with the ingestion code. Custom reports and bean-web should be removed: the underlying bottle library seems unmaintained at this point, Fava subsumes bean-web, and I never liked the custom reports code anyway (they're a pain to modify). I never use them myself anymore (other than through bean-web). I really think it's possible to replace those with filters on top enhanced SQL query results. The conversion to Ledger and HLedger from Beancount now seems largely useless, I'm not sure anyone's using those. I'll probably move these to another repo, where they would eventually rot, or if someone cares, adopt them and maintain or evolve them. beancount/web : will be deleted or moved to another repo. beancount/reports : will be deleted or moved to another repo. Note that this includes deprecating beancount/scripts/bake , which depends heavily on bean-web. I have no substitute for bean-bake, but I think I'd like to eventually build something better, a tool that would directly render a user-provided list of specific SQL queries to PDF files and collate them, something you can print. Jupyter notebook support. A replacement for the lightweight interface bean-web used to provide could be Jupyter Notebook integration of the query engine, so that users can run SQL queries from cells and have them rendered as tables, or perhaps a super light web application which only supports rendering general SQL queries to tables. Built-in Plugins. Beancount provides a list of internal plugins under beancount/plugins . It's not indicated clearly, but there have evolved two groups of plugins in there: stable plugins used by the core, and experimental plugins showcasing ideas, which are often incomplete implementations of something that was proposed from a thread on the mailing-list. The former group will be ported to C++, and the latter group should probably move to another location with much looser acceptance constraints. First, there are \"meta-plugins\" which only include groups of other plugins: Only one of those should remain, and maybe be enabled by default (making Beancount pedantic by default): auto pedantic The following plugins should remain in the core and be ported to C++: auto_accounts check_closing check_commodity close_tree commodity_attr check_average_cost coherent_cost currency_accounts implicit_prices leafonly noduplicates nounused onecommodity sellgains unique_prices The following are the experimental implementations of ideas that should move to a dedicated repo where other people can chip in other plugin implementations: book_conversions divert_expenses exclude_tag fill_account fix_payees forecast ira_contribs mark_unverified merge_meta split_expenses tag_pending unrealized Because it's a really common occurrence, the new transfer_lots plugin should be part of the built-in ones. Projects. The beancount/projects directory contains the export script and a project to produce data for a will. The will script will be moved outside the core of Beancount, I'm not sure anyone's using that. Maybe the new external plugins repo could include that script and other scripts I shared under /experimental. The export script should be grouped together with beancount/scripts/sql and other methods to send / share data outside of a ledger; these could remain in the core (I'm using the export script regularly to sync my aggregates and stock exposure to a Google Sheets doc which reflects intraday changes). Scripts. Some of the scripts are completely unrelated to Beancount, they are companions. The scrape validator. The sheets upload. The treeify tool. These should be moved elsewhere. One of the advantages of having all the code in the same repo is that it makes it possible to synchronize API changes across the entire codebase with a single commit. As such, I may keep some of the codes in the same repo until the new C++ core has stabilized, and properly separate them only when Vnext releases.","title":"Restructuring the Code"},{"location":"beancount_v3.html#universal-lightweight-query-engine-ulque","text":"The SQL query engine for Beancount was initially a prototype but has grown to become the main way to get data out of it. I've been pretty liberal about adding functionality to it when needed and it's time to clean this up and consider a more polished solution. In Vnext, the query/SQL code gets eventually forked to a separate project (and repo) operating on arbitrary data schemas (via protobufs as a common description for various sources of data) and has support for Beancount integration. Imagine if you could automatically infer a schema from an arbitrary CSV file, and run operations on it, either as a Python library function or as a standalone tool. Furthermore, this tool will support sources and/or sinks to/from Google Sheets, XLS spreadsheets, containers of binary streams of serialized protos, tables from HTML web pages, PDF files, directories of files, and many more. This is going to be a data analysis tool with a scope closer to that of the Pandas library rather than an accounting-focused project, but also a universal converter tool, that will include the functionality of the upload-to-sheets script (which will get removed). One of the lessons from the SQL query engine in Beancount is that with just a little bit of post-processing (such as treeify ), we can do most of the operations in Beancount (journals, balance sheet & income statements) as queries with filters and aggregations. The tool will be made extensible in the ways required to add some of the idiosyncrasies required by Beancount, which are: Native support for a Decimal type . The addition of custom types for aggregators with the semantics of beancount.core.Inventory/Position/Amount . The ability to automatically generate a dynamic column rendering a line-by-line aggregation of another column (or set thereof), that is, a \"balance\" column . The ability to render a \" bottom line \" of aggregates at the end of the results table. Functions for splitting of aggregated columns , for amounts and inventories into multiple columns (e..g, \"123.00 USD\" becomes two columns: (123.00, \"USD\") to be processable in spreadsheets, and also for splitting debits and credits to their own columns. In particular, printing multiple lots accumulated in an account should be made natural from the SQL query, replacing the \"flatten\" feature by a more standard splitting off an array type. Moreover, broadening the focus with a new project definition will make a change to testing it thoroughly (the current one is still in a bit of a prototype stage and does not have nearly the amount of required tests), and also include data type validation (no more exceptions at runtime), by implementing a typed SQL translator. I'll document this elsewhere. This is a much bigger project, but I suspect with the broader scope, it will be easier to test and take on a life of its own. I'm preparing a design doc on this.","title":"Universal Lightweight Query Engine (ulque)"},{"location":"beancount_v3.html#api-rework","text":"I write a lot of custom scripts, and there are a number of things that bother me about today's Beancount API, which I want to radically improve: Consolidate symbols under \"bn\". The internal API calls for importing the symbols from each package separately, but now that I'll have split off the ingestion and reporting code, all of the public API, or at least the majority of the commonly used objects in the core should be available from a single package, a bit like numpy: import beancount as bn \u2026 bn.Inventory(...) bn.Amount(...) bn.Transaction(...) # etc. I'd like for \"bn\" to become the de-facto two-letter import on top of which we write all the scripts. Default values in constructors. The namedtuple containers are mighty fine, but their constructors never had optional arguments, and it's always a bit of a dance to create those containers with a ton of \"None\" options. I never liked it. We'll make this tidy in the next iteration. No API documentation. While there is a substantial amount of documentation around the project, there is no documentation showing people how to use the Python API, e.g. how to accumulate balances, how to create and use a realization tree, how to pull information out of an accumulated inventory object, etc. I think that documenting some of the most common operations will go a long way towards empowering people to make the most out of Beancount. Some of these operations include: Accumulating lots of an inventory and printing them. Converting to market value, and making corresponding account adjustments. \u2026. add more \u2026 Exposed, usable booking. Booking will be a simple loop that can be invoked from Python with an entry and some accumulated state. Moreover, the Inventory object should begin to implement some of the lower-level operations required for booking, such that iterating over a set of postings and doing e.g., average booking, can be done via method calls on an Inventory object. Inventory should take a more prominent place in the API. Data types. Well defined data types should be provided for all objects to make liberal use of the typing module over all new code. Maybe create a module called \"bn.types\" but they should be available directly from \"bn.*\" so that there is a single short-named import. Terminology. I'd like to stop using \"entries\" and consolidate over the name \"directives\" in Vnext. Realization. I've been using a collections.defaultdict(Inventory) and a \"realization\" interchangeably. Both of these are mappings from an account name (or some other key) to an Inventory state object. I'd like to unify both of these constructs into the realization and make it into a commonly used object, with some helper methods.","title":"API Rework"},{"location":"beancount_v3.html#parser-rewrite","text":"Since we will now depend on C++, the parser will get to be rewritten. Worry not: the input syntax will remain the same or at least compatible with the existing v2 parser. What will change is: Unicode UTF-8 support. The lexer will get rewritten with RE/flex instead of GNU flex. This scanner generator supports Unicode natively and any of the input tokens will support UTF-8 syntax. This should include account names, an oft-requested feature. Flags. The current scanner limits our ability to support any flag and supports only a small list of them. I think the list has proved sufficient for use, but since I'll be putting some work into a new scanner I'm hoping to clean up that story and support a broader, better defined subset of single-letter flags for transactions. Time. The parser will parse and provide a time field, in addition to the date. The time may be used as an extra key in sorting directives. The details for this are yet to be determined, but this is requested often enough at the very minimum the parser will output it as metadata, and at best, it may become a first-class feature. Caching. The pickle cache will be removed. Until very recently , there weren't great options for disabling it (env vars) and I'd rather remove the only two environment variables that Beancount honors as a side-effect. Since the C++ code should be fast enough, hopefully a cache will not be needed. Tags & links. In practice, those two features occupy a very similar role as that of metadata (used to filter transactions). I'm contemplating unseating the special place taken by tags and links in the favor of turning those into metadata; the input syntax would not be removed, but instead the values would be merged into the metadata fields. I'm not 100% sure yet about doing this and open for discussion. Furthermore, the parser should be made to accept #tag and ^link where metadata is declared today, which would be convenient syntax. Finally, users have expressed a desire for tags on postings. We should contemplate that. Plugins configuration as protos. The options for the various plugins have been loosely defined as eval'ed Python code. This is pretty loose and doesn't provide a great opportunity for plugins to do validation nor document their expected inputs. I'd like to formalize plugin configuration syntax a bit, by supporting text-formatted protos in the input syntax (for a message type which would be provided by the plugins themselves). Parser in C++. The parser will be rewritten in C++. In the process of writing Vnext, I'll try to maintain a single grammar for both for as long as possible by calling out to a C++ driver interface, which will have two distinct implementations: one for the V2 version calling into Python, and one for the Vnext parser generating protos. In the process I may be porting the lexer and grammar Python implementation to C, as discussed in this ticket . Better includes. Current includes fail to recognize options that aren't in the top-level file. This caused many surprises in the past and should be fixed. At the minimum, an error should be raised.","title":"Parser Rewrite"},{"location":"beancount_v3.html#code-quality-improvements","text":"Rename \"augmentation\" and \"reduction\" to \"opening\" and \"closing\" everywhere. This is just more common terminology and will be more familiar and understandable to people outside of our context. Type annotations. The use of mypy or pytype with type annotations in Python 3 is by now a very common sight, and works quite well. As part of Vnext, all of the core libraries will be modified to include type annotations and the build should be running pytype automatically. I'll need to add this to our Bazel rules (Google doesn't currently provide external support for this). While doing this, I may relax some of the Args/Returns documentation convention, because in many cases (but not all) the type annotations are sufficient to get a good interpretation of a function's API. PyLint in build. Similarly, the linter should be run as an integral part of the build. I'd like to find a way to selectively and explicitly have to disable it during development, but otherwise be set up such that lint errors would be equivalent to build failures. Flexible constructors for Python API. The types generated by collections.namedtuple() or typing.NamedTuple don't offer flexible constructors with named parameters. I think all codes that create transaction objects today would benefit from having constructors with default values, and I'll be providing those to create corresponding proto objects.","title":"Code Quality Improvements"},{"location":"beancount_v3.html#tolerances-precision","text":"The story around how precision and tolerances are dealt with hasn't been great, for two reasons: Explicit tolerance option. I've tried to design the tolerance (used for balancing transactions) to be automatic and automatically inferred from statistics from the numbers in the input. The results aren't great. In Vnext I aim to provide an explicit option for setting the tolerance per currency. Precision. There are various places where numbers get rendered in v2: the reports code, the SQL query, and debugging scripts, and the way precision is set hasn't been used consistently. The precision also needs to be explicitly settable by the user. Rounding. There is another quantity that's used during interpolation: the precision used to round calculated numbers. Moreover, there is a need to distinguish between the precision and tolerances for numbers when used as prices vs. when used as units (see here ). One way is to store the display context per currency PAIR, not per currency itself. The distinction between these quantities hasn't been documented well; I'll keep in mind to clearly annotate those codes in Vnext and add suitable docs for this. Mostly the precision will be a rendering concern and a quantity that will be relevant for the new universal SQL query tool. Some prior design documentation exists here .","title":"Tolerances & Precision"},{"location":"beancount_v3.html#core-improvements","text":"Some desiderata of new features are discussed below. These are all relevant to the core. Note that the changes should not interfere with current usage much, if at all. I expect that v2 users will be largely unaffected and won't have to change their ledger files.","title":"Core Improvements"},{"location":"beancount_v3.html#booking-rules-redesign","text":"Main document One of the current problems with booking is that entering an augmenting leg and a reducing leg have to be different by nature. The augmentation leg has to provide the cost basis via {...} syntax, and the reducing leg has to enter the price in the annotation and not in the cost basis. For example: 2021-02-24 * \"BOT +1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @143.75\" Assets:US:Ameritrade:Futures:Options 1 QNEG21C13100 {2875.00 USD} contract: 143.75 USD ... 2021-02-24 * \"SOLD -1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @149.00\" Assets:US:Ameritrade:Futures:Options -1 QNEG21C13100 {} @ 2980.00 USD contract: 149.00 USD ... Notice how the selling transaction has to be written down differently from the perspective of the user. The thing is, this makes it difficult from the perspective of the importer writer. It also ties the required syntax with the state of the inventory it's applied to, as it assumes something about this inventory. Moreover, this makes it difficult to write an importer that would handle a crossing of the absolute position, like this: 2021-02-19 * \"BOT +1 /NQH21:XCME @13593.00\" Assets:US:Ameritrade:Futures:Contracts 1 NQH21 {271860.00 USD} contract: 13593.00 USD Assets:US:Ameritrade:Futures:Margin -271860.00 USD Expenses:Financial:Commissions 2.25 USD Expenses:Financial:Fees 1.25 USD Assets:US:Ameritrade:Futures:Cash -3.50 USD 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -2 NQH21 {271815.00 USD} contract: 13590.75 USD Assets:US:Ameritrade:Futures:Margin 543630.00 USD Income:US:Ameritrade:Futures:PnL 45.00 USD Expenses:Financial:Commissions 4.50 USD Expenses:Financial:Fees 2.50 USD Assets:US:Ameritrade:Futures:Cash -52.00 USD The issue here is that we're crossing the flat line, in other words, we go from long one to short one. There are only two ways to do that properly right now: Disable booking and use the cost only, as per above. This is not great \u2014 booking is terribly useful. Track the position in your importer and separate the reducing and augmenting legs: 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {271815.00 USD} Both solutions aren't great. So I came up with something new: a complete reevaluation of how the syntax is to be interpreted. In fact, it's a simplification. What we can do is the following: use only the price annotation syntax for both augmentation and reduction and currency conversions, with a new booking rule \u2014 Match lots without cost basis in priority. If the lots have no cost basis, the weight of this posting is simply the converted amount, as before. If a match has been made against a lot with cost basis, the weight of this posting is that implied by the matched lots. Make the {...} used solely for disambiguating lots to match, and nothing else. If you have unambiguous matches, or a flexible booking strategy, e.g. FIFO, you'd pretty much never have to use the cost matching reduction. With this, the futures transaction above would simply use the @ price annotation syntax for both transactions. It would Make importers substantially simpler to write Supports futures naturally Be backward compatible with existing inputs for both currency conversions and investments. It is also more generally consistent and friendlier to Ledger users, without sacrificing any of the tighter constraints Beancount provides. I think it's even simpler to think about. Furthermore, this: Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD would be interpreted as \"match this lot, but only those with a cost basis attached to them.\" One question that remains is to decide whether an augmentation \u2014 now written down simply with @ price annotation \u2014 would store the cost basis in the inventory or not. I think we could make this determination per-commodity, or per-account. This would impose a new constraint: a commodity (or \"in an account\") would always be stored with cost basis, or not.","title":"Booking Rules Redesign"},{"location":"beancount_v3.html#posting-vs-settlement-dates","text":"When you import a transaction between multiple accounts within a single ledger, e.g. a credit card payment from one's checking account, the dates at which the transaction posts in each account may differ. One side is called the \"transaction date\" or \"posting date\" and the other side the \"settlement date.\" Where the money lives in between is somewhere in limbo (well in practice there is no money at all, just differences in accounting between institutions, things are never reflected instantly). One of the major shortcomings of the current core code is that the ability to insert a single transaction with postings at different dates is missing. Users are recommended to select a single date and fudge the other one. Some prior discussion on this topic exists here . Unfortunately, this method makes it impossible to represent the precise posting history on at least one of the two accounts. A good solution needs to be provided in Vnext, because this is a very common problem and I'd like to provide a system that allows you to precisely mirror your actual account history. The automatic insertion of transfer accounts to hold the commodities can be implemented as a feature, and it should live in the core. One possible idea would be to allow optional posting dates, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD which would result in two transactions behind the scenes, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD Equity:Transfer 2020-01-21 * \"ONLINE PAYMENT - THANK YOU\" \"\" Liabilities:US:Amex:BlueCash 2397.72 USD Equity:Transfer The lack of symmetry here raises the question of whether we should allow a transaction without a date or not: * \"ONLINE PAYMENT - THANK YOU\" \"\" 2020-01-19 Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD I think we can figure this out and the first solution is very doable. Input Split Transactions Some users like to organize their inputs in different files, or in different sections that strictly contain all of an account's transactions in order. This is related in spirit to the posting and settlement dates problem: at the moment the user is required to choose one of the two locations to insert their transaction. This should not be necessary. We should provide a mechanism that would allow users to insert the halves of a transaction into two different locations in their file, and a robust merging mechanism that would ensure that the two related transactions have been matched and merged (so that no unmerged half remains) and otherwise report errors clearly. The two halves could look like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD \u2026 2020-01-21 * \"AMEX EPAYMENT ACH PMT; DEBIT\" Liabilities:US:Amex:BlueCash 2397.72 USD The matching could be done via explicit insertion of special links, or by heuristics to match all such related transactions (perhaps declaring valid account pairs, thresholding on date differences and exactly matching amounts). When impossible to match, an error should be raised. Those merged transactions should be checked for balancing. Note how each of the transactions has a differing date; this would integrate with the transfer account solution proposed in the previous section. I haven't designed something yet, but this should be easy to implement and should be provided as a core feature, since it's so closely related to the input syntax.","title":"Posting vs. Settlement Dates"},{"location":"beancount_v3.html#currency-accounts-instead-of-a-single-conversion","text":"The current implementation of multiple currency transactions relies on a special \"conversion transaction\" that is automatically inserted at reporting time (when closing the year) to account for the sum total of imbalances between currencies. The goal of this transaction is to ensure that if you just sum up all the postings in the book, the result is purely an empty inventory (and not some residual amount of profit or loss incurred during currency exchange across different rates \u2014 note that we're talking only of the @price syntax, not investments). This is a bit of a kludge (the transaction itself does not balance, it converts to zero amounts of a fictional currency in order to keep itself quietly passing the balance test). What's more, its actual value is dependent on a subset of filtered transactions being summed up so it's a reporting-level construct, see here . There exists a method for dealing with multiple currencies without compromising on the hermeticity of individual transactions, described online, here . Using that method, you can filter any subset of transactions and summing them up will cleanly cancel out all lots. You don't need to insert any extra weight to fix up the balance. Also, you can explicitly book profits against the accrued gains in the currency accounts and zero them out and take advantage of this when you report them (and track them over time). The downside is that any currency conversion would see extra postings being inserted, etc. 2020-06-02 * \"Bought document camera\" Expenses:Work:Conference 59.98 EUR @ USD Liabilities:CreditCard -87.54 USD Equity:CurrencyAccounts:EUR -59.98 EUR Equity:CurrencyAccounts:USD 87.54 USD The problem is that it's a pain to use this method manually, it requires too much extra input. It's possible to have Beancount do that for us behind the scenes, completely automatically. I coded a proof-of-concept implementation here , but it's incomplete . In Vnext: The insertion of the kludgey conversions transactions should be removed. The currency accounts should become the norm. The fact that the two streams of directives will be very clearly separated should help, by distinguishing even more clearly between the parsing representation and the fully booked one, which will show these extra legs on transactions The prototype should be completed and issues fixed completely (not that much work involved).","title":"Currency Accounts instead of a Single Conversion"},{"location":"beancount_v3.html#strict-payees","text":"I'm not sure if this makes sense yet, but I'd like to clean up the mess that payee strings are today. Payees are free-form, and if the user does not take care to clean them up\u2014and I'm one of those who doesn't\u2014the memos from imported sources are messy. It could be interesting to create a new directive to declare payee names ahead of time and an optional model that would require payees to be found in the list of declared payee names. Payees would have to have open and close dates, dates which would define the valid duration of the relationship with that payee (thereby adding more error verification capability).","title":"Strict Payees"},{"location":"beancount_v3.html#price-inference-from-database","text":"Interpolation from price database. One of the oft-requested features is the ability to automatically interpolate prices from the internal price database history. I think that this should be doable unambiguously and deterministically and added to the interpolation algorithm. Price validation. Since a lot of the conversions at price (i.e., using \"@\") are inferred by leaving out one number, we should validate that the effective price is within some tolerance of a pre-existing price point near the date. This would provide yet another level of checking.","title":"Price Inference from Database"},{"location":"beancount_v3.html#quantizing-operators","text":"Another useful addition to the syntax would be operators that automatically quantize their result to a precision that depends on the particular target currency. For example, 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 / 1.19 EUR Expenses:Food:Taxes 2.13 / 1.19 * 0.19 EUR ; for example to calculate tax Assets:Cash That would become: 1970-01-01 * \"coffee\" Expenses:Food:Net 1.789915966386555 EUR Expenses:Food:Taxes 0.340084033613445 EUR Assets:Cash If instead an operator like this were provided, it would fix the issue: 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 /. 1.19 EUR Expenses:Food:Taxes (2.13 / 1.19 * 0.19). EUR Assets:Cash Or somesuch. Or maybe we'll want to add an option such that every evaluation of an arithmetic expression is automatically quantized as such.","title":"Quantizing Operators"},{"location":"beancount_v3.html#constraints-system-budgeting","text":"Beancount does not support budgeting constraints explicitly, but I think it would be possible to extend the balance assertion semantics to cover this. The current balance assertions check (a) a single commodity, and (b) that the amount is precisely equal to an expected one. Balance assertions should be extended to support inequalities, e.g., 2020-06-02 balance Liabilities:CreditCard > 1000.00 USD and perhaps we could check for the total inventory like this 2020-06-02 balanceall Assets:Cash 200.00 USD, 300.00 CAD I'd be curious to hear what would work best from users who do budgeting and design a minimalistic expression language to support that use case (though I'd try to keep it as simple as possible to avoid feature creep). Also, if the syntax is getting changed, adding a syntax that allows checking multiple currencies at once, and possibly a complete assertion that checks there aren't other commodities in the account could also make sense.","title":"Constraints System & Budgeting"},{"location":"beancount_v3.html#average-cost-booking","text":"Average cost booking has been discussed and a good solution sketched out a very long time ago. Vnext should sport that method natively; a lot of users want to have this feature for dealing with their tax-deferred accounts. It takes a bit of work to handle the precision of the various automated conversions right. The way it would work is by automatically merging all related lots of the same commodity on a reduction, and optionally on an augmentation. Some constraints may be required (e.g. only a single commodity in that account).","title":"Average Cost Booking"},{"location":"beancount_v3.html#trade-matching-reporting","text":"A few core tasks related to P/L and trading still need to be implemented. Trade list. A problem that I've really been wanting to solve for a very long time but never found the time for is to save crumbs from the booking process so that a correct list of trade pairs could be easily extracted from the list of directives. I wrote some notes here and here a long time ago. Essentially, for each booked reduction, insert a reference to the corresponding augmenting posting. I've prototyped this as metadata but it should be made something more official. A single linear scan can pick up these references, build a mapping and recover the (buy, sell) pairs to produce a table of trades. There's a precedent I once wrote in a plugin . Needless to say, producing a list of trades is a pretty basic function that Beancount does not provide out of the box today; it really should. Right now users write their own scripts. This needs to be supported out-of-the-box. Harmonize balance and gains validation. Checking that transactions balance and that income from gains balance with a transaction's prices (the sellgains plugin) are done in completely separate places. Those two codes occupy similar roles, and should be implemented next to each other. Commissions in P/L. Properly counting profits & losses by taking off the fraction of buying commission of an original lot and the selling commission into account is not possible at the moment. I think it could be done with a plugin that moves some of the (computed) income leg into a separate negative income account to do this properly for reporting purposes.","title":"Trade Matching & Reporting"},{"location":"beancount_v3.html#self-reductions","text":"Currently the application of reductions operates on the inventory preceding the transaction. This prevents the common case of self-reductions, and both I and some users have come across this problem before, e.g. this recent thread ( ticket ). This comes off as unintuitive to some users and ought to have a better solution than requiring splitting of transactions. Since we're rewriting the booking code entirely in Vnext, contemplate a new definition that would provide a well-defined behavior in this case. I remember from prior experiments attempting to implement this that it wasn't a trivial thing to define. Revisit. This would be a nice improvement.","title":"Self-Reductions"},{"location":"beancount_v3.html#stock-splits","text":"Some discussion and perhaps a strategy for handling stock splits should be devised in Vnext. Right now, Beancount ignores the issue. At the minimum this could be just adding the information to the price database. See this document for more details.","title":"Stock Splits"},{"location":"beancount_v3.html#multipliers","text":"Options have a standard contract size of 100. Futures have a contract size that depends on the particular instrument (e.g., /NQ with a multiplier of 20). I've been handling this for options by multiplying the units by 100, and for futures by multiplying the contract size by the per-contract multipliers (ditto for options on futures). I do this in the importers. For options, it works and it isn't too bad (e.g. positions of -300 instead of -3), but for futures, it's ugly. The result is not representative of the actual transaction. I'd like to add a per-currency multiplier, as well as a global dictionary of regexps-to-multiplier to apply, and for this to be applied everywhere consistently. One challenge is that everywhere there's a cost or price calculation, this has to be applied. In the current version, those are just multiplications so in many parts of the codebase these haven't been wrapped up in a function that could easily be modified. This needs to happen in a big rewrite \u2014 this is the opportunity to do this. Example here.","title":"Multipliers"},{"location":"beancount_v3.html#returns-calculations","text":"If you look at investment brokers out there, no one calculates returns correctly. Brokers provide one of two features: No cash transfers. Total value of account today vs. total value of account at some point in the past (i.e., account inception or beginning of the year). This isn't very useful because they never account for the addition or removal of cash to the account. For example, say you open an account with $100,000 and invest, and mid-year add another $20,000, say the original investments are now worth $95,000, the report would show a gain of $15,000, whereas you really incurred a loss. Better brokers like Vanguard will show a plot that includes two overlaid bars, one with cash added and profit overlaid, like this: Lack of interest or dividends. Other brokers will report P/L over time from the investments, but they fail to account for actual interest or dividends received (they only look at the price of the underlying) so that's not useful for bonds or for stocks with significant dividends, or when grouping them, they fail to account for the addition of new positions over time. Counterfactual performance. Finally, all of them fail to compare your actual annualized performance with that of a benchmark portfolio with equivalent cash infusions. For example, instead of your actual investments made, compare with the performance you would have obtained if you had invested in some standardized portfolio of investments over that particular time period, given the actual historical prices of those instruments. Ideally one should be able to define any alternative portfolio to compare against using their particular cash transfers. More fancy analyses aren't even contemplated, e.g., what would have been the impact of changing my rebalancing strategy (or actually implementing a more strict one)? There are well known methods for both time-based and value-based returns reporting. The right thing to do is to extract a time-series of cash flows and compute the annualized or IRR returns, or value-weights. I started this work at some point and ran against some difficulties and eventually removed it. The results remain here. I'd really love to build this, and eventually perhaps this could grow into its own project, with associated support functions in the Beancount core. This will possibly be a project of its own, but this requires similar support for enumerating instruments and price sources as that which is needed for fetching prices, as well as functions for isolating cash flows for specific subsets of accounts; these should probably live in the core. UPDATE September 2020: This has mostly been implemented. See this document for details.","title":"Returns Calculations"},{"location":"beancount_v3.html#unsigned-debits-and-credits","text":"A useful idea that's nearly trivial to implement is to allow users to input all positive unit numbers and to automatically flip the signs on input, and to output them all as positive numbers as well splitting them up between Debit and Credit columns. This would make Beancount's rendering a lot easier to grok for people with an existing background in accounting. This feature will introduce no complexity, easy to add for free. See TODO here .","title":"Unsigned Debits and Credits"},{"location":"beancount_v3.html#holdings","text":"One of the libraries I built at some point was this notion of a \"holding\", here . At the time I wasn't sure if that data would contain much more than what's in a posting, but as it turns out, representing holdings as just the posting is good enough, all you need is the market value, and compute the total values from the units is trivial. In fact, I've haven't been using that code for years, I've been using the export script instead, which writes out a table that gets uploaded to a Google Sheets doc. This proves to me aggregating the positions in an Inventory is plenty sufficient in practice, along with a mapping of latest market prices. I'm going to delete that code. It's only been used in the reports code anyway, which will be removed anyway, and in the experimental \" unrealized gains \" plug, which was only a proof-of-concept that convinced me booking such gains as transactions is not a good idea and which will get removed and live in a separate experimental repository anyway.","title":"Holdings"},{"location":"beancount_v3.html#tooling-for-debugging","text":"Context should recover erroneous transactions. One of the major annoyances of error recovery is that if a transaction involves some types of errors, the postings aren't produced in the stream of directives. This problem is related to the lack of clarity between the merely parsed data structure and the fully resolved one. In particular, this means that the \"bean-doctor context\" debugging command often cannot provide useful context around a failing transaction. This really needs to be fixed, to improve debuggability. Document debugging tools. In general, I should write a better exposition of how to use the various transaction debugging tools; a lot of the questions on the mailing-list would disappear if users knew better how to leverage those. Interactive context in Emacs. If the performance allows it, we could build an Emacs mode which renders the context around a partially written transaction, including inventories of the affected accounts before and after the transaction, as well as interpolated values, to a different buffer updated interactively. This would make it much more fun to input data and provide immediate feedback about the newly inserted transaction.","title":"Tooling for Debugging"},{"location":"beancount_v3.html#documentation-improvements","text":"Remove dependency on furius.ca. The current Google Docs based documentation links to other documents via a global redirect (the definition is found here ). While it does not happen often that my web server goes down (perhaps a few times per year), when it does it takes a few days to rectify the situation. That server is hosted graciously in the company of some friends of mine. Kirill has proved that it would be possible to replace all the links to redirects on github, that would look like this: beancount.github.io/ instead of furius.ca/beancount/doc/ . In order to do this, I'll need to run a script using the Docs API on all the Google Docs to change them automatically.","title":"Documentation Improvements"},{"location":"beancount_v3.html#conclusion","text":"There are other items in the TODO file . These are just the main, big issues that I think matter the most and I'd like to address them in a Vnext rewrite. Development branches will look like this: v2 : Current master will be branched to \"v2\", which will track the stable current version. That branch will build with both the current setup.py system and Bazel. Fixes will be implemented on that branch where possible, and merged to Vnext. master : Current master will become Vnext. Only the Bazel build will be supported on that branch. Any comments appreciated.","title":"Conclusion"},{"location":"beancount_v3.html#appendix","text":"More core ideas for Vnext that came about during discussions after the fact.","title":"Appendix"},{"location":"beancount_v3.html#customizable-booking","text":"For transfer lots with cost basis\u2026 an idea would be to create a new kind of hook, one that is registered from a plugin, e.g. a callback of yours invoked by the booking code itself, and whose results applied to a transaction are immediately reflected on the state of the affected inventories. Maybe this is the right place to provide custom algorithms so that their impact is affecting the subsequent inventories correctly and immediately. Now, imagine generalizing this further to provide and implement all of the current booking mechanisms that are currently built in the core. Call this \"customizable booking.\" ( thread ).","title":"Customizable Booking"},{"location":"beancount_v3.html#ugly-little-things","text":"print_entry() uses buffering that makes it impossible to use regular print() interspersed with the regular stdout without providing file= option. Fix this, make this regular instead, that's just annoying, just print to regular stdout. The default format for __str__ for inventories puts () around the rendering. When there's a single position, that looks like a negative number. That's dumb. Use {} instead, or something else. Add a flag to bean-check to make it run --auto plugins by default. This is great for imported files, which may not have a complete ledger to feed in.","title":"Ugly Little Things"},{"location":"beancount_v3.html#incremental-booking-beancount-server-emacs-companion","text":"In order to make recomputation fast, the idea of creating a standalone \"Beancount server\" starts to make sense. The expensive part of the Beancout calculations on a large file is the booking and interpolation. The key to making things fast is thus to keep all the original unprocessed transactions in memory along with the booked and interpolated ones, and on a change, reparse the modified files and scan all the transactions, updating only the ones whose accounts have been affected. This could be problematic in theory: some plugins may rely on non-local effects in a way that affects what they output. I believe in practice it would work 99% of the time. But I think it may be worth a prototype. On the other hand, Vnext may turn out to be fast enough recomputing everything from scratch every single time (my own file went from 4s -> 0.3ms for the parsing stage of the largest file), so maybe this is irrelevant overall. Such a server would be a perfect companion to a running Emacs. We could build an Emacs mode which communicates with the server.","title":"Incremental Booking/ Beancount Server / Emacs Companion"},{"location":"beancount_v3.html#tags-links-merge-with-metadata","text":"TODO(blais): Add colon syntax","title":"Tags & Links Merge with MetaData"},{"location":"beancount_v3_dependencies.html","text":"Beancount C++ version: Dependencies \uf0c1 Martin Blais , June 2020 Beancount is going to get rewritten in C++, here is the set of dependencies I've tested and that I'm comfortable maintaining in the long run: Base environment \uf0c1 Bazel build ( https://github.com/bazelbuild/bazel ): The Google build system is the most stable approach to build that I know of, much better than SCons and certainly much better than CMake. It allows you to pin down a specific set of dependencies by explicitly referencing other repositories and git repos at specific commit revisions (including non-released ones), and the sometimes annoying constraints it imposes results in hermetically reproducible builds like no other build system can do. This minimizes surprises and hopefully the number of platform-dependent portability issues. It also minimizes the amount of pre-installed packages we assume your system has (e.g. it'll download and compile its own Bison, for example). It runs fast, computes the minimal set of tests and targets to rebuild, and is highly configurable. The downside of choosing Bazel is the same of other Google-issued open source projects: the original version of that product is internal and as a result there are a lot of strange idiosyncrasies to deal with (e.g. //external, the @bazel_tools repo, etc.), many of which are poorly documented outside the company and with a good number of unresolved tickets. However, at this stage I've already managed to create a working build with most of the dependencies described in this section. C++14 with GCC and Clang/LLVM : Both compilers will be supported. Clang provides a much better front-end and stdlib implementation but is a little slower to build. GCC is more commonly present in the wild but the error messages are\u2026 well, we all got used to this I suppose. Note that despite requiring C++14, I will refrain from using exotic features of the language (including classes). There may be questions about Windows support. Abseil-Cpp base library ( https://github.com/abseil/abseil-cpp ): The base library of functions is issued from Google's own gigantic codebase and has been battle-hardened and tested like no other\u2014this is what the Google products run on. This provides a most stable API to work with (it's unlikely to change much given how much code depends on it), one which complements stdc++ well, and whose existing contact surfaces are bound to remain pretty static. It's simpler and more stable than Boost, and doesn't offer a myriad of libraries we're not going to need anyway (plus, I love Titus' approach to C++).. This fills in a lot of the basic string manipulation functions you get for free in Python but crave in C++ (e.g. absl::StrCat). Google Test ( https://github.com/google/googletest ): This is the widely used C++ testing framework I'm already familiar with, which supports matchers and mocking. Data representation \uf0c1 Protocol Buffers ( https://github.com/protocolbuffers/protobuf ): I will maintain a functional style in this C++ rewrite and I need a replacement for Python's nametuples to represent directives. This means creating a lot of simple naked structured data that will need to be created dynamically from within tests (there's a good text-format parser) and also serialized to disk as the boundary between the core and query language will become a file of protobuf messages. Protobuf provides a good hierarchical data structure with repeated fields that is supported in many languages (this opens the door potentially to plugins written in e.g., Go), and it's possible to provide Python bindings for them. It will also become the interface between the Beancount's core and the input to the query language. We will be using proto3 with version >=3.12 in order to have support for optional presence fields (null values). Riegeli (https://github.com/google/riegeli) : An efficient and compressed binary format for storing sequences of protobuf messages to files. I think the Beancount core will output this; it's compact and reads fast. It's also another Googlething that ought to receive more attention than it does and supports both C++ and Python and protobufs. mpdecimal ( https://www.bytereef.org/mpdecimal/ ) : This is the same C-level library used by Python's implementation of Decimal numbers. Using this library will allow to easily operate between the C++ core and Python's runtime. I need to represent decimal numbers in C++ memory with minimal functionality and reasonably small range (BigNum classes are typically more than what we need). We don't need much of the scope for decimal\u2026. basic arithmetic operations + quantizing, mainly. There are other libraries out there: GMP , decNumber . There is some information on this thread: ( https://stackoverflow.com/questions/14096026/c-decimal-data-types . For on-disk representation, I will need a protobuf message definition for those, and I'm thinking of defining a union of string (nice to read but lots of conversions from string to decimal) with some more efficient exponent + mantissa decimal equivalent. Parser \uf0c1 RE/flex lexer ( https://github.com/Genivia/RE-flex ): This modern regexp-based scanner generator supports Unicode natively and is very fast and well documented. It provides a great alternative to the aging GNU flex which made it difficult to support non-ASCII characters outside of string literals (i.e.., for account names). I've had success using it on other projects. Many users want account names in their home language; this will make it easy to provide a UTF-8 parser for the entire file. GNU Bison ( https://git.savannah.gnu.org/git/bison.git ): We will stick with GNU Bison, but instead use the C++ complete modes it supports. I'm hesitating continuing with this parser generator as it's showing its age but it's pretty stable and I can't quite justify the extra work to upgrade to ANTLR. We will have to pull some tricks to support the same grammar for generating C code for v2 and C++ code for the next version; the parser code could be provided with a dispatch table of functions, which would be static C functions in v2, and methods in a C++ version. Some of the generation parameters (% directives) will be different (see here for an example). I nternational Components for Unicode (ICU) ( https://github.com/unicode-org/icu.git ): This is the standard library to depend on for Unicode support. Our C++ will not use std::wstring/std::wchar, but rather regular std::string and function calls to this library where necessary. Python \uf0c1 Python3 (https://www.python.org/) : Not much to say. I will keep using the latest version. Python is a tank of an extension language and no plans to change that. pybind11 ( https://github.com/pybind/pybind11 ): I want to provide a Python API nearly identical to the current one in Beancount, or better (which means simpler). One of the requirements I've had is to make it cheap to pass a list of protobuf objects for the directives to a Python callback, without copying (serializing and deserializing) between C++ and Python\u2014for plugins. I've investigated multiple libraries to interoperate between Python and C++: Cython, CLIF, SWIG, etc. and serialization is a problem (see this partial solution ). The one that seems to have the most momentum at the moment is pybind11, a pure header library which is an evolution from Boost::Python, that offers the most control over the generated API. It also works well with protocol buffer targets built with fast_cpp_protos: only pointers are passed through, so plugins passing in and out the full list of directives should be possible. I also happen to be familiar with Boost::Python having used it 20 years ago, it's really quite similar actually (but does away with the rest of Boost). Type annotations , PyType (or MyPy ?): I've already been compliant to a custom configuration of PyLint for Python but the codebase does not use the increasingly ubiquitous type annotations . In the rewritten subset of the code that will remain, I'd like to have all functions annotated and to replace the sometimes redundant Args/Returns docstrings with a more free-form documentation (the types may be sufficient to avoid the formalism of Args/Returns blocks). I'll have to see how this affects the auto-generated docs . An important addition is that I want to start not only annotating, but running one of the type checkers automatically as part of the build. I'm already familiar with Google's pytype, but perhaps mypy is a good alternative. In any case, the only hurdle for that is to craft Bazel rules that invoke these automatically across the entire codebase, as part of py_library() and py_binary() rules. I'll also attempt to make pylint run in the same way (as part of the build) with a custom flag to disable it during development, instead of having a separate lint target. Subpar ( https://github.com/google/subpar ): It's not clear to me yet how to perform a pip-compatible setup.py for a Bazel build, but surely we can find a way to build wheels for PyPI using the binaries built by Bazel. For packaging a self-contained binary of Python + extensions, the \"subpar\" Bazel rules is supposed to handle that. However, at the moment it does not support C extensions .","title":"Dependencies"},{"location":"beancount_v3_dependencies.html#beancount-c-version-dependencies","text":"Martin Blais , June 2020 Beancount is going to get rewritten in C++, here is the set of dependencies I've tested and that I'm comfortable maintaining in the long run:","title":"Beancount C++ version: Dependencies"},{"location":"beancount_v3_dependencies.html#base-environment","text":"Bazel build ( https://github.com/bazelbuild/bazel ): The Google build system is the most stable approach to build that I know of, much better than SCons and certainly much better than CMake. It allows you to pin down a specific set of dependencies by explicitly referencing other repositories and git repos at specific commit revisions (including non-released ones), and the sometimes annoying constraints it imposes results in hermetically reproducible builds like no other build system can do. This minimizes surprises and hopefully the number of platform-dependent portability issues. It also minimizes the amount of pre-installed packages we assume your system has (e.g. it'll download and compile its own Bison, for example). It runs fast, computes the minimal set of tests and targets to rebuild, and is highly configurable. The downside of choosing Bazel is the same of other Google-issued open source projects: the original version of that product is internal and as a result there are a lot of strange idiosyncrasies to deal with (e.g. //external, the @bazel_tools repo, etc.), many of which are poorly documented outside the company and with a good number of unresolved tickets. However, at this stage I've already managed to create a working build with most of the dependencies described in this section. C++14 with GCC and Clang/LLVM : Both compilers will be supported. Clang provides a much better front-end and stdlib implementation but is a little slower to build. GCC is more commonly present in the wild but the error messages are\u2026 well, we all got used to this I suppose. Note that despite requiring C++14, I will refrain from using exotic features of the language (including classes). There may be questions about Windows support. Abseil-Cpp base library ( https://github.com/abseil/abseil-cpp ): The base library of functions is issued from Google's own gigantic codebase and has been battle-hardened and tested like no other\u2014this is what the Google products run on. This provides a most stable API to work with (it's unlikely to change much given how much code depends on it), one which complements stdc++ well, and whose existing contact surfaces are bound to remain pretty static. It's simpler and more stable than Boost, and doesn't offer a myriad of libraries we're not going to need anyway (plus, I love Titus' approach to C++).. This fills in a lot of the basic string manipulation functions you get for free in Python but crave in C++ (e.g. absl::StrCat). Google Test ( https://github.com/google/googletest ): This is the widely used C++ testing framework I'm already familiar with, which supports matchers and mocking.","title":"Base environment"},{"location":"beancount_v3_dependencies.html#data-representation","text":"Protocol Buffers ( https://github.com/protocolbuffers/protobuf ): I will maintain a functional style in this C++ rewrite and I need a replacement for Python's nametuples to represent directives. This means creating a lot of simple naked structured data that will need to be created dynamically from within tests (there's a good text-format parser) and also serialized to disk as the boundary between the core and query language will become a file of protobuf messages. Protobuf provides a good hierarchical data structure with repeated fields that is supported in many languages (this opens the door potentially to plugins written in e.g., Go), and it's possible to provide Python bindings for them. It will also become the interface between the Beancount's core and the input to the query language. We will be using proto3 with version >=3.12 in order to have support for optional presence fields (null values). Riegeli (https://github.com/google/riegeli) : An efficient and compressed binary format for storing sequences of protobuf messages to files. I think the Beancount core will output this; it's compact and reads fast. It's also another Googlething that ought to receive more attention than it does and supports both C++ and Python and protobufs. mpdecimal ( https://www.bytereef.org/mpdecimal/ ) : This is the same C-level library used by Python's implementation of Decimal numbers. Using this library will allow to easily operate between the C++ core and Python's runtime. I need to represent decimal numbers in C++ memory with minimal functionality and reasonably small range (BigNum classes are typically more than what we need). We don't need much of the scope for decimal\u2026. basic arithmetic operations + quantizing, mainly. There are other libraries out there: GMP , decNumber . There is some information on this thread: ( https://stackoverflow.com/questions/14096026/c-decimal-data-types . For on-disk representation, I will need a protobuf message definition for those, and I'm thinking of defining a union of string (nice to read but lots of conversions from string to decimal) with some more efficient exponent + mantissa decimal equivalent.","title":"Data representation"},{"location":"beancount_v3_dependencies.html#parser","text":"RE/flex lexer ( https://github.com/Genivia/RE-flex ): This modern regexp-based scanner generator supports Unicode natively and is very fast and well documented. It provides a great alternative to the aging GNU flex which made it difficult to support non-ASCII characters outside of string literals (i.e.., for account names). I've had success using it on other projects. Many users want account names in their home language; this will make it easy to provide a UTF-8 parser for the entire file. GNU Bison ( https://git.savannah.gnu.org/git/bison.git ): We will stick with GNU Bison, but instead use the C++ complete modes it supports. I'm hesitating continuing with this parser generator as it's showing its age but it's pretty stable and I can't quite justify the extra work to upgrade to ANTLR. We will have to pull some tricks to support the same grammar for generating C code for v2 and C++ code for the next version; the parser code could be provided with a dispatch table of functions, which would be static C functions in v2, and methods in a C++ version. Some of the generation parameters (% directives) will be different (see here for an example). I nternational Components for Unicode (ICU) ( https://github.com/unicode-org/icu.git ): This is the standard library to depend on for Unicode support. Our C++ will not use std::wstring/std::wchar, but rather regular std::string and function calls to this library where necessary.","title":"Parser"},{"location":"beancount_v3_dependencies.html#python","text":"Python3 (https://www.python.org/) : Not much to say. I will keep using the latest version. Python is a tank of an extension language and no plans to change that. pybind11 ( https://github.com/pybind/pybind11 ): I want to provide a Python API nearly identical to the current one in Beancount, or better (which means simpler). One of the requirements I've had is to make it cheap to pass a list of protobuf objects for the directives to a Python callback, without copying (serializing and deserializing) between C++ and Python\u2014for plugins. I've investigated multiple libraries to interoperate between Python and C++: Cython, CLIF, SWIG, etc. and serialization is a problem (see this partial solution ). The one that seems to have the most momentum at the moment is pybind11, a pure header library which is an evolution from Boost::Python, that offers the most control over the generated API. It also works well with protocol buffer targets built with fast_cpp_protos: only pointers are passed through, so plugins passing in and out the full list of directives should be possible. I also happen to be familiar with Boost::Python having used it 20 years ago, it's really quite similar actually (but does away with the rest of Boost). Type annotations , PyType (or MyPy ?): I've already been compliant to a custom configuration of PyLint for Python but the codebase does not use the increasingly ubiquitous type annotations . In the rewritten subset of the code that will remain, I'd like to have all functions annotated and to replace the sometimes redundant Args/Returns docstrings with a more free-form documentation (the types may be sufficient to avoid the formalism of Args/Returns blocks). I'll have to see how this affects the auto-generated docs . An important addition is that I want to start not only annotating, but running one of the type checkers automatically as part of the build. I'm already familiar with Google's pytype, but perhaps mypy is a good alternative. In any case, the only hurdle for that is to craft Bazel rules that invoke these automatically across the entire codebase, as part of py_library() and py_binary() rules. I'll also attempt to make pylint run in the same way (as part of the build) with a custom flag to disable it during development, instead of having a separate lint target. Subpar ( https://github.com/google/subpar ): It's not clear to me yet how to perform a pip-compatible setup.py for a Bazel build, but surely we can find a way to build wheels for PyPI using the binaries built by Bazel. For packaging a self-contained binary of Python + extensions, the \"subpar\" Bazel rules is supposed to handle that. However, at the moment it does not support C extensions .","title":"Python"},{"location":"beangulp.html","text":"Beangulp \uf0c1 Martin Blais , Jan 2021 Importing data for Beancount has been supported originally by the LedgerHub project along with a library of importers, then reintegrated as a pure framework library (with some examples) in the Beancount repo as beancount.ingest, and now we're splitting up that repository again and will factor out the importing framework to another repo for easier maintenance and evolution. This document lays out some of the changes desired in this new version. New Repo \uf0c1 The new repository will be located at http://github.com/beancount/beangulp Beangulp will target compatibility with the latest beancount release from the v3 branch only. Beancount v3 is expected to evolve rapidly at the beginning, thus, to make the life of early adopters less painful, careful use of version number increments and versioned dependencies should be employed. Ideally, Beangulp should depend on the current minor version of Beancount only, for example, if Beancount 3.0.0 is released, Beangulp should declare install_requires: beancount >3.0, <3.1 See setuptools doc and PEP-440 . Status \uf0c1 As of Jan 2022, most of this proposal is complete and implemented. (Thanks to Daniele Nicolodi for doing most of the work.) Changes \uf0c1 Library Only \uf0c1 The current implementation allows one to use the bean-identify, bean-extract and bean-file tools on a \"config file\" which is evaluated Python, or create a script and call a single endpoint that will implement the subcommands. In order to make this work, a really convoluted trampoline is used to bounce the evaluation to the same code. I'll admit it trumps even me who wrote it whenever I have to go in there and edit that code. It also makes it inconvenient to let users add custom before/after customizations to their import process. The next version will support only (2). The bean-identify, bean-extract, bean-file programs will all be removed. The user will be expected to write their own script. One File \uf0c1 Right now, each importer consists of two files: the implementation file and an associated test file, e.g. soandso_bank.py soandso_bank_test.py The test file is small and usually calls out to a library function to find model files and expected outputs. Since there's hardly any real test code, we'd like to be able to have a single Python file that contains its test invocation. A new endpoint will be added to define the tests in the importer implementation. Self-Running \uf0c1 Moreover, that new function should also be the same one as that which is used to put together the configuration script. In other words, an importer's main() function should be equivalent to an importer's invocation with a single configured importer, one that is configured with the configuration used for testing. This will allow users to just \"run the importer\" on a specific set of files without having to define a configuration, by using the test configuration, like this: soandso_bank.py extract ~/Downloads/transactions.csv Having this option makes it super convenient for people to share the one file and test it out immediately, without having to create a configuration nor a main program. I don\u2019t think there is an easy clean way to implement this other than having something like if __name__ == \u2018__main__\u2019: main = Ingest([SoAndSoBankImporter()]) main() in the importer definition file. This should work right now without changes. Although, it is often the case now that importers require a bit of configuration to work (I am not sure, I don\u2019t use any of the importers distributed with Beancount or widely used). Test S ubcommand & Generate \uf0c1 Since the importers are runnable and define their test cases for pytest to run over, we should also add a subcommand \"test\" to complete \"identify\", \"extract\" and \"file\". That venue is also a great place to replace the --generate option which required ugly injection of the pytestconfig, and instead, implement it ourselves and add a second subcommand: \"genexpect\" to generate the expected file for testing. The interface becomes: soandso_bank.py identify ~/Downloads/ soandso_bank.py extract ~/Downloads/ soandso_bank.py file ~/Downloads/ ~/documents soandso_bank.py test ~/documents/testdocs soandso_bank.py generate ~/Downloads/transactions.csv This way we can remove the pytestconfig dep injection and also simplify the logic of unit tests, which had to handle both the check vs. generate scenarios. This should result in simpler code. One Expected File \uf0c1 Expected outputs from an importer are stored in multiple files with suffixes .extract, .file_name, .file_date, etc. If we had all the output in one file, the \"genexpect\" subcommand could generate everything to stdout. This is convenient. myimporter.py myimporter.beancount Leverage the Beancount syntax to store the expected values for \"file_name()\", \"file_date()\" and other outputs. Store those in Event or Custom directives and have the testing code assert on them. The new contents of a test directory should be simple pairs of (a) original downloaded file and (b) expected output, containing the transactions but also all the other method outputs. Duplicates Identification \uf0c1 This has never really worked properly. I think if this was implemented separately based on each importer \u2014 in other words, letting each importer define how to identify duplicates, e.g., if a unique transaction ID can be assumed having been inserted as a link to disambiguate \u2014 we could do this a lot cleaner. It would be ideal if each importer could specify duplicate id detection, in the importer. It could call on a more general but less reliable method, and that code should live in Beangulp. CSV Utils \uf0c1 I have a lot of really convenient utilities for slicing and dicing CSV files from ugly CSV downloads. CSV downloads often are used to house multiple tables and types of data, and a higher-level of processing is often needed on top of these files. I have code like this spread all over. This deserves a nice library. What's more, I have a nice table processing library under the Baskets project, which hasn't been given the proper quality treatment yet. Clean up my little table library and merge it with all the CSV utils. Put this in beangulp, or even contemplate making this its own project, including a CSV importer. \"CSV world.\" Not sure this matters as much after discovering petl. We could depend on petl. Caching \uf0c1 When running conversion jobs on large files, it would be nice to have a cache and avoid running those more than once. The (small) converted data could be cached and loaded back up in order to avoid running the expensive conversion more than once. One difficulty is that the conversions required to be run depend on the importers configuration, and each importer is unaware of the other ones. All the command-line arguments and at least the head of the file contents should be hashed in. This library could be pretty independent from Beancount. API Changes \uf0c1 \"file_date()\" is not clear; \"get_filing_date()\" would be. The extra argument on extract() is irregular compared to all the other methods. Find a better way? I'm not 100% sure I like my little memoizing file wrapper (\"cache\") with cache. Replace it with a disk-only one. Automatic Insertion \uf0c1 A really convenient and easily built feature that the new code should have is the automatic insertions of the extracted output to an existing ledger, before the point of a special string token in the file. Make this part of the library, as an alternative for storing the output of the importer, e.g. $ ./myimport.py extract | bean-insert ledger.beancount Amex This could also be a flag to \"extract\" $ ./myimport.py extract -f ledger.beancount -F Amex","title":"Beangulp"},{"location":"beangulp.html#beangulp","text":"Martin Blais , Jan 2021 Importing data for Beancount has been supported originally by the LedgerHub project along with a library of importers, then reintegrated as a pure framework library (with some examples) in the Beancount repo as beancount.ingest, and now we're splitting up that repository again and will factor out the importing framework to another repo for easier maintenance and evolution. This document lays out some of the changes desired in this new version.","title":"Beangulp"},{"location":"beangulp.html#new-repo","text":"The new repository will be located at http://github.com/beancount/beangulp Beangulp will target compatibility with the latest beancount release from the v3 branch only. Beancount v3 is expected to evolve rapidly at the beginning, thus, to make the life of early adopters less painful, careful use of version number increments and versioned dependencies should be employed. Ideally, Beangulp should depend on the current minor version of Beancount only, for example, if Beancount 3.0.0 is released, Beangulp should declare install_requires: beancount >3.0, <3.1 See setuptools doc and PEP-440 .","title":"New Repo"},{"location":"beangulp.html#status","text":"As of Jan 2022, most of this proposal is complete and implemented. (Thanks to Daniele Nicolodi for doing most of the work.)","title":"Status"},{"location":"beangulp.html#changes","text":"","title":"Changes"},{"location":"beangulp.html#library-only","text":"The current implementation allows one to use the bean-identify, bean-extract and bean-file tools on a \"config file\" which is evaluated Python, or create a script and call a single endpoint that will implement the subcommands. In order to make this work, a really convoluted trampoline is used to bounce the evaluation to the same code. I'll admit it trumps even me who wrote it whenever I have to go in there and edit that code. It also makes it inconvenient to let users add custom before/after customizations to their import process. The next version will support only (2). The bean-identify, bean-extract, bean-file programs will all be removed. The user will be expected to write their own script.","title":"Library Only"},{"location":"beangulp.html#one-file","text":"Right now, each importer consists of two files: the implementation file and an associated test file, e.g. soandso_bank.py soandso_bank_test.py The test file is small and usually calls out to a library function to find model files and expected outputs. Since there's hardly any real test code, we'd like to be able to have a single Python file that contains its test invocation. A new endpoint will be added to define the tests in the importer implementation.","title":"One File"},{"location":"beangulp.html#self-running","text":"Moreover, that new function should also be the same one as that which is used to put together the configuration script. In other words, an importer's main() function should be equivalent to an importer's invocation with a single configured importer, one that is configured with the configuration used for testing. This will allow users to just \"run the importer\" on a specific set of files without having to define a configuration, by using the test configuration, like this: soandso_bank.py extract ~/Downloads/transactions.csv Having this option makes it super convenient for people to share the one file and test it out immediately, without having to create a configuration nor a main program. I don\u2019t think there is an easy clean way to implement this other than having something like if __name__ == \u2018__main__\u2019: main = Ingest([SoAndSoBankImporter()]) main() in the importer definition file. This should work right now without changes. Although, it is often the case now that importers require a bit of configuration to work (I am not sure, I don\u2019t use any of the importers distributed with Beancount or widely used).","title":"Self-Running"},{"location":"beangulp.html#test-subcommand-generate","text":"Since the importers are runnable and define their test cases for pytest to run over, we should also add a subcommand \"test\" to complete \"identify\", \"extract\" and \"file\". That venue is also a great place to replace the --generate option which required ugly injection of the pytestconfig, and instead, implement it ourselves and add a second subcommand: \"genexpect\" to generate the expected file for testing. The interface becomes: soandso_bank.py identify ~/Downloads/ soandso_bank.py extract ~/Downloads/ soandso_bank.py file ~/Downloads/ ~/documents soandso_bank.py test ~/documents/testdocs soandso_bank.py generate ~/Downloads/transactions.csv This way we can remove the pytestconfig dep injection and also simplify the logic of unit tests, which had to handle both the check vs. generate scenarios. This should result in simpler code.","title":"Test Subcommand & Generate"},{"location":"beangulp.html#one-expected-file","text":"Expected outputs from an importer are stored in multiple files with suffixes .extract, .file_name, .file_date, etc. If we had all the output in one file, the \"genexpect\" subcommand could generate everything to stdout. This is convenient. myimporter.py myimporter.beancount Leverage the Beancount syntax to store the expected values for \"file_name()\", \"file_date()\" and other outputs. Store those in Event or Custom directives and have the testing code assert on them. The new contents of a test directory should be simple pairs of (a) original downloaded file and (b) expected output, containing the transactions but also all the other method outputs.","title":"One Expected File"},{"location":"beangulp.html#duplicates-identification","text":"This has never really worked properly. I think if this was implemented separately based on each importer \u2014 in other words, letting each importer define how to identify duplicates, e.g., if a unique transaction ID can be assumed having been inserted as a link to disambiguate \u2014 we could do this a lot cleaner. It would be ideal if each importer could specify duplicate id detection, in the importer. It could call on a more general but less reliable method, and that code should live in Beangulp.","title":"Duplicates Identification"},{"location":"beangulp.html#csv-utils","text":"I have a lot of really convenient utilities for slicing and dicing CSV files from ugly CSV downloads. CSV downloads often are used to house multiple tables and types of data, and a higher-level of processing is often needed on top of these files. I have code like this spread all over. This deserves a nice library. What's more, I have a nice table processing library under the Baskets project, which hasn't been given the proper quality treatment yet. Clean up my little table library and merge it with all the CSV utils. Put this in beangulp, or even contemplate making this its own project, including a CSV importer. \"CSV world.\" Not sure this matters as much after discovering petl. We could depend on petl.","title":"CSV Utils"},{"location":"beangulp.html#caching","text":"When running conversion jobs on large files, it would be nice to have a cache and avoid running those more than once. The (small) converted data could be cached and loaded back up in order to avoid running the expensive conversion more than once. One difficulty is that the conversions required to be run depend on the importers configuration, and each importer is unaware of the other ones. All the command-line arguments and at least the head of the file contents should be hashed in. This library could be pretty independent from Beancount.","title":"Caching"},{"location":"beangulp.html#api-changes","text":"\"file_date()\" is not clear; \"get_filing_date()\" would be. The extra argument on extract() is irregular compared to all the other methods. Find a better way? I'm not 100% sure I like my little memoizing file wrapper (\"cache\") with cache. Replace it with a disk-only one.","title":"API Changes"},{"location":"beangulp.html#automatic-insertion","text":"A really convenient and easily built feature that the new code should have is the automatic insertions of the extracted output to an existing ledger, before the point of a special string token in the file. Make this part of the library, as an alternative for storing the output of the importer, e.g. $ ./myimport.py extract | bean-insert ledger.beancount Amex This could also be a flag to \"extract\" $ ./myimport.py extract -f ledger.beancount -F Amex","title":"Automatic Insertion"},{"location":"calculating_portolio_returns.html","text":"Calculating Portfolio Returns \uf0c1 Martin Blais , Sept 2020 http://furius.ca/beancount/doc/returns This document describes how to compute portfolio returns from a Beancount ledger. Motivation \uf0c1 You will be surprised to find that discount brokers typically do not provide accurate and complete returns calculations for your investments based on your specific cash flows. They tend to report other measures of performance: Change in value. The simplest they provide is a snapshot of the account value at the beginning and end of the period (or year). The problem with this method is that it does not reflect your infusions or removal of cash as such, nor your changes in positions. For example, if you had an account with $50,000 at the beginning of the year and you've added in $30,000 in August, reporting a difference of $37,000 at the end of December is just not useful (you'd have to mentally discount for new invested cash during the year, and what if you want to break it down and compare the returns from different instruments in the account?). Underlying performance. They might report the growth of the underlying asset in isolation, disregarding your specific positions. It's not very useful to say \"HDV grew by 8.2% over the last year\" if you invested only (or even mostly) in the second half of the year. What I'd like is to know how much my specific investments grew, given my specific changes in positions and timings. In other words, how did I do? No dividends. Another issue is that performance is typically only reporting capital appreciation due to the change in price of the investment. Ideally one would like to break down the performance between capital appreciation and dividends and returns for each of those components, as well as overall performance, so you can compare returns from stocks and bonds to each other. You can sometimes find out that information from the Yield field in the summary, but for uneven distributions, this won't help me find out what my actual returns from dividends were, over any period. Commissions and management expenses. Some brokers charge management expenses on a monthly basis, based on the value of the account. I would like to know what my portfolio return is net of those expenses. I want accurate returns reporting, based on my actual cash flows and changes of positions over time. If I maintain your investment information in a Beancount ledger, in theory it contains all the data I need in order to compute your true returns, based on the specific timings of my own savings (cash infusions) and which positions I held at which time. It's just not in the simplest format required to do it\u2014 Beancount transactions are much more flexible than one might want and a simpler series of cash flows needs to be extracted from it. This document explains how I finally did this from my own Ledger. And how we might generalize this to yours, based on some simple rules. Most of this text is dedicated to the pedestrian details of extracting the right data. The source code can be found here . In addition, a fair and honest comparison to other investments scenarios should be possible, based on those same cash flows. For instance, you should be able to produce data that looks like \"My investments in ZZZ have returned 8.2%, 1.1% of which were from dividends, and if I'd invested in a 60/40 portfolio of broad stocks and bonds it would have returned 7.2% instead.\" In other words, I want to assess my performance relative to a number of common alternatives. (Finally, note that if all you need is a snapshot of your current positions, that's already handled by the export script .) History \uf0c1 In 2014, I made a brief attempt to break down information from my ledger to do this. At the time I got bogged down in details when some of the time-series I was extracting weren't producing what looked like sensible results (some with outliers). I got too detailed too fast. Sometimes it's better to just get the whole job done and come back for the details. I hadn't logged enough debugging information and I didn't have enough confidence in its output to use it. I never actually finished the work at the time, and eventually moved the scripts to experiments and shrugged. \"Later.\" In August 2020, I sat down to do this again, this time with a less ambitious goal of just getting a good approximation and producing lots of debug output, boiling down extraction to pull out just the cash flows and to get the job complete over all my accounts, even if it meant making some adjustments to my input file. It turned out to be the right decision: I managed to complete the task, and this document presents my journey, the method, its assumptions, quirks, and some results. Overview of Method \uf0c1 The method I'll use is to extract cash flow information for each investment. What I call \" an investment \" in this context is a kind of financial instrument , e.g., shares of \"VTI\" for the Vanguard Total Stock Market ETF, invested in a particular account , e.g. a Vanguard 401k. A list of cash flows is a dated list of positive or negative currency being invested or withdrawn as proceeds to/from that investment. So I have a list of cash flows per account, and each account records only one financial instrument. For reporting I'll group those per-account series in logical units of \" reports \", for example lumping together ones for the same instrument bought in different accounts / brokers, or different instruments that represent the same underlying by merging their cash flows. Or even to group them by \"strategy\". Basically, by merging the cash flows of multiple accounts, I have a generic way to group any subsets of accounts and compute their returns. Using the cash flows, I will then run a simple root finding routine to calculate the average annual rate those flows would have to grow in order to result in their final market value. This provides me with overall returns. This is similar to calculating the Internal Rate of Return . I will do that for the entire time series, but also for sub-intervals within the time series to compute, e.g. calendar returns (i.e., each year or quarter) or cumulative returns for trailing periods. Since cash flows are flagged as dividends or not, I can separate the returns from appreciation from the returns from dividends. Reports with plots are produced for each of the groups. Here's a diagram that shows how the \"configure\", \"compute_returns\" and \"download_prices\" scripts work together: These will be further detailed below. Configuration \uf0c1 First, a configuration needs to be created to define a list of investments, and groups of those, for which reports will be produced. This configuration is provided as a text-formatted protocol buffer message. Investment. An investment corresponds to a particular instrument stored in a particular account. It also involves other transactions that don't directly involve that particular account. We want to provide a few set of account names: Asset account. The name of the account holding the commodities for that investment. Matching accounts. A list of additional accounts which will select transactions with postings to them. Cash accounts. A list of accounts external to the investment, which will be used to flag inflows and outflows to and from the investment. Report. While we can compute returns for each investment, the more general and useful case is to compute returns on groups of them. A report is simply a list of investment names. A PDF file will be produced for each report. Here's an example of 6 investments and 3 reports: investments { # Accounts at BTrade. investment { currency: \"GLD\" asset_account: \"Assets:US:BTrade:GLD\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"AMZN\" asset_account: \"Assets:US:BTrade:AMZN\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"QQQ\" asset_account: \"Assets:US:BTrade:QQQ\" dividend_accounts: \"Income:US:BTrade:QQQ:Dividend\" cash_accounts: \"Assets:US:BTrade:Cash\" } # Accounts at IBKR. investment { currency: \"IAU\" asset_account: \"Assets:US:IBKR:IAU\" cash_accounts: \"Assets:US:IBKR:Cash\" } investment { currency: \"SLV\" asset_account: \"Assets:US:IBKR:SLV\" cash_accounts: \"Assets:US:IBKR:Cash\" } # Accounts at Schwab. investment { currency: \"GOOG\" asset_account: \"Assets:US:Schwab:GOOG\" cash_accounts: \"Assets:US:Schwab:Cash\" cash_accounts: \"Assets:AccountsReceivable\" cash_accounts: \"Assets:US:GoogleInc:GSURefund\" } } groups { group { name: \"strategy.gold\" investment: \"Assets:US:BTrade:GLD\" investment: \"Assets:US:IBKR:IAU\" } group { name: \"strategy.tech\" investment: \"Assets:US:BTrade:QQQ\" investment: \"Assets:US:BTrade:FB\" investment: \"Assets:US:Schwab:GOOG\" } group { name: \"all\" investment: \"Assets:US:*\" currency: \"USD\" } group { name: \"accounts.BTrade\" investment: \"Assets:US:BTrade:*\" currency: \"USD\" } } Different reports can include the same investment. References to accounts and investment names support simple UNIX-style globbing patterns in the configuration. These are expanded to full account names at runtime and stored in the output. A \" configure.py \" script can automatically infer a working basic configuration from an existing Beancount ledger. A report will be generated for each unique instrument and the same metadata fields honored by the \"export\" script (\"assetcls\", \"strategy\") will also generate reports. I recommend you run this on your ledger and then custom tailor the configuration manually. Finding Accounts \uf0c1 This script needs to figure out the list of available investments to report on. By convention I keep pairs of dedicated leaf accounts for each commodity type in my ledger, one to contain the actual positions (assets) and one to receive dividends, e.g., for \"VTI\" held in broker \"BTrade\", accounts like 2012-03-01 open Assets:US:BTrade:VTI VTI 2012-03-01 open Income:US:BTrade:VTI:Dividend USD This has two consequences: (a) it makes it easy to find the list of accounts that contain investments (any account with a leaf account name that is also one of the commodities found in the ledger), and (b) it nicely isolates all the activity related to each of those investments by finding all the transactions with a posting to that account . I recommend you follow the same convention in your chart of accounts. The result is a list of accounts like \"Assets:US:BTrade:VTI\", specific to each (instrument, institution). I further filter down this list to the subset of accounts which were still open up to 15 years ago (I close my accounts\u2014using Beancount's Close directive\u2014when they're done). In my particular case, I didn't have much savings back then, and there's no point in bothering to do the work to normalize those crumbs in my investing history for that far back. Extracting Cash Flow Data \uf0c1 This section describes the various steps I took to extract relevant data from my ledger. Extracting Relevant Transactions \uf0c1 For each of the identified asset accounts, we want to pull out from the ledger's transactions the list of transactions affecting that account. We simply run through the entire ledger's transactions keeping transactions with at least one posting to the investment's asset account, dividend income accounts, or to any of the other \"match accounts\" defined for it. For instance, transactions for cash dividend payments will not show an asset posting so if dividends are paid; a typical dividend payment would contain only the dividend income posting and a cash posting (for the deposit): 2019-11-27 * \"Dividend\" Income:US:BTrade:VTI:Dividend -123.45 USD Assets:US:BTrade:Cash 123.45 USD So it is necessary to include the dividend account in the configuration to include those transactions, because they typically do not involve the asset account. Account Categorization \uf0c1 The next step is to generalize transactions to templates : we record the full set of accounts involved in the extracted transactions of each investment, and assign them to general categories based on their role in the transaction. For example, if I inspect my \"VTI\" trades, I will encounter the following accounts: Assets:US:BTrade:Cash Assets:US:BTrade:VTI Expenses:Financial:Commissions Income:US:BTrade:VTI:Dividend I map each account to one of several generic categories (I could probably simplify this now): ASSET # The account holding the commodity. CASH # Cash accounts, employer matches, contributions. DIVIDEND # Dividend income account. EXPENSES # Commissions, fees and other expenses. INCOME # Non-dividend income, P/L, gains, or other. OTHERASSET # Other assets than the primary asset for this investment. OTHER # Any other account. Like this: 'Assets:US:BTrade:Cash': CASH 'Assets:US:BTrade:VTI': ASSET 'Expenses:Financial:Commissions': EXPENSES 'Income:US:BTrade:VTI:Dividend': DIVIDEND In this way, I can compare similar transactions to each other across instruments and extract information from them with the same code. For example, a transaction that involves a dividend account and a cash account is a cash dividend payment, and I can write a generic handler to extract the cash flows for this. The categorization was originally prototyped with a set of ad-hoc rules, but the configuration now serves to provide the categorization. Note: In the process of doing this, I noticed many irregularities in how I named my accounts. For example, I used \":Dividend\" and \":Dividends\" sometimes. I went through my ledger and had to make some changes to name accounts coherently, and iterated until all my accounts were categorized correctly. You may have to review some of your data entry as well. Handling Transactions using the Signature \uf0c1 Using the account-category mappings from the previous section, I was able to derive a unique \"signature\" for each transaction. For example, a transaction like this: 2020-03-12 * \"(DOI) ORDINARY DIVIDEND\" Income:US:BTrade:VTI:Dividend -1312.31 USD Assets:US:BTrade:Cash 1312.31 USD would have a signature of CASH_DIVIDEND which is hopefully always a dividend payment. Beancount has a pretty flexible syntax and does not enforce that your transactions follow particular templates like this, so it wasn't clear to me when I started this project what patterns I would find in my ledger of 12 years of ad-hoc data entry\u2026 I wasn't certain that this categorization and these signatures would be sufficient to correctly handle a correct conversion to cash flows. So I had my script produce two sets of files for debugging: Investment details. A file for each investment , rendering a list of all the transactions that were extracted for it, decorated with metadata showing the categorizations inferred on each posting, as well as a categorization map of all the accounts encountered. I inspected these files visually to ensure that the account/patterns from the configuration were extracting the full and correct set of transactions involved in that investment. Signature transactions. A file for each unique signature , with the full list of transactions matching that signature across all investments. By inspecting these files, I made sure that all the transactions matching the same signature were indeed playing the same role, so that a single handler per signature is sufficient. At this point, I had a limited list of unique signatures, each with clear unique roles: ASSET_CASH : Purchase or sale ASSET_CASH_EXPENSES : Purchase or sale with commission ASSET_CASH_INCOME : Purchase or sale with profit ASSET_CASH_INCOME_EXPENSES : Purchase or sale with commission and profit ASSET_EXPENSES : Fee paid from liquidation ASSET_INCOME : Cost basis adjustment (with P/L) ASSET_INCOME_EXPENSES : Fee from liquidation (with P/L) ASSET : Stock splits ASSET_DIVIDEND : Dividend reinvested CASH_DIVIDEND : Dividend payment CASH_INCOME_DIVIDEND : Dividend payment and gains distribution ASSET_OTHERASSET : Exchange of stock/symbol \u2026 Note that the specific list really depends on the particular contents of your ledger and you should inspect the files produced for correctness. I then wrote specific handlers to produce the cash flows corresponding to each of those transaction signatures, reasoning about each of those cases in isolation. This allowed me to correctly produce a full list of cash flows per investment. Note: In practice I encountered 3 or 4 more signatures types that were a bit exotic and by fixing up my ledger I managed to either correct or break apart these transactions to equivalent but simpler ones. In particular, one of my importers was lumping together trades occurring on the same day, and I went back and fixed it and those transactions manually. The ASSET_OTHERASSET signature, in particular, is an exchange of stock (Google -> GOOG,GOOGL). Doing something like this brings up idiosyncrasies in your bookkeeping technique. Being consistent and using fewer templates is helpful. It would be a valuable idea for an accompanying plugin to restrict the possible set of templates to a select few, so that data entry is constrained to work well with this returns production code. Generalizing Production of Cash Flows \uf0c1 After inspecting each of my signature handlers, I tried to generalize them to a single unified handler that would work across all transactions. It turns out that, at least with my existing ledger's transactions, it's possible. Essentially, recording inflows or outflows to cash accounts or other assets is sufficient. In a transaction like this: 2013-09-18 * \"Buy shares of HOOL\" Assets:US:BTrade:Cash -818.55 USD flow: CASH Assets:US:BTrade:HOOL 8 HOOL {101.20 USD} flow: ASSET Expenses:Financial:Commissions 8.95 USD flow: EXPENSES The \"CASH\" posting is a sufficient incoming cash flow, so we record -818.55 USD. In a cash dividend payment: 2013-12-17 * \"Cash Dividend payment\" Assets:US:BTrade:Cash 38.50 USD flow: CASH Income:US:BTrade:HOOL:Dividends -38.50 USD flow: DIVIDEND Similarly the 38.50 is a sufficient outgoing cash flow, so we record +38.50 USD. On the other hand a reinvested asset dividend, as you would find in some mutual funds, does not generate any cash flow; it simply remains in the investment and increases its total value: 2013-12-30 * \"Reinvested dividend\" Assets:US:BTrade:HOOL 0.356 {103.41} USD flow: ASSET Income:US:BTrade:HOOL:Dividends -36.81 USD flow: DIVIDEND This rule seems sufficient to handle all the contents of my ledger correctly. In the end, I implemented both methods: I use the general rule to produce the list of cash flows, but I also call out to the explicit handlers and cross-check that the extracted cash flows are identical, just to be sure. This is enabled by a flag in compute_returns.py ( --check-explicit-flows ). This forces me to ensure that I've analyzed all the possible transaction templates. Note: If in using this script you find cases from your ledger that aren't handled by using a series of cash accounts, please let me know (on the mailing-list). Cash Flows \uf0c1 The handlers described in the previous section each produced a list of cash flows per transaction, and together for the account, they are essentially a list of: (Date, Amount, IsDividend) Now, this is a simpler model to work from. For each account, we now have a sorted series of dated cash flows. Note that Amount includes its cost currency (I have both USD and CAD), IsDividend is a flag identifying the cash flow as being a dividend payment or not (to compute returns without the dividends). These series of cash flows can be easily merged between accounts, and truncated over time by inserting initial or final cash flows corresponding to the market value at those dates. Rendered, they might look like this (because of the scale, rendering the log brings up detail that is otherwise difficult to see; dividends are rendered in green): Note that since many transactions do not generate cash flows, the list of cash flows of an investment is insufficient by itself to compute the value of the investment over time. When truncating for a time interval, the market value of the investment is derived using the list of transactions. Finally, the list of cash flows for each group of investments reported can be trivially merged by concatenating them. Computing Returns \uf0c1 Calculating the Average Growth Rate \uf0c1 For each series of cash flows, the cash flows are merged together. I use scipy.optimize.fsolve to calculate the rate that satisfies net present value: c f i /(1\u2005+\u2005 r ) t i = 0 where cf i are the signed cash flow amounts and t i are the times from today for each cash flow (in years). We solve for r . To compute the returns without dividends, we just exclude cash flows returned from dividends. The difference tells us how much of our returns was due solely to dividend income. It's important to note that if the corresponding positions are still invested, you have to insert a final negative cash flow for the market value at the latest date to zero it out. You're essentially simulating a sale. If significant transaction costs are going to be involved, you might want to simulate those as well (e.g. if you're doing this for a home, in particular). Here's the beauty in this: nowhere was the underlying price used , except for marking the current position value to market value. We did not read any external measure of returns. These returns are computed from cash going in and coming out . These are actual realized returns. They do not lie. Intervals. To compute calendar returns, e.g., returns for years 2016, 2017, 2018, 2019, 2020, I truncate the cash flows to keep only those inside the interval, e.g. 2018-01-01 to 2018-12-31 for year 2018, and if there was an existing position at the beginning of the interval, insert a negative cash flow at the start of the series equivalent to the market value at those dates. I do the same thing at the end of the interval, with a positive cash flow, as described previously. Ideally I'd like to look at different sets of intervals: Lifetime of investment. Total returns over the entire lifetime of the positions, to boil it all down to a single number. Calendar. Annual or quarterly returns over the last 10 or 15 years, to witness variation in returns over time. Cumulative. Cumulative returns over the last 10 or 15 years, aligned on calendar periods, to get a sense of whether things are improving or worsening, and how well my strategies are doing in more recent periods (e.g. last 3 years). High frequency cumulative. Cumulative returns over the last 12 months, aligned with monthly or weekly dates, to assess the impact of decisions in the short-term. These can be rendered in tables, and investments can be compared to each other in that way. Filling in Missing Price Points \uf0c1 The calculations of returns over various intervals require marking positions to market value at the beginning and end of the interval dates. Beancount being designed hermetically, i.e., it does not fetch any price externally, only uses what's in the ledger, its price database lookup will automatically produce the last available price point and its recording date before the requested date. Depending on your recording discipline, some of those prices might be out-of-date and introduce inaccuracies. This is especially important since the amounts converted at the end of periods (i.e. to estimate the value of current positions) can be large and meaningfully influence even the lifetime returns number. So it's important to have relatively fresh price points in the ledger's price database. Now the question is, given a changing set of positions over time, for a given set of interval dates, which price entries are required to produce accurate results? Because this depends heavily on the particular inputs of the returns script, in order to solve this problem I simply wrapped the price database with a facade that collects all the (instrument, date) pairs for requested conversions during the production of the reports, and filter those down by some age threshold (e.g., price not older than 3 days). These are essentially the price points missing from the file. At the end of the script, I output these to a file with Price directives, and another program (download_prices.py) can read that file and fetch historical rates for those. It produces updated rates which you can paste to your ledger file as a one-off adjustment and then recompute more accurate returns. Pulling data from Yahoo! Finance worked for 90% of my positions, but some of my older instruments were quite old or even retired, or not available (e.g., some retirement funds), so I had to find them by browsing and manually entering some of these price points (I had something like 30\u2026 no big deal). Rolling windows. One important point is that going forward, it will be easier to align reporting intervals to some calendar-based interval (e.g., monthly), so that I don't have to regenerate price data every time I produce my returns. Aligning to months is probably good enough for my time horizon. Stock Splits. Beancount does not explicitly make adjustments of prices for stocks that split, so your price source should return the pre-split price of the stock at the time if you are recording it as such. You should be able to check your price time series for errors using Fava. Currency Conversion \uf0c1 Another important detail is that each investment has its own quote currency. I used to live in Canada, and some of my older investments are denominated in CAD. So the question arises: do I compute the returns in local (CAD) currency, or in terms of my reference currency (USD)? It's convenient that Beancount's Inventory object has functions that can easily perform those conversions where needed. And since the cash flows I extracted are stored using Beancount's Amount object, I already have the quote currencies correctly in my extracted dataset. In any group, if all the instruments have the same quote currency, I report returns in that currency. If the group includes a mix of quote currencies, I further convert everything to USD (so I get USD returns). Reporting \uf0c1 Grouping Accounts \uf0c1 Returns are calculated jointly for each group of accounts in each \"report\", as defined in the configuration. Here are some example groupings that are sensible to define: Same instrument in different accounts. If you buy the same stock in different accounts, it makes sense to report on the returns for that stock jointly, across all your accounts. Same underlying. Some instruments represent the same stock, e.g. GOOG and GOOGL (different share class, same company). Also, IAU and GLD (Gold) are different ETFs whose values are both derived from physical gold reserves (located in bank basements in London). Same asset class. Instruments from the same asset class, e.g. \"metals\", which would include IAU, GLD, SLV, COPX, etc., or \"REITs\", which would include VNQ, VGSLX, etc. Or \"all stocks\" vs. \"all bonds\". By Strategy. In my portfolio investment method, I have a multi-headed approach where I define specific broad strategies and then select a list of instruments to implement it. For instance, I have a \"tech sector\" strategy, which includes FAANG companies. Or a \"growth stock\" strategy, which might include different indexes like VUG, IWO and RGAGX. I can report how well those strategies are doing, across brokers. Or geographically, \"developed APAC\", including EWY, EWT, EWS, EWA. By Broker. I can report returns by broker or broker account. In particular, this can be an easy way to separate realized profits by tax treatment (e.g., 401k is tax-deferred). Asset type. Comparing all index funds to all managed funds (e.g. mutual funds). Note that different reports can include the same investments. Groupings aren't exclusive. You define what makes most sense for your situation. For reference, I use more than 20 reporting groups. Running the Code \uf0c1 Simply call ./experiments/returns/compute_returns.py to generate all reports and debugging files, where is in the format shown in \u201cConfiguration\u201d . It's a little slow \u2014 some performance improvements are possible \u2014 but if you supply a list of report names after the final argument, only those investments and reports will be processed, so you can iterate faster that way. See flags with --help for details, and config.proto for the configuration input documentation. Results Rendered \uf0c1 For each reporting group, I currently produce: A plot of the cash flows over time , and a smaller plot of log (cash flows). Dividend payments are typically dwarfed by cash flows for the principal, so the log chart allows me to see timings. This is mainly used to get an overview of activity over time, and for debugging. A plot of cumulative value, where I render two curves: Cumulative cash flows , with a growth curve matching that returns I regressed over. The result should be a plot with gentle slope between cash flows (corresponding to the total returns growth), with a final drop to zero. Market value over time : A curve of the mark-to-market value of the portfolio over time. This allows me to make some sense of calendar returns, by witnessing how the asset value moves based on price. A table of total returns , returns without dividends, and returns from dividends only. A table of calendar returns for each year, also broken down by total, ex-dividends, dividends-only. (I'll probably render this as a plot in the future.) A table of trailing cumulative returns . This is going to get refined and augmented as I'm actively working on this code [September 2020]. Example \uf0c1 Here's an example report, for a subset of accounts with a \"growth\" focus, held at different brokers. I produce one of these for each reporting group. (I greyed out parts for privacy.) Interpretation Gotchas \uf0c1 A few notes are in order: All rates are annualized . This makes it easy to compare numbers to each other, but it also means that positions held for a short amount of time will produce numbers that are unrealistic for long term extrapolation. In particular, new positions entered only a few months ago may be subject to high growth or a big drop, both of which when extrapolated to an entire year may show eye-popping percentages. Do keep this in mind, especially when looking at recent positions added to your portfolio. Taxes aren't factored in. Returns from taxable accounts and tax-deferred accounts should be evaluated differently and if the difference in tax is large, they can't be compared so readily. Do keep in mind that in most countries gains are only taxed on realization (sale) so in effect, long held investments behave more or less like tax-deferred ones. Just don't sell so much. This is a great advantage in holding broadly diversified ETFs (and usually unquantified, as people's attention is still overly focused on those benefits from registered accounts, e.g., 401k plan). Cost basis. Note that nowhere in our calculations was the cost basis used or factored in, so don't confuse it with market value. The cost basis is only useful for tax-related effects. Other Instruments Types \uf0c1 Note that computing returns in this manner isn't limited to only stocks and bonds. Using the same methodology, we can include other types of instruments: P2P Lending \uf0c1 I've used LendingClub before all the hedge funds got involved to pick up the low-hanging fruit, and eventually let all the bonds I'd invested in expire. It was easy to apply the same methodology to compute returns from those investments. Originally, I had the discipline to record this investment from monthly PDF statements using transactions like this: 2016-10-31 * \"2016-10-31.Monthly_Statement.pdf\" Assets:US:LendingClub:FundsLent -451.52 LENCLUB {1 USD} Assets:US:LendingClub:Cash 451.52 USD Income:US:LendingClub:LoanInterest -21.68 USD Income:US:LendingClub:Recoveries -5.92 USD Expenses:Financial:Fees 1.08 USD ;; Recovery fees Expenses:Financial:Fees 4.71 USD ;; Service fees Expenses:Financial:Fees 0.45 USD ;; Collection fees Assets:US:LendingClub:Cash Assets:US:LendingClub:FundsLent -23.05 LENCLUB {1 USD} Income:US:LendingClub:ChargedOff 23.05 USD And later on, after the principal bonds expired, transactions like this: 2018-11-30 * \"2018-11-30.Monthly_Statement.pdf\" Income:US:LendingClub:Recoveries -2.73 USD Expenses:Financial:Fees 0.49 USD ;; Recovery fees Assets:US:LendingClub:Cash All it took to compute returns is this configuration for the investment: investment { currency: \"LENCLUB\" asset_account: \"Assets:US:LendingClub:FundsLent\" match_accounts: \"Income:US:LendingClub:Recoveries\" cash_accounts: \"Assets:US:LendingClub:Cash\" } Note that the match account configuration was necessary to pick up crumbs from later transactions with only recoveries (and no posting to the asset account). For what it's worth, I was able to generate a 6.75% return over this investment. Meh. Real Estate \uf0c1 Cash flows can be extracted from all transactions for a home, so you can compute the returns for all the money poured into your home, as if it was done purely for investment purposes. Usually buying a home is done for other reasons \u2014 stability for children, the ability to make your own improvements, the forced savings involved in regular principal payments, and oftentimes just a sense of \"having a home\" \u2014 but in the vast majority of the cases, home ownership is more of a cost center and returns would have been better invested elsewhere (see this book for a great expos\u00e9 of the pros & cons). Personally, I have better things to do than fix toilets and worry about leaky windows in the winter so I went back to renting, but I went through the experience once and it was quite worthwhile, as a learning experience but also to experience the \"joy\" of having my own place. Through the exercise is useful to calculate how much having your own home is actually costing you, and how much you might have made by putting the very same cash flows into the market instead. It's a little more involved, because, You will have to have discipline to segregate expenses you wouldn't have had if you'd have rented to accounts specific for your home. You will need to account for equivalent rent received that you didn't have to pay because you lived in the home. If you still own the home, you will have to simulate a substantial agent fee and other closing costs (by inserting a transaction). Most places have a large tax break when selling a property (sometimes the full capital appreciation), so that really needs to be factored in. It's not always simple to calculate, especially if you rented your property for some of its ownership lifetime, in which case you might only be able to deduct a part of the capital gains. For some, there is great value in the optionality of being able to move easily and at low cost (e..g, accepting a new job at the other side of the country) and that personal value will be difficult to estimate. This will require me to do more categorization work, and it will be best documented as a separate project, though using the same code. I did manage to create a simple configuration and extract a 5%-ish number out of it, but I think I'll need a bit more work to declare victory. More to come. Options \uf0c1 I sometimes hedge some of my portfolio downrisk with long-dated OTM PUT positions. I've sold OTM PUTs and CALLs in the near term to finance it. The method I described for stocks in this document works equally well for options, that is, extracting cash flows from cash. The main differences are that: Currency names. Instrument names are specific to each contract\u2014they include the expiration date and strike price\u2014so I don't store the options to an account whose name includes the instrument name at the leaf. I just use a generic leaf account name, such as \"Options\" or \"Hedging\" which I select when I enter/exit positions. Prices for options. Prices for options aren't as easily fetchable programmatically. I'm having to use a private API for this. Perhaps more importantly, declining theta, varying vol and non-linearity near the strike means I do need to have to have pretty recent price estimates in my ledger in order to compute returns on unrealized gains/losses. I think the effect is strong enough that I'd want to eventually have some code to always update the prices just before generating the reports. Future Work \uf0c1 This section describes desired future improvements on the returns code, which I'm likely to implement, and the corresponding challenges. Note: As I'm writing this in September 2020, I'm actively working on this code and probably will continue over the next few months' weekends. It's possible this document falls slightly out-of-date and that some of the proposals below become implemented. Refer to the source code for the latest. Relative Size over Time \uf0c1 Another useful bit of data I could easily add to reports is a stack plot of market values over time, as well as relative fractions of each investment in a report group. This would be useful for monitoring growth of particular instruments in a group to help with rebalancing. Comparison Against a Benchmark \uf0c1 One of the important barometers of success in running your own portfolio is a comparison to an equivalent investment of capital in a simple portfolio of diversified stocks and bonds rebalanced regularly. After all, if my returns aren't beating that, in a normal environment, one could make the argument I shouldn't bother with a more complicated allocation. Assuming that my access to capital is the same (which it is, because in this project I'm looking at savings from income), I could simply replace my cash flows by cash flows to this simulated portfolio, in other words, use the same timings to simulate buying other assets. I would have to exclude the dividend payments because those are specific to the particular instruments I used, and at the same time generate simulated dividends from similarly sized positions in the benchmark portfolio. It should be possible to do this without modifying my ledger. One issue is that I will require a set of accurate prices for the benchmark, at the dates of my historical cash flows. Like my script already does for aging price points, this could easily be stored in a file for fetching. Perhaps more complicated is the fact that Beancount does not currently support a database of per-share dividend distributions. This could be added without introducing new syntax by attaching and honoring metadata on Price directives, such as 2020-09-01 price LQD 136.16 USD distribution: 0.295 USD Overall, it wouldn't be too difficult to implement this. Including Uninvested Cash \uf0c1 One of the problems I'm facing in investing my own portfolio is the lack of discipline around timely investing of available cash, especially when times are uncertain and difficult decisions have to be made. In order to account for the drag on performance from that, I should include an \"investment\" that's purely reflecting the total amount of uninvested cash over time. Because this varies a lot, a good approximation can be obtained by sampling total amount of investable cash every month and synthesizing cash flows from differences. If that cash flow series gets included as part of a portfolio, it'll suitably drag its returns by diluting them. After-Tax Value \uf0c1 At the moment I export all my holdings to a Google Sheets doc using the export script , and from that, break it down between long-term and short-term positions vs. pre-tax, after-tax, Roth, and taxable buckets. From those 8 aggregates, I remove estimated taxes and report a single \"estimated after-tax net worth\" and corresponding tax liability. This is a rough estimate. The returns report is however much more detailed, and I could simulate tax payments not only on liquidation, but also at the end of each year (from dividends and sales). I have all the lot dates on each position held so I can automatically figure out short-term vs. long-term lots. Inflation Adjustments \uf0c1 The market value amounts reported on the returns charts and calendar returns do not account for inflation. Over long periods of time, those can make an important difference in returns. It would be useful to discount the returns over time using annual estimates for the CPI (or some other estimate of inflation; you could even make up your own, from the expenses in your ledger), so that we can look at a curve of real growth and not just nominal growth. Sales Commission \uf0c1 The configuration could easily be improved to let the user specify expected commissions on sales of investments, either in absolute or relative (%) amounts. This would be used to mark positions with more realistic liquidation values. This could make a difference on investments with either small amounts or large commissions (i.e., working through a regular broker, or on real estate). Risk Estimation & Beta \uf0c1 A perhaps more advanced topic would be to compute an estimate of the variance from the specific portfolio composition in order to calculate and report some measurement of risk, such as Sharpe Ratio . This would require a sufficient number of regularly spaced price points. Variation of the measures over time could be fun too, as well as calculating the current portfolio's specific sensitivity to market as a whole (beta). Conclusion \uf0c1 I had expected it would be possible to produce a clear picture of returns from Beancount data, and having done it I'm more satisfied with the level of detail and clarity I was to produce from my ledger than I had expected. This goes well beyond plotting of net worth over time, this actually works really well, and I can use it to compare the performance of different investments fairly. I hope at least some Beancount users will be able to run it on their ledgers and I'm looking forward to hearing some feedback from those who set it up. Perhaps most importantly, I was very surprised to see results on my own portfolio returns. I'm one of those people who would normally shrug and guess an underwhelming ballpark number if asked what I thought my returns were, such as \"meh, 6% or something, not super happy.\" Doing this work was originally driven by not having the right answer to this question. It turns out, over the last 15 years, that I've generated an almost 12% average annual return, and 14% annual return in the last 5 years. I haven't yet made the benchmark comparison, and for sure these numbers should be put side-by-side with the market for a fair assessment. Nevertheless, going through the exercise has provided me with renewed confidence and hope about the future, and I hope the clarity it will bring to other Beancount users' own investments will be similarly enlightening.","title":"Calculating Portfolio Returns"},{"location":"calculating_portolio_returns.html#calculating-portfolio-returns","text":"Martin Blais , Sept 2020 http://furius.ca/beancount/doc/returns This document describes how to compute portfolio returns from a Beancount ledger.","title":"Calculating Portfolio Returns"},{"location":"calculating_portolio_returns.html#motivation","text":"You will be surprised to find that discount brokers typically do not provide accurate and complete returns calculations for your investments based on your specific cash flows. They tend to report other measures of performance: Change in value. The simplest they provide is a snapshot of the account value at the beginning and end of the period (or year). The problem with this method is that it does not reflect your infusions or removal of cash as such, nor your changes in positions. For example, if you had an account with $50,000 at the beginning of the year and you've added in $30,000 in August, reporting a difference of $37,000 at the end of December is just not useful (you'd have to mentally discount for new invested cash during the year, and what if you want to break it down and compare the returns from different instruments in the account?). Underlying performance. They might report the growth of the underlying asset in isolation, disregarding your specific positions. It's not very useful to say \"HDV grew by 8.2% over the last year\" if you invested only (or even mostly) in the second half of the year. What I'd like is to know how much my specific investments grew, given my specific changes in positions and timings. In other words, how did I do? No dividends. Another issue is that performance is typically only reporting capital appreciation due to the change in price of the investment. Ideally one would like to break down the performance between capital appreciation and dividends and returns for each of those components, as well as overall performance, so you can compare returns from stocks and bonds to each other. You can sometimes find out that information from the Yield field in the summary, but for uneven distributions, this won't help me find out what my actual returns from dividends were, over any period. Commissions and management expenses. Some brokers charge management expenses on a monthly basis, based on the value of the account. I would like to know what my portfolio return is net of those expenses. I want accurate returns reporting, based on my actual cash flows and changes of positions over time. If I maintain your investment information in a Beancount ledger, in theory it contains all the data I need in order to compute your true returns, based on the specific timings of my own savings (cash infusions) and which positions I held at which time. It's just not in the simplest format required to do it\u2014 Beancount transactions are much more flexible than one might want and a simpler series of cash flows needs to be extracted from it. This document explains how I finally did this from my own Ledger. And how we might generalize this to yours, based on some simple rules. Most of this text is dedicated to the pedestrian details of extracting the right data. The source code can be found here . In addition, a fair and honest comparison to other investments scenarios should be possible, based on those same cash flows. For instance, you should be able to produce data that looks like \"My investments in ZZZ have returned 8.2%, 1.1% of which were from dividends, and if I'd invested in a 60/40 portfolio of broad stocks and bonds it would have returned 7.2% instead.\" In other words, I want to assess my performance relative to a number of common alternatives. (Finally, note that if all you need is a snapshot of your current positions, that's already handled by the export script .)","title":"Motivation"},{"location":"calculating_portolio_returns.html#history","text":"In 2014, I made a brief attempt to break down information from my ledger to do this. At the time I got bogged down in details when some of the time-series I was extracting weren't producing what looked like sensible results (some with outliers). I got too detailed too fast. Sometimes it's better to just get the whole job done and come back for the details. I hadn't logged enough debugging information and I didn't have enough confidence in its output to use it. I never actually finished the work at the time, and eventually moved the scripts to experiments and shrugged. \"Later.\" In August 2020, I sat down to do this again, this time with a less ambitious goal of just getting a good approximation and producing lots of debug output, boiling down extraction to pull out just the cash flows and to get the job complete over all my accounts, even if it meant making some adjustments to my input file. It turned out to be the right decision: I managed to complete the task, and this document presents my journey, the method, its assumptions, quirks, and some results.","title":"History"},{"location":"calculating_portolio_returns.html#overview-of-method","text":"The method I'll use is to extract cash flow information for each investment. What I call \" an investment \" in this context is a kind of financial instrument , e.g., shares of \"VTI\" for the Vanguard Total Stock Market ETF, invested in a particular account , e.g. a Vanguard 401k. A list of cash flows is a dated list of positive or negative currency being invested or withdrawn as proceeds to/from that investment. So I have a list of cash flows per account, and each account records only one financial instrument. For reporting I'll group those per-account series in logical units of \" reports \", for example lumping together ones for the same instrument bought in different accounts / brokers, or different instruments that represent the same underlying by merging their cash flows. Or even to group them by \"strategy\". Basically, by merging the cash flows of multiple accounts, I have a generic way to group any subsets of accounts and compute their returns. Using the cash flows, I will then run a simple root finding routine to calculate the average annual rate those flows would have to grow in order to result in their final market value. This provides me with overall returns. This is similar to calculating the Internal Rate of Return . I will do that for the entire time series, but also for sub-intervals within the time series to compute, e.g. calendar returns (i.e., each year or quarter) or cumulative returns for trailing periods. Since cash flows are flagged as dividends or not, I can separate the returns from appreciation from the returns from dividends. Reports with plots are produced for each of the groups. Here's a diagram that shows how the \"configure\", \"compute_returns\" and \"download_prices\" scripts work together: These will be further detailed below.","title":"Overview of Method"},{"location":"calculating_portolio_returns.html#configuration","text":"First, a configuration needs to be created to define a list of investments, and groups of those, for which reports will be produced. This configuration is provided as a text-formatted protocol buffer message. Investment. An investment corresponds to a particular instrument stored in a particular account. It also involves other transactions that don't directly involve that particular account. We want to provide a few set of account names: Asset account. The name of the account holding the commodities for that investment. Matching accounts. A list of additional accounts which will select transactions with postings to them. Cash accounts. A list of accounts external to the investment, which will be used to flag inflows and outflows to and from the investment. Report. While we can compute returns for each investment, the more general and useful case is to compute returns on groups of them. A report is simply a list of investment names. A PDF file will be produced for each report. Here's an example of 6 investments and 3 reports: investments { # Accounts at BTrade. investment { currency: \"GLD\" asset_account: \"Assets:US:BTrade:GLD\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"AMZN\" asset_account: \"Assets:US:BTrade:AMZN\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"QQQ\" asset_account: \"Assets:US:BTrade:QQQ\" dividend_accounts: \"Income:US:BTrade:QQQ:Dividend\" cash_accounts: \"Assets:US:BTrade:Cash\" } # Accounts at IBKR. investment { currency: \"IAU\" asset_account: \"Assets:US:IBKR:IAU\" cash_accounts: \"Assets:US:IBKR:Cash\" } investment { currency: \"SLV\" asset_account: \"Assets:US:IBKR:SLV\" cash_accounts: \"Assets:US:IBKR:Cash\" } # Accounts at Schwab. investment { currency: \"GOOG\" asset_account: \"Assets:US:Schwab:GOOG\" cash_accounts: \"Assets:US:Schwab:Cash\" cash_accounts: \"Assets:AccountsReceivable\" cash_accounts: \"Assets:US:GoogleInc:GSURefund\" } } groups { group { name: \"strategy.gold\" investment: \"Assets:US:BTrade:GLD\" investment: \"Assets:US:IBKR:IAU\" } group { name: \"strategy.tech\" investment: \"Assets:US:BTrade:QQQ\" investment: \"Assets:US:BTrade:FB\" investment: \"Assets:US:Schwab:GOOG\" } group { name: \"all\" investment: \"Assets:US:*\" currency: \"USD\" } group { name: \"accounts.BTrade\" investment: \"Assets:US:BTrade:*\" currency: \"USD\" } } Different reports can include the same investment. References to accounts and investment names support simple UNIX-style globbing patterns in the configuration. These are expanded to full account names at runtime and stored in the output. A \" configure.py \" script can automatically infer a working basic configuration from an existing Beancount ledger. A report will be generated for each unique instrument and the same metadata fields honored by the \"export\" script (\"assetcls\", \"strategy\") will also generate reports. I recommend you run this on your ledger and then custom tailor the configuration manually.","title":"Configuration"},{"location":"calculating_portolio_returns.html#finding-accounts","text":"This script needs to figure out the list of available investments to report on. By convention I keep pairs of dedicated leaf accounts for each commodity type in my ledger, one to contain the actual positions (assets) and one to receive dividends, e.g., for \"VTI\" held in broker \"BTrade\", accounts like 2012-03-01 open Assets:US:BTrade:VTI VTI 2012-03-01 open Income:US:BTrade:VTI:Dividend USD This has two consequences: (a) it makes it easy to find the list of accounts that contain investments (any account with a leaf account name that is also one of the commodities found in the ledger), and (b) it nicely isolates all the activity related to each of those investments by finding all the transactions with a posting to that account . I recommend you follow the same convention in your chart of accounts. The result is a list of accounts like \"Assets:US:BTrade:VTI\", specific to each (instrument, institution). I further filter down this list to the subset of accounts which were still open up to 15 years ago (I close my accounts\u2014using Beancount's Close directive\u2014when they're done). In my particular case, I didn't have much savings back then, and there's no point in bothering to do the work to normalize those crumbs in my investing history for that far back.","title":"Finding Accounts"},{"location":"calculating_portolio_returns.html#extracting-cash-flow-data","text":"This section describes the various steps I took to extract relevant data from my ledger.","title":"Extracting Cash Flow Data"},{"location":"calculating_portolio_returns.html#extracting-relevant-transactions","text":"For each of the identified asset accounts, we want to pull out from the ledger's transactions the list of transactions affecting that account. We simply run through the entire ledger's transactions keeping transactions with at least one posting to the investment's asset account, dividend income accounts, or to any of the other \"match accounts\" defined for it. For instance, transactions for cash dividend payments will not show an asset posting so if dividends are paid; a typical dividend payment would contain only the dividend income posting and a cash posting (for the deposit): 2019-11-27 * \"Dividend\" Income:US:BTrade:VTI:Dividend -123.45 USD Assets:US:BTrade:Cash 123.45 USD So it is necessary to include the dividend account in the configuration to include those transactions, because they typically do not involve the asset account.","title":"Extracting Relevant Transactions"},{"location":"calculating_portolio_returns.html#account-categorization","text":"The next step is to generalize transactions to templates : we record the full set of accounts involved in the extracted transactions of each investment, and assign them to general categories based on their role in the transaction. For example, if I inspect my \"VTI\" trades, I will encounter the following accounts: Assets:US:BTrade:Cash Assets:US:BTrade:VTI Expenses:Financial:Commissions Income:US:BTrade:VTI:Dividend I map each account to one of several generic categories (I could probably simplify this now): ASSET # The account holding the commodity. CASH # Cash accounts, employer matches, contributions. DIVIDEND # Dividend income account. EXPENSES # Commissions, fees and other expenses. INCOME # Non-dividend income, P/L, gains, or other. OTHERASSET # Other assets than the primary asset for this investment. OTHER # Any other account. Like this: 'Assets:US:BTrade:Cash': CASH 'Assets:US:BTrade:VTI': ASSET 'Expenses:Financial:Commissions': EXPENSES 'Income:US:BTrade:VTI:Dividend': DIVIDEND In this way, I can compare similar transactions to each other across instruments and extract information from them with the same code. For example, a transaction that involves a dividend account and a cash account is a cash dividend payment, and I can write a generic handler to extract the cash flows for this. The categorization was originally prototyped with a set of ad-hoc rules, but the configuration now serves to provide the categorization. Note: In the process of doing this, I noticed many irregularities in how I named my accounts. For example, I used \":Dividend\" and \":Dividends\" sometimes. I went through my ledger and had to make some changes to name accounts coherently, and iterated until all my accounts were categorized correctly. You may have to review some of your data entry as well.","title":"Account Categorization"},{"location":"calculating_portolio_returns.html#handling-transactions-using-the-signature","text":"Using the account-category mappings from the previous section, I was able to derive a unique \"signature\" for each transaction. For example, a transaction like this: 2020-03-12 * \"(DOI) ORDINARY DIVIDEND\" Income:US:BTrade:VTI:Dividend -1312.31 USD Assets:US:BTrade:Cash 1312.31 USD would have a signature of CASH_DIVIDEND which is hopefully always a dividend payment. Beancount has a pretty flexible syntax and does not enforce that your transactions follow particular templates like this, so it wasn't clear to me when I started this project what patterns I would find in my ledger of 12 years of ad-hoc data entry\u2026 I wasn't certain that this categorization and these signatures would be sufficient to correctly handle a correct conversion to cash flows. So I had my script produce two sets of files for debugging: Investment details. A file for each investment , rendering a list of all the transactions that were extracted for it, decorated with metadata showing the categorizations inferred on each posting, as well as a categorization map of all the accounts encountered. I inspected these files visually to ensure that the account/patterns from the configuration were extracting the full and correct set of transactions involved in that investment. Signature transactions. A file for each unique signature , with the full list of transactions matching that signature across all investments. By inspecting these files, I made sure that all the transactions matching the same signature were indeed playing the same role, so that a single handler per signature is sufficient. At this point, I had a limited list of unique signatures, each with clear unique roles: ASSET_CASH : Purchase or sale ASSET_CASH_EXPENSES : Purchase or sale with commission ASSET_CASH_INCOME : Purchase or sale with profit ASSET_CASH_INCOME_EXPENSES : Purchase or sale with commission and profit ASSET_EXPENSES : Fee paid from liquidation ASSET_INCOME : Cost basis adjustment (with P/L) ASSET_INCOME_EXPENSES : Fee from liquidation (with P/L) ASSET : Stock splits ASSET_DIVIDEND : Dividend reinvested CASH_DIVIDEND : Dividend payment CASH_INCOME_DIVIDEND : Dividend payment and gains distribution ASSET_OTHERASSET : Exchange of stock/symbol \u2026 Note that the specific list really depends on the particular contents of your ledger and you should inspect the files produced for correctness. I then wrote specific handlers to produce the cash flows corresponding to each of those transaction signatures, reasoning about each of those cases in isolation. This allowed me to correctly produce a full list of cash flows per investment. Note: In practice I encountered 3 or 4 more signatures types that were a bit exotic and by fixing up my ledger I managed to either correct or break apart these transactions to equivalent but simpler ones. In particular, one of my importers was lumping together trades occurring on the same day, and I went back and fixed it and those transactions manually. The ASSET_OTHERASSET signature, in particular, is an exchange of stock (Google -> GOOG,GOOGL). Doing something like this brings up idiosyncrasies in your bookkeeping technique. Being consistent and using fewer templates is helpful. It would be a valuable idea for an accompanying plugin to restrict the possible set of templates to a select few, so that data entry is constrained to work well with this returns production code.","title":"Handling Transactions using the Signature"},{"location":"calculating_portolio_returns.html#generalizing-production-of-cash-flows","text":"After inspecting each of my signature handlers, I tried to generalize them to a single unified handler that would work across all transactions. It turns out that, at least with my existing ledger's transactions, it's possible. Essentially, recording inflows or outflows to cash accounts or other assets is sufficient. In a transaction like this: 2013-09-18 * \"Buy shares of HOOL\" Assets:US:BTrade:Cash -818.55 USD flow: CASH Assets:US:BTrade:HOOL 8 HOOL {101.20 USD} flow: ASSET Expenses:Financial:Commissions 8.95 USD flow: EXPENSES The \"CASH\" posting is a sufficient incoming cash flow, so we record -818.55 USD. In a cash dividend payment: 2013-12-17 * \"Cash Dividend payment\" Assets:US:BTrade:Cash 38.50 USD flow: CASH Income:US:BTrade:HOOL:Dividends -38.50 USD flow: DIVIDEND Similarly the 38.50 is a sufficient outgoing cash flow, so we record +38.50 USD. On the other hand a reinvested asset dividend, as you would find in some mutual funds, does not generate any cash flow; it simply remains in the investment and increases its total value: 2013-12-30 * \"Reinvested dividend\" Assets:US:BTrade:HOOL 0.356 {103.41} USD flow: ASSET Income:US:BTrade:HOOL:Dividends -36.81 USD flow: DIVIDEND This rule seems sufficient to handle all the contents of my ledger correctly. In the end, I implemented both methods: I use the general rule to produce the list of cash flows, but I also call out to the explicit handlers and cross-check that the extracted cash flows are identical, just to be sure. This is enabled by a flag in compute_returns.py ( --check-explicit-flows ). This forces me to ensure that I've analyzed all the possible transaction templates. Note: If in using this script you find cases from your ledger that aren't handled by using a series of cash accounts, please let me know (on the mailing-list).","title":"Generalizing Production of Cash Flows"},{"location":"calculating_portolio_returns.html#cash-flows","text":"The handlers described in the previous section each produced a list of cash flows per transaction, and together for the account, they are essentially a list of: (Date, Amount, IsDividend) Now, this is a simpler model to work from. For each account, we now have a sorted series of dated cash flows. Note that Amount includes its cost currency (I have both USD and CAD), IsDividend is a flag identifying the cash flow as being a dividend payment or not (to compute returns without the dividends). These series of cash flows can be easily merged between accounts, and truncated over time by inserting initial or final cash flows corresponding to the market value at those dates. Rendered, they might look like this (because of the scale, rendering the log brings up detail that is otherwise difficult to see; dividends are rendered in green): Note that since many transactions do not generate cash flows, the list of cash flows of an investment is insufficient by itself to compute the value of the investment over time. When truncating for a time interval, the market value of the investment is derived using the list of transactions. Finally, the list of cash flows for each group of investments reported can be trivially merged by concatenating them.","title":"Cash Flows"},{"location":"calculating_portolio_returns.html#computing-returns","text":"","title":"Computing Returns"},{"location":"calculating_portolio_returns.html#calculating-the-average-growth-rate","text":"For each series of cash flows, the cash flows are merged together. I use scipy.optimize.fsolve to calculate the rate that satisfies net present value: c f i /(1\u2005+\u2005 r ) t i = 0 where cf i are the signed cash flow amounts and t i are the times from today for each cash flow (in years). We solve for r . To compute the returns without dividends, we just exclude cash flows returned from dividends. The difference tells us how much of our returns was due solely to dividend income. It's important to note that if the corresponding positions are still invested, you have to insert a final negative cash flow for the market value at the latest date to zero it out. You're essentially simulating a sale. If significant transaction costs are going to be involved, you might want to simulate those as well (e.g. if you're doing this for a home, in particular). Here's the beauty in this: nowhere was the underlying price used , except for marking the current position value to market value. We did not read any external measure of returns. These returns are computed from cash going in and coming out . These are actual realized returns. They do not lie. Intervals. To compute calendar returns, e.g., returns for years 2016, 2017, 2018, 2019, 2020, I truncate the cash flows to keep only those inside the interval, e.g. 2018-01-01 to 2018-12-31 for year 2018, and if there was an existing position at the beginning of the interval, insert a negative cash flow at the start of the series equivalent to the market value at those dates. I do the same thing at the end of the interval, with a positive cash flow, as described previously. Ideally I'd like to look at different sets of intervals: Lifetime of investment. Total returns over the entire lifetime of the positions, to boil it all down to a single number. Calendar. Annual or quarterly returns over the last 10 or 15 years, to witness variation in returns over time. Cumulative. Cumulative returns over the last 10 or 15 years, aligned on calendar periods, to get a sense of whether things are improving or worsening, and how well my strategies are doing in more recent periods (e.g. last 3 years). High frequency cumulative. Cumulative returns over the last 12 months, aligned with monthly or weekly dates, to assess the impact of decisions in the short-term. These can be rendered in tables, and investments can be compared to each other in that way.","title":"Calculating the Average Growth Rate"},{"location":"calculating_portolio_returns.html#filling-in-missing-price-points","text":"The calculations of returns over various intervals require marking positions to market value at the beginning and end of the interval dates. Beancount being designed hermetically, i.e., it does not fetch any price externally, only uses what's in the ledger, its price database lookup will automatically produce the last available price point and its recording date before the requested date. Depending on your recording discipline, some of those prices might be out-of-date and introduce inaccuracies. This is especially important since the amounts converted at the end of periods (i.e. to estimate the value of current positions) can be large and meaningfully influence even the lifetime returns number. So it's important to have relatively fresh price points in the ledger's price database. Now the question is, given a changing set of positions over time, for a given set of interval dates, which price entries are required to produce accurate results? Because this depends heavily on the particular inputs of the returns script, in order to solve this problem I simply wrapped the price database with a facade that collects all the (instrument, date) pairs for requested conversions during the production of the reports, and filter those down by some age threshold (e.g., price not older than 3 days). These are essentially the price points missing from the file. At the end of the script, I output these to a file with Price directives, and another program (download_prices.py) can read that file and fetch historical rates for those. It produces updated rates which you can paste to your ledger file as a one-off adjustment and then recompute more accurate returns. Pulling data from Yahoo! Finance worked for 90% of my positions, but some of my older instruments were quite old or even retired, or not available (e.g., some retirement funds), so I had to find them by browsing and manually entering some of these price points (I had something like 30\u2026 no big deal). Rolling windows. One important point is that going forward, it will be easier to align reporting intervals to some calendar-based interval (e.g., monthly), so that I don't have to regenerate price data every time I produce my returns. Aligning to months is probably good enough for my time horizon. Stock Splits. Beancount does not explicitly make adjustments of prices for stocks that split, so your price source should return the pre-split price of the stock at the time if you are recording it as such. You should be able to check your price time series for errors using Fava.","title":"Filling in Missing Price Points"},{"location":"calculating_portolio_returns.html#currency-conversion","text":"Another important detail is that each investment has its own quote currency. I used to live in Canada, and some of my older investments are denominated in CAD. So the question arises: do I compute the returns in local (CAD) currency, or in terms of my reference currency (USD)? It's convenient that Beancount's Inventory object has functions that can easily perform those conversions where needed. And since the cash flows I extracted are stored using Beancount's Amount object, I already have the quote currencies correctly in my extracted dataset. In any group, if all the instruments have the same quote currency, I report returns in that currency. If the group includes a mix of quote currencies, I further convert everything to USD (so I get USD returns).","title":"Currency Conversion"},{"location":"calculating_portolio_returns.html#reporting","text":"","title":"Reporting"},{"location":"calculating_portolio_returns.html#grouping-accounts","text":"Returns are calculated jointly for each group of accounts in each \"report\", as defined in the configuration. Here are some example groupings that are sensible to define: Same instrument in different accounts. If you buy the same stock in different accounts, it makes sense to report on the returns for that stock jointly, across all your accounts. Same underlying. Some instruments represent the same stock, e.g. GOOG and GOOGL (different share class, same company). Also, IAU and GLD (Gold) are different ETFs whose values are both derived from physical gold reserves (located in bank basements in London). Same asset class. Instruments from the same asset class, e.g. \"metals\", which would include IAU, GLD, SLV, COPX, etc., or \"REITs\", which would include VNQ, VGSLX, etc. Or \"all stocks\" vs. \"all bonds\". By Strategy. In my portfolio investment method, I have a multi-headed approach where I define specific broad strategies and then select a list of instruments to implement it. For instance, I have a \"tech sector\" strategy, which includes FAANG companies. Or a \"growth stock\" strategy, which might include different indexes like VUG, IWO and RGAGX. I can report how well those strategies are doing, across brokers. Or geographically, \"developed APAC\", including EWY, EWT, EWS, EWA. By Broker. I can report returns by broker or broker account. In particular, this can be an easy way to separate realized profits by tax treatment (e.g., 401k is tax-deferred). Asset type. Comparing all index funds to all managed funds (e.g. mutual funds). Note that different reports can include the same investments. Groupings aren't exclusive. You define what makes most sense for your situation. For reference, I use more than 20 reporting groups.","title":"Grouping Accounts"},{"location":"calculating_portolio_returns.html#running-the-code","text":"Simply call ./experiments/returns/compute_returns.py to generate all reports and debugging files, where is in the format shown in \u201cConfiguration\u201d . It's a little slow \u2014 some performance improvements are possible \u2014 but if you supply a list of report names after the final argument, only those investments and reports will be processed, so you can iterate faster that way. See flags with --help for details, and config.proto for the configuration input documentation.","title":"Running the Code"},{"location":"calculating_portolio_returns.html#results-rendered","text":"For each reporting group, I currently produce: A plot of the cash flows over time , and a smaller plot of log (cash flows). Dividend payments are typically dwarfed by cash flows for the principal, so the log chart allows me to see timings. This is mainly used to get an overview of activity over time, and for debugging. A plot of cumulative value, where I render two curves: Cumulative cash flows , with a growth curve matching that returns I regressed over. The result should be a plot with gentle slope between cash flows (corresponding to the total returns growth), with a final drop to zero. Market value over time : A curve of the mark-to-market value of the portfolio over time. This allows me to make some sense of calendar returns, by witnessing how the asset value moves based on price. A table of total returns , returns without dividends, and returns from dividends only. A table of calendar returns for each year, also broken down by total, ex-dividends, dividends-only. (I'll probably render this as a plot in the future.) A table of trailing cumulative returns . This is going to get refined and augmented as I'm actively working on this code [September 2020].","title":"Results Rendered"},{"location":"calculating_portolio_returns.html#example","text":"Here's an example report, for a subset of accounts with a \"growth\" focus, held at different brokers. I produce one of these for each reporting group. (I greyed out parts for privacy.)","title":"Example"},{"location":"calculating_portolio_returns.html#interpretation-gotchas","text":"A few notes are in order: All rates are annualized . This makes it easy to compare numbers to each other, but it also means that positions held for a short amount of time will produce numbers that are unrealistic for long term extrapolation. In particular, new positions entered only a few months ago may be subject to high growth or a big drop, both of which when extrapolated to an entire year may show eye-popping percentages. Do keep this in mind, especially when looking at recent positions added to your portfolio. Taxes aren't factored in. Returns from taxable accounts and tax-deferred accounts should be evaluated differently and if the difference in tax is large, they can't be compared so readily. Do keep in mind that in most countries gains are only taxed on realization (sale) so in effect, long held investments behave more or less like tax-deferred ones. Just don't sell so much. This is a great advantage in holding broadly diversified ETFs (and usually unquantified, as people's attention is still overly focused on those benefits from registered accounts, e.g., 401k plan). Cost basis. Note that nowhere in our calculations was the cost basis used or factored in, so don't confuse it with market value. The cost basis is only useful for tax-related effects.","title":"Interpretation Gotchas"},{"location":"calculating_portolio_returns.html#other-instruments-types","text":"Note that computing returns in this manner isn't limited to only stocks and bonds. Using the same methodology, we can include other types of instruments:","title":"Other Instruments Types"},{"location":"calculating_portolio_returns.html#p2p-lending","text":"I've used LendingClub before all the hedge funds got involved to pick up the low-hanging fruit, and eventually let all the bonds I'd invested in expire. It was easy to apply the same methodology to compute returns from those investments. Originally, I had the discipline to record this investment from monthly PDF statements using transactions like this: 2016-10-31 * \"2016-10-31.Monthly_Statement.pdf\" Assets:US:LendingClub:FundsLent -451.52 LENCLUB {1 USD} Assets:US:LendingClub:Cash 451.52 USD Income:US:LendingClub:LoanInterest -21.68 USD Income:US:LendingClub:Recoveries -5.92 USD Expenses:Financial:Fees 1.08 USD ;; Recovery fees Expenses:Financial:Fees 4.71 USD ;; Service fees Expenses:Financial:Fees 0.45 USD ;; Collection fees Assets:US:LendingClub:Cash Assets:US:LendingClub:FundsLent -23.05 LENCLUB {1 USD} Income:US:LendingClub:ChargedOff 23.05 USD And later on, after the principal bonds expired, transactions like this: 2018-11-30 * \"2018-11-30.Monthly_Statement.pdf\" Income:US:LendingClub:Recoveries -2.73 USD Expenses:Financial:Fees 0.49 USD ;; Recovery fees Assets:US:LendingClub:Cash All it took to compute returns is this configuration for the investment: investment { currency: \"LENCLUB\" asset_account: \"Assets:US:LendingClub:FundsLent\" match_accounts: \"Income:US:LendingClub:Recoveries\" cash_accounts: \"Assets:US:LendingClub:Cash\" } Note that the match account configuration was necessary to pick up crumbs from later transactions with only recoveries (and no posting to the asset account). For what it's worth, I was able to generate a 6.75% return over this investment. Meh.","title":"P2P Lending"},{"location":"calculating_portolio_returns.html#real-estate","text":"Cash flows can be extracted from all transactions for a home, so you can compute the returns for all the money poured into your home, as if it was done purely for investment purposes. Usually buying a home is done for other reasons \u2014 stability for children, the ability to make your own improvements, the forced savings involved in regular principal payments, and oftentimes just a sense of \"having a home\" \u2014 but in the vast majority of the cases, home ownership is more of a cost center and returns would have been better invested elsewhere (see this book for a great expos\u00e9 of the pros & cons). Personally, I have better things to do than fix toilets and worry about leaky windows in the winter so I went back to renting, but I went through the experience once and it was quite worthwhile, as a learning experience but also to experience the \"joy\" of having my own place. Through the exercise is useful to calculate how much having your own home is actually costing you, and how much you might have made by putting the very same cash flows into the market instead. It's a little more involved, because, You will have to have discipline to segregate expenses you wouldn't have had if you'd have rented to accounts specific for your home. You will need to account for equivalent rent received that you didn't have to pay because you lived in the home. If you still own the home, you will have to simulate a substantial agent fee and other closing costs (by inserting a transaction). Most places have a large tax break when selling a property (sometimes the full capital appreciation), so that really needs to be factored in. It's not always simple to calculate, especially if you rented your property for some of its ownership lifetime, in which case you might only be able to deduct a part of the capital gains. For some, there is great value in the optionality of being able to move easily and at low cost (e..g, accepting a new job at the other side of the country) and that personal value will be difficult to estimate. This will require me to do more categorization work, and it will be best documented as a separate project, though using the same code. I did manage to create a simple configuration and extract a 5%-ish number out of it, but I think I'll need a bit more work to declare victory. More to come.","title":"Real Estate"},{"location":"calculating_portolio_returns.html#options","text":"I sometimes hedge some of my portfolio downrisk with long-dated OTM PUT positions. I've sold OTM PUTs and CALLs in the near term to finance it. The method I described for stocks in this document works equally well for options, that is, extracting cash flows from cash. The main differences are that: Currency names. Instrument names are specific to each contract\u2014they include the expiration date and strike price\u2014so I don't store the options to an account whose name includes the instrument name at the leaf. I just use a generic leaf account name, such as \"Options\" or \"Hedging\" which I select when I enter/exit positions. Prices for options. Prices for options aren't as easily fetchable programmatically. I'm having to use a private API for this. Perhaps more importantly, declining theta, varying vol and non-linearity near the strike means I do need to have to have pretty recent price estimates in my ledger in order to compute returns on unrealized gains/losses. I think the effect is strong enough that I'd want to eventually have some code to always update the prices just before generating the reports.","title":"Options"},{"location":"calculating_portolio_returns.html#future-work","text":"This section describes desired future improvements on the returns code, which I'm likely to implement, and the corresponding challenges. Note: As I'm writing this in September 2020, I'm actively working on this code and probably will continue over the next few months' weekends. It's possible this document falls slightly out-of-date and that some of the proposals below become implemented. Refer to the source code for the latest.","title":"Future Work"},{"location":"calculating_portolio_returns.html#relative-size-over-time","text":"Another useful bit of data I could easily add to reports is a stack plot of market values over time, as well as relative fractions of each investment in a report group. This would be useful for monitoring growth of particular instruments in a group to help with rebalancing.","title":"Relative Size over Time"},{"location":"calculating_portolio_returns.html#comparison-against-a-benchmark","text":"One of the important barometers of success in running your own portfolio is a comparison to an equivalent investment of capital in a simple portfolio of diversified stocks and bonds rebalanced regularly. After all, if my returns aren't beating that, in a normal environment, one could make the argument I shouldn't bother with a more complicated allocation. Assuming that my access to capital is the same (which it is, because in this project I'm looking at savings from income), I could simply replace my cash flows by cash flows to this simulated portfolio, in other words, use the same timings to simulate buying other assets. I would have to exclude the dividend payments because those are specific to the particular instruments I used, and at the same time generate simulated dividends from similarly sized positions in the benchmark portfolio. It should be possible to do this without modifying my ledger. One issue is that I will require a set of accurate prices for the benchmark, at the dates of my historical cash flows. Like my script already does for aging price points, this could easily be stored in a file for fetching. Perhaps more complicated is the fact that Beancount does not currently support a database of per-share dividend distributions. This could be added without introducing new syntax by attaching and honoring metadata on Price directives, such as 2020-09-01 price LQD 136.16 USD distribution: 0.295 USD Overall, it wouldn't be too difficult to implement this.","title":"Comparison Against a Benchmark"},{"location":"calculating_portolio_returns.html#including-uninvested-cash","text":"One of the problems I'm facing in investing my own portfolio is the lack of discipline around timely investing of available cash, especially when times are uncertain and difficult decisions have to be made. In order to account for the drag on performance from that, I should include an \"investment\" that's purely reflecting the total amount of uninvested cash over time. Because this varies a lot, a good approximation can be obtained by sampling total amount of investable cash every month and synthesizing cash flows from differences. If that cash flow series gets included as part of a portfolio, it'll suitably drag its returns by diluting them.","title":"Including Uninvested Cash"},{"location":"calculating_portolio_returns.html#after-tax-value","text":"At the moment I export all my holdings to a Google Sheets doc using the export script , and from that, break it down between long-term and short-term positions vs. pre-tax, after-tax, Roth, and taxable buckets. From those 8 aggregates, I remove estimated taxes and report a single \"estimated after-tax net worth\" and corresponding tax liability. This is a rough estimate. The returns report is however much more detailed, and I could simulate tax payments not only on liquidation, but also at the end of each year (from dividends and sales). I have all the lot dates on each position held so I can automatically figure out short-term vs. long-term lots.","title":"After-Tax Value"},{"location":"calculating_portolio_returns.html#inflation-adjustments","text":"The market value amounts reported on the returns charts and calendar returns do not account for inflation. Over long periods of time, those can make an important difference in returns. It would be useful to discount the returns over time using annual estimates for the CPI (or some other estimate of inflation; you could even make up your own, from the expenses in your ledger), so that we can look at a curve of real growth and not just nominal growth.","title":"Inflation Adjustments"},{"location":"calculating_portolio_returns.html#sales-commission","text":"The configuration could easily be improved to let the user specify expected commissions on sales of investments, either in absolute or relative (%) amounts. This would be used to mark positions with more realistic liquidation values. This could make a difference on investments with either small amounts or large commissions (i.e., working through a regular broker, or on real estate).","title":"Sales Commission"},{"location":"calculating_portolio_returns.html#risk-estimation-beta","text":"A perhaps more advanced topic would be to compute an estimate of the variance from the specific portfolio composition in order to calculate and report some measurement of risk, such as Sharpe Ratio . This would require a sufficient number of regularly spaced price points. Variation of the measures over time could be fun too, as well as calculating the current portfolio's specific sensitivity to market as a whole (beta).","title":"Risk Estimation & Beta"},{"location":"calculating_portolio_returns.html#conclusion","text":"I had expected it would be possible to produce a clear picture of returns from Beancount data, and having done it I'm more satisfied with the level of detail and clarity I was to produce from my ledger than I had expected. This goes well beyond plotting of net worth over time, this actually works really well, and I can use it to compare the performance of different investments fairly. I hope at least some Beancount users will be able to run it on their ledgers and I'm looking forward to hearing some feedback from those who set it up. Perhaps most importantly, I was very surprised to see results on my own portfolio returns. I'm one of those people who would normally shrug and guess an underwhelming ballpark number if asked what I thought my returns were, such as \"meh, 6% or something, not super happy.\" Doing this work was originally driven by not having the right answer to this question. It turns out, over the last 15 years, that I've generated an almost 12% average annual return, and 14% annual return in the last 5 years. I haven't yet made the benchmark comparison, and for sure these numbers should be put side-by-side with the market for a fair assessment. Nevertheless, going through the exercise has provided me with renewed confidence and hope about the future, and I hope the clarity it will bring to other Beancount users' own investments will be similarly enlightening.","title":"Conclusion"},{"location":"command_line_accounting_cookbook.html","text":"Command-line Accounting Cookbook \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/cookbook Introduction A Note of Caution Account Naming Conventions Choosing an Account Type Choosing Opening Dates How to Deal with Cash Cash Withdrawals Tracking Cash Expenses Salary Income Employment Income Accounts Booking Salary Deposits Vacation Hours 401k Contributions Vesting Stock Grants Other Benefits Points Food Benefits Currency Transfers & Conversions Investing and Trading Accounts Setup Funds Transfers Making a Trade Receiving Dividends Conclusion Introduction \uf0c1 The best way to learn the double-entry method is to look at real-world examples. The method is elegant, but it can seem unintuitive to the newcomer how transactions have to be posted in order to perform the various operations that one needs to do in counting for different types financial events. This is why I wrote this cookbook. It is not meant to be a comprehensive description of all the features supported, but rather a set of practical guidelines to help you solve problems. I think this will likely be the most useful document in the Beancount documentation set! All the examples here apply to any double-entry accounting system: Ledger, GnuCash, or even commercial systems. Some of the details may differ only slightly. This cookbook is written using the syntax and calculation method of the Beancount software. This document also assumes that you are already familiar with the general balancing concepts of the double-entry method and with at least some of the syntax of Beancount which is available from its user\u2019s manual or its cheat sheet . If you haven\u2019t begun writing down your first file, you will want to read Getting Started with Beancount and do that first. Command-line accounting systems are agnostic about the types of things they can count and allow you to get creative with the kinds of units that you can invent to track various kinds of things. For instance, you can count \u201cIRA contribution dollars,\u201d which are not real dollars, but which correspond to \u201cpossible contributions in real dollars,\u201d and you obtain accounts of assets, income and expenses types for them - it works. Please do realize that some of those clever tricks may not be possible in more traditional accounting systems. In addition, some of the operations that would normally require a manual process in these systems can be automated away for us, e.g., \u201cclosing a year\u201d is entirely done by the software at any point in time, and balance assertions provide a safeguard that allow us to change the details of past transactions with little risk, so there is no need to \u201creconcile\u201d by baking the past into a frozen state. More flexibility is at hand. Finally, if you have a transaction entry problem that is not covered in this document, please do leave a comment in the margin, or write up your problem to the Ledger mailing-list . I would like for this document to cover as many realistic scenarios as possible. A Note of Caution \uf0c1 While reading this, please take note that the author is a dilettante: I am a computer scientist, not an accountant. In fact, apart from a general course I took in college and having completed the first year of a CFA program, I have no real training in accounting. Despite this, I do have some practical experience in maintaining three set of books using this software: my personal ledger (8 years worth of full financial data for all accounts), a joint ledger with my spouse, and the books of a contracting and consulting company I used to own. I also used my double-entry system to communicate with my accountant for many years and he made suggestions. Nevertheless\u2026 I may be making fundamental mistakes here and there, and I would appreciate you leaving a comment in the margin if you find anything dubious. Account Naming Conventions \uf0c1 You can define any account name you like, as long as it begins with one of the five categories: Assets, Liabilities, Income, Expenses, or Equity (note that you can customize those names with options - see the Language Syntax document for details). The accounts names are generally defined to have multiple name components, separated by a colon (:), which imply an accounts hierarchy, or \u201c chart of accounts \u201d: Assets:Component1:Component2:Component3:... Over time, I\u2019ve iterated over many ways of defining my account names and I have converged to the following convention for Assets, Liabilities, and Income accounts: Type : Country : Institution : Account : SubAccount What I like about this is that when you render a balance sheet, the tree that gets rendered nicely displays accounts by country first, then by institution. Some example account names: Assets:US:BofA:Savings ; Bank of America \u201cSavings\u201d account Assets:CA:RBC:Checking ; Royal Bank of Canada \u201cChecking\u201d account Liabilities:US:Amex:Platinum ; American Express Platinum credit card Liabilities:CA:RBC:Mortgage ; Mortgage loan account at RBC Income:US:ETrade:Interest ; Interest payments in E*Trade account Income:US:Acme:Salary ; Salary income from ACME corp. Sometimes I use a further sub-account or two, when it makes sense. For example, Vanguard internally keeps separate accounts depending on whether the contributions were from the employee or the employer\u2019s matching amount: Assets:US:Vanguard:Contrib401k:RGAGX ; My contributions to this fund Assets:US:Vanguard:Match401k:RGAGX ; Employer contributions For investment accounts, I tend organize all their contents by storing each particular type of stock in its own sub-account: Assets:US:ETrade:Cash ; The cash contents of the account Assets:US:ETrade:FB ; Shares of Facebook Assets:US:ETrade:AAPL ; Shares of Apple Assets:US:ETrade:MSFT ; Shares of Microsoft \u2026 This automatically organizes the balance sheet by types of shares, which I find really nice. Another convention that I like is to use the same institution component name when I have different related types of accounts. For instance, the E*Trade assets account above has associated income streams that would be booked under similarly named accounts: Income:US:ETrade:Interest ; Interest income from cash deposits Income:US:ETrade:Dividends ; Dividends received in this account Income:US:ETrade:PnL ; Capital gains or losses from trades \u2026 For \u201cExpenses\u201d accounts, I find that there are generally no relevant institutions. For those it makes more sense to treat them as categories and just have a simple hierarchy that corresponds to the kinds of expenses they count, some examples: Expenses:Sports:Scuba ; All matters of diving expenses Expenses:Transport:Train ; Train (mostly Amtrak, but not always) Expenses:Transport:Bus ; Various \u201cchinese bus\u201d companies Expenses:Transport:Flights ; Flights (various airlines) \u2026 I have a lot of these, like 250 or more. It is really up to you to decide how many to define and how finely to aggregate or \u201ccategorize\u201d your expenses this way. But of course, you should only define them as you need them; don\u2019t bother defining a huge list ahead of time. It\u2019s always easy to add new ones. It is worth noting that the institution does not have to be a \u201creal\u201d institution. For instance, I owned a condo unit in a building, and I used the Loft4530 \u201cinstitution\u201d for all its related accounts: Assets:CA:Loft4530:Property Assets:CA:Loft4530:Association Income:CA:Loft4530:Rental Expenses:Loft4530:Acquisition:Legal Expenses:Loft4530:Acquisition:SaleAgent Expenses:Loft4530:Loan-Interest Expenses:Loft4530:Electricity Expenses:Loft4530:Insurance Expenses:Loft4530:Taxes:Municipal Expenses:Loft4530:Taxes:School If you have all of your business in a single country and have no plans to move to another, you might want to skip the country component for brevity. Finally, for \u201cEquity\u201d accounts, well, \u2026. normally you don\u2019t end up defining many of them, because these are mostly created to report the net income and currency conversions from previous years or the current exercise period on the balance sheet. Typically you will need at least one, and it doesn\u2019t matter much what you name it: Equity:Opening-Balances ; Balances used to open accounts You can customize the name of the other Equity accounts that get automatically created for the balance sheet. Choosing an Account Type \uf0c1 Part of the art of learning what accounts to book transactions to is to come up with relevant account names and design a scheme for how money will flow between those accounts, by jotting down some example transactions. It can get a bit creative. As you\u2019re working out how to \u201ccount\u201d all the financial events in your life, you will often end up wondering what account type to select for some of the accounts. Should this be an \u201cAssets\u201d account? Or an \u201cIncome\u201d account? After all, other than for creating reports, Beancount doesn\u2019t treat any of these account types differently\u2026 But this does not mean that you can just use any type willy nilly. Whether an account appears in the balance sheet or income statement does matter\u2014there is usually a correct choice. When in doubt, here are some guidelines to choose an account type. First, if the amounts to be posted to the account are only relevant to be reported for a period of time , they should be one of the income statement accounts: Income or Expenses. On the other hand, if the amount always needs to be included in the total balance of an account, then it should be a balance sheet account: Assets or Liabilities. Second, if the amounts are generally 1 positive, or \u201cgood from your perspective,\u201d the account should be either an Assets or an Expenses account. If the amounts are generally negative, or \u201cbad from your perspective,\u201d the account should be either a Liabilities or an Income account. Based on these two indicators, you should be able to figure out any case. Let\u2019s work through some examples: A restaurant meal represents something that you obtained in exchange for some assets (cash) or a liability (paid by credit card). Nobody ever cares what the \u201csum total of all food since you were born\u201d amounts to. Only the transitional value matters: \u201cHow much did I spend in restaurants this month ?\u201d Or, since the beginning of the year ? Or, during this trip? This clearly points to an Expenses account. But you might wonder\u2026 this is a positive number, but it is money I spent? Yes, the account that you spent from was subtracted from (debited) in exchange for the expense you received . Think of the numbers in the expenses account as things you received that vanish into the ether right after you receive them. These meals are consumed.. and then they go somewhere. Okay, we\u2019ll stop the analogy here. You own some shares of a bond, and receive an interest payment. This interest is cash deposited in an Assets account, for example, a trading account. What is the other leg to be booked to? Choosing Opening Dates \uf0c1 Some of the accounts you need to define don\u2019t correspond to real world accounts. The Expenses:Groceries account represents the sum total of grocery expenses since you started counting. Personally, I like to use my birth date on those. There\u2019s a rationale to it: it sums all the groceries you\u2019ve ever spent money on, and this started only when you came to this world. You can use this rationale on other accounts. For example, all the income accounts associated with an employer should probably be opened at the date you began the job, and end on the date you left. Makes sense. How to Deal with Cash \uf0c1 Let\u2019s start with cash. I typically define two accounts at my birth date: 1973-04-27 open Assets:Cash 1973-04-27 open Assets:ForeignCash The first account is for active use, this represents my wallet, and usually contains only units of my operating currencies, that is, the commodities I usually think of as \u201ccash.\u201d For me, they are USD and CAD commodities. The second account is meant to hold all the paper bills that I keep stashed in a pocket from trips around the world, so they\u2019re out of the way in some other account and I don\u2019t see them in my cash balance. I transfer those to the main account when I do travel to such places, e.g., if I return to Japan, I\u2019ll move my JPY from Assets:ForeignCash to Assets:Cash right before the trip and use them during that time. Cash Withdrawals \uf0c1 An ATM withdrawal from a checking account to cash will typically look like this: 2014-06-28 * \"DDA WITHDRAW 0609C\" Assets:CA:BofA:Checking -700.00 USD Assets:Cash You would see this transaction be imported in your checking account transactions download. Tracking Cash Expenses \uf0c1 One mistake people make when you tell them you\u2019re tracking all of your financial accounts is to assume that you have to book every single little irrelevant cash transaction to a notebook. Not so! It is your choice to decide how many of these cash transactions to take down (or not). Personally, I try to minimize the amount of manual effort I put into updating my Ledger. My rule for dealing with cash is this: If it is for food or alcohol, I don\u2019t track it. If it is for something else, I keep the receipt and enter it later. This works for me, because the great majority of my cash expenses tend to be food (or maybe I just make it that way by paying for everything else with credit cards). Only a few receipts pile up somewhere on my desk for a couple of months before I bother to type them in. However, you will need to make occasional adjustments to your cash account to account for these expenses. I don\u2019t actually bother doing this very often\u2026 maybe once every three months, when I feel like it. The method I use is to take a snapshot of my wallet (manually, by counting the bills) and enter a corresponding balance assertion: 2014-05-12 balance Assets:Cash 234.13 USD Every time I do this I\u2019ll also add a cash distribution adjusted to balance the account: 2014-06-19 * \"Cash distribution\" Expenses:Food:Restaurant 402.30 USD Expenses:Food:Alcohol 100.00 USD Assets:Cash ; -502.30 USD 2014-06-20 balance Assets:Cash 194.34 USD If you wonder why the amounts in the cash account don\u2019t add up (234.13 -502.30 \u2260 194.34), it is because between the two assertions I added to the cash account by doing some ATM withdrawals against the checking account, and those appear somewhere else (in the checking account section). The withdrawal increased the balance of the cash account. It would appear if I rendered a journal for Assets:Cash . I could have made my life simpler and used a Pad directive if I had booked everything to food\u2014pad entries don\u2019t work solely at the beginning of an account\u2019s history, but also between any two balance assertions on the same account\u2014but I want to book 80% of it to food and 20% alcohol, to more accurately represent my real usage of cash 2 . Finally, if you end up with a long time period between the times that you do this, you may want to \u201cspread out\u201d your expenses by adding more than one cash distribution 3 manually, so that if you generate a monthly report, a large cash expense does not appear as a single lump in or outside that month. Salary Income \uf0c1 Accounting for your salary is rewarding: you will be able to obtain a summary of income earned during the year as well as the detail of where the various deductions are going, and you will enjoy the satisfaction of seeing matching numbers from your Beancount reports when you receive your W-2 form from your employer (or on your T4 if you\u2019re located in Canada). I put all entries related to an employer in their own dedicated section. I start it by setting an event to the date I began working there, for example, using the hypothetical company \u201cHooli\u201d (from the Silicon Valley show): 2012-12-13 event \"employer\" \"Hooli Inc.\" This allows me to automatically calculate the number of days I\u2019ve been working there. When I leave a job, I\u2019ll change it to the new one, or an empty string, if I don\u2019t leave for another job: 2019-03-02 event \"employer\" \"\" This section will make several assumptions. The goal is to expose you to the various ideas you can use to account for your income correctly. You will almost certainly end up having to adapt these ideas to your specific situation. Employment Income Accounts \uf0c1 Then you define accounts for your pay stubs. You need to make sure that you have an account corresponding to each line of your pay stub. For example, here are some of the income accounts I define for this employment income at Hooli Inc.: 2012-12-13 open Income:US:Hooli:Salary USD ; \"Regular Pay\" 2012-12-13 open Income:US:Hooli:ReferralBonus USD ; \"Referral bonuses\" 2012-12-13 open Income:US:Hooli:AnnualBonus USD ; \"Annual bonus\" 2012-12-13 open Income:US:Hooli:Match401k USD ; \"Employer 401k match\" 2012-12-13 open Income:US:Hooli:GroupTermLife USD ; \"Group Term Life\" These correspond to regular salary pay, bonuses received for employee referrals, annual bonus, receipts for 401k (Hooli in this example will match some percentage of your contributions to your retirement account), receipts for life insurance (it appears both as an income and an expense), and benefits paid for your gym subscription. There are more, but this is a good example. (In this example I wrote down the names used in the stub as a comment, but you could insert them as metadata instead if you prefer.) You will need to book taxes withheld at the source to accounts for that year (see the tax section for details on this): 2014-01-01 open Expenses:Taxes:TY2014:US:Medicare USD 2014-01-01 open Expenses:Taxes:TY2014:US:Federal USD 2014-01-01 open Expenses:Taxes:TY2014:US:StateNY USD 2014-01-01 open Expenses:Taxes:TY2014:US:CityNYC USD 2014-01-01 open Expenses:Taxes:TY2014:US:SDI USD 2014-01-01 open Expenses:Taxes:TY2014:US:SocSec USD These accounts are for Medicare taxes, Federal, New York State and NYC taxes (yes, New York City residents have an additional tax on top of state tax), state disability insurance (SDI) payments, and finally, taxes to pay for social security. You will also need to have some accounts defined elsewhere for the various expenses that are paid automatically from your pay: 2012-12-13 open Expenses:Health:Life:GroupTermLife USD ; \"Life Ins.\" 2012-12-13 open Expenses:Health:Dental:Insurance USD ; \"Dental\" 2012-12-13 open Expenses:Health:Medical:Insurance USD ; \"Medical\" 2012-12-13 open Expenses:Health:Vision:Insurance USD ; \"Vision\" 2012-12-13 open Expenses:Internet:Reimbursement USD ; \"Internet Reim\" 2012-12-13 open Expenses:Transportation:PreTax USD ; \"Transit PreTax\" These correspond to typical company group plan life insurance payments, premiums for dental, medical and vision insurances, reimbursements for home internet usage, and pre-tax payments for public transit (the city of New York allows you to pay for your MetroCard with pre-tax money through your employer). Booking Salary Deposits \uf0c1 Then, when I import details for a payment via direct deposit to my checking account, it will look like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD If I haven\u2019t received my pay stub yet, I might book it temporarily to the salary account until I do: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD ! Income:US:Hooli:Salary When I receive or fetch my pay stub, I remove this and complete the rest of the postings. A realistic entry for a gross salary of $140,000 would look something like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD Income:US:Hooli:GroupTermLife -25.38 USD Income:US:Hooli:Salary -5384.62 USD Expenses:Health:Dental:Insurance 2.88 USD Expenses:Health:Life:GroupTermLife 25.38 USD Expenses:Internet:Reimbursement -34.65 USD Expenses:Health:Medical:Insurance 36.33 USD Expenses:Transportation:PreTax 56.00 USD Expenses:Health:Vision:Insurance 0.69 USD Expenses:Taxes:TY2014:US:Medicare 78.08 USD Expenses:Taxes:TY2014:US:Federal 1135.91 USD Expenses:Taxes:TY2014:US:CityNYC 75.03 USD Expenses:Taxes:TY2014:US:SDI 1.20 USD Expenses:Taxes:TY2014:US:StateNY 340.06 USD Expenses:Taxes:TY2014:US:SocSec 328.42 USD It\u2019s quite unusual for a salary payment to have no variation at all from its previous one: rounding up or down from the payroll processor will often result in a difference of a penny, social security payments will cap to their maximum, and there are various deductions that will occur from time to time, e.g., deductions on taxable benefits received. Moreover, contributions to a 401k will affect that amounts of taxes withheld at the source. Therefore, you end up having to look at each pay stub individually to enter its information correctly. But this is not as time-consuming as it sounds! Here\u2019s a trick: it\u2019s a lot easier to update your transactions if you list your postings in the same order as they appear on your pay stub. You just copy-paste the previous entry, read the pay stub from top to bottom and adjust the numbers accordingly. It takes a minute for each. It\u2019s worth noting some unusual things about the previous entry. The \u201cgroup term life\u201d entry has both a $25.38 income leg and an expense one. This is because Hooli pays for the premium (it reads exactly like that on the stubs.) Hooli also reimburses some of home internet, because I use it to deal with production issues. This appears as a negative posting to reduce the amount of my expense Expenses:Internet account. Vacation Hours \uf0c1 Our pay stubs also include accrued vacation and the total vacation balance, in vacation hours. You can also track these amounts on the same transactions. You need to declare corresponding accounts: 2012-12-13 open Income:US:Hooli:Vacation VACHR 2012-12-13 open Assets:US:Hooli:Vacation VACHR 2012-12-13 open Expenses:Vacation:Hooli VACHR Vacation that accrues is something you receive and thus is treated as Income in units of \u201cVACHR\u201d, and accumulates in an Assets account, which holds how many of these hours you currently have available to \u201cspend\u201d as time off. Updating the previous salary income transaction entry: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Hooli:Vacation 4.62 VACHR Income:US:Hooli:Vacation -4.62 VACHR 4.62 VACHR on a bi-weekly paycheck 26 times per year is 26 x 4.62 ~= 120 hours. At 8 hours per day, that is 15 work days, or 3 weeks, which is a standard vacation package for new US Hooligans in this example. When you do take time off, you book an expense against your accumulated vacation time: 2014-06-17 * \"Going to the beach today\" Assets:US:Hooli:Vacation -8 VACHR Expenses:Vacation:Hooli The Expenses account tracks how much vacation you\u2019ve used. From time to time you can check that the balance reported on your pay stub\u2014the amount of vacation left that your employer thinks you have\u2014is the same as that which you have accounted for: 2014-02-29 balance Assets:US:Hooli:Vacation 112.3400 VACHR You can \u201cprice\u201d your vacation hour units to your hourly rate, so that your vacations Assets account shows how much the company would pay you if you decided to quit. Assuming that $140,000/year salary, 40 hour weeks and 50 weeks of work, which is 2000 hours per year, we obtain a rate of $70/hour, which you enter like this: 2012-12-13 price VACHR 70.00 USD Similarly, if your vacation hours expires or caps, you can calculate how much dollar-equivalent you\u2019re forfeiting by working too much and giving up your vacation time. You would write off some of the VACHR from your Assets account into an income account (representing losses). 401k Contributions \uf0c1 The 401k plan allows you to make contributions to a tax-deferred retirement account using pre-tax dollars. This is carried out via withholdings from your pay. To account for those, you simply include a posting with the corresponding contribution towards your retirement account: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD \u2026 If you\u2019re accounting for your available contributions (see the tax section of this document), you will want to reduce your \u201c401k contribution\u201d Assets account at the same time. You would add two more postings to the transaction: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD Assets:US:Federal:PreTax401k -1000.00 US401K Expenses:Taxes:TY2014:US:Federal:PreTax401k 1000.00 US401K \u2026 If your employer matches your contributions, this may not show on your pay stubs. Because these contributions are not taxable\u2014they are deposited directly to a tax-deferred account\u2014your employer does not have to include them in the withholding statement. You will see them appear directly in your investment account as deposits. You can book them like this to the retirement account\u2019s tax balance: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:Cash 1173.08 USD And then insert a second transaction when you invest this case, or directly purchasing assets from the contribution if you have specified an asset allocation and this is automated by the broker: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:VMBPX 106.741 VMBPX {10.99 USD} Note that the fund that manages your 401k accounts may be tracking your contributions and your employer\u2019s contributions in separate buckets. You would declare sub-accounts for this and make the corresponding changes: 2012-12-13 open Assets:US:Vanguard:PreTax401k:VMBPX VMBPX 2012-12-13 open Assets:US:Vanguard:Match401k:VMBPX VMBPX It is common for them to do this in order to track each source of contribution separately, because there are several constraints on rollovers to other accounts that depend on it. Vesting Stock Grants \uf0c1 See the dedicated document on this topic for more details. Other Benefits \uf0c1 You can go crazy with tracking benefits if you want. Here are a few wild ideas. Points \uf0c1 If your employer offers a sponsored massage therapy program on-site, you could presumably book a massage out of your paycheck or even from some internal website (if the company is modern), and you could pay for them using some sort of internal points system, say, \u201cHooli points\u201d. You could track those using a made-up currency, e.g., \u201cMASSA\u2019s\u201d and which could be priced at 0.50 USD, the price at which you could purchase them: 2012-12-13 open Assets:US:Hooli:Massage MASSA 2012-12-13 price MASSA 0.50 USD When I purchase new massage points, I 2013-03-15 * \"Buying points for future massages\" Liabilities:US:BofA:CreditCard -45.00 USD Assets:US:Hooli:Massage 90 MASSA {0.50 USD} If you\u2019re occasionally awarded some of these points, and you can track that in an Income account. Food Benefits \uf0c1 Like many of the larger technology companies, Hooli presumably provides free food for its employees. This saves time and encourages people to eat healthy. This is a bit of a trend in the tech world right now. This benefit does not show up anywhere, but if you want to price it as part of your compensation package, you can track it using an Income account: 2012-12-13 open Income:US:Hooli:Food Depending on how often you end up eating at work, you could guesstimate some monthly allowance per month: 2013-06-30 * \"Distribution for food eaten at Hooli\" Income:US:Hooli:Food -350 USD Expenses:Food:Restaurant Currency Transfers & Conversions \uf0c1 If you convert between currencies, such as when performing an international transfer between banks, you need to provide the exchange rate to Beancount. It looks like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF The balance amount of the second posting is calculated as 10,000.00 USD x 0.90 CHF/USD = 9,000 CHF, and the transaction balances. Depending on your preference, you could have placed the rate on the other posting, like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF @ 1.11111 USD Assets:US:BofA:Checking 10000.00 USD The balance amount of the first posting is calculated as -9000.00 CHF x 1.11111 USD/CHF = 10000.00 USD 4 . Typically I will choose the rate that was reported to me and put it on the corresponding side. You may also want to use the direction that F/X markets use for trading the rate, for example, the Swiss franc trades as USD/CHF, so I would prefer the first transaction. The price database converts the rates in both directions, so it is not that important 5 . If you use wire transfers, which is typical for this type of money transfer, you might incur a fee: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9025.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF Expenses:Fees:Wires 25.00 CHF If you convert cash at one of these shady-looking currency exchange parlors found in tourist locations, it might look like this: 2014-03-03 * \"Changed some cash at the airport in Lausanne\" Assets:Cash -400.00 USD @ 0.90 CHF Assets:Cash 355.00 CHF Expenses:Fees:Services 5.00 CHF In any case, you should never convert currency units using the cost basis syntax, because the original conversion rate needs to be forgotten after depositing the units, and not kept around attached to simple currency. For example, this would be incorrect usage: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD {0.90 CHF} ; <-bad! If you did that by mistake, you would incur errors when you attempted to use the newly USD deposited: Beancount would require that you specify the cost of these \u201cUSD\u201d in CHF, e.g., \u201cdebit from my USD that I changed at 0.90 USD/CHF\u201d. Nobody does this in the real world, and neither should you when you represent your transactions: once the money has converted, it\u2019s just money in a different currency, with no associated cost. Finally, a rather subtle problem is that using these price conversions back and forth at different rates over time breaks the accounting equation to some extent: changes in exchange rate may create small amounts of money out of thin air and all the balances don\u2019t end up summing up to zero. However, this is not a problem, because Beancount implements an elegant solution to automatically correct for this problem, so you can use these conversions freely without having to worry about this: it inserts a special conversions entry on the balance sheet to invert the cumulative effect of conversions for the report and obtain a clean balance of zero. (A discussion of the conversions problem is beyond the scope of this cookbook; please refer to Solving the Conversions Problem if you\u2019d like to know more.) Investing and Trading \uf0c1 Tracking trades and associated gains is a fairly involved topic. You will find a more complete introduction to profit and loss and a detailed discussion of various scenarios in the Trading with Beancount document, which is dedicated to this topic. Here we will discuss how to setup your account and provide simple example transactions to get you started. Accounts Setup \uf0c1 You should create an account prefix to root various sub-accounts associated with your investment account. Say you have an account at ETrade, this could be \u201c Assets:US:ETrade \u201d. Choose an appropriate institution name. Your investment account will have a cash component. You should create a dedicated sub-account will represent uninvested cash deposits: 2013-02-01 open Assets:US:ETrade:Cash USD I recommend that you further define a sub-account for each of the commodity types that you will invest in. Although this is not strictly necessary\u2014Beancount accounts may contain any number of commodities\u2014it is a nice way to aggregate all the positions in that commodity together for reporting. Say you will buy shares of LQD and BND, two popular bond ETFs: 2013-02-01 open Assets:US:ETrade:LQD LQD 2013-02-01 open Assets:US:ETrade:BND BND This also helps produce nicer reports: balances are often shown at cost and it\u2019s nice to see the total cost aggregated by commodity for various reasons (i.e., each commodity provides exposure to different market characteristics). Using a dedicated sub-account for each commodity held within an institution is a good way to do that. Unless you have specific reasons not to do so, I highly suggest sticking with this by default (you can always change it later by renaming accounts). Specifying commodity constraints on your accounts will help you detect data entry mistakes. Stock trades tend to be a bit more involved than regular transactions, and this is certainly going to be helpful. Then, you will hopefully receive income in this account, in two forms: capital gains, or \u201cP&L\u201d, and dividends. I like to account for these by institution, because this is how they have to be declared for taxes. You may also receive interest income. Define these: 2013-02-01 open Income:US:ETrade:PnL USD 2013-02-01 open Income:US:ETrade:Dividends USD 2013-02-01 open Income:US:ETrade:Interest USD Finally, to account for transaction fees and commissions, you will need some general accounts to receive these: 1973-04-27 open Expenses:Financial:Fees 1973-04-27 open Expenses:Financial:Commissions Funds Transfers \uf0c1 You will normally add some initial money in this account by making a transfer from an external account, say, a checking account: 2014-02-04 * \"Transferring money for investing\" Assets:US:BofA:Checking -2000.00 USD Assets:US:ETrade:Cash 2000.00 USD Making a Trade \uf0c1 Buying stock should have a posting that deposits the new commodity in the commodity\u2019s sub-account, and debits the cash account to the corresponding amounts plus commissions: 2014-02-16 * \"Buying some LQD\" Assets:US:ETrade:LQD 10 LQD {119.24 USD} Assets:US:ETrade:Cash -1199.35 USD Expenses:Financial:Commissions 6.95 USD Note that when you\u2019re buying units of a commodity, you are establishing a new trade lot in the account\u2019s inventory and it is necessary that you provide the cost of each unit (in this example, 119.24 USD per share of LQD). This allows us to account for capital gains correctly. Selling some of the same stock work similarly, except that an extra posting is added to absorb the capital gain or loss: 2014-02-16 * \"Selling some LQD\" Assets:US:ETrade:LQD -5 LQD {119.24 USD} @ 123.40 USD Assets:US:ETrade:Cash 610.05 USD Expenses:Financial:Commissions 6.95 USD Income:US:Etrade:PnL Note that the postings of shares removed from the Assets:US:ETrade:LQD account is a lot reduction and you must provide information to identify which lot you\u2019re reducing, in this case, by providing the per-share cost basis of 119.24 USD. I normally let Beancount calculate the capital gain or loss for me, which is why I don\u2019t specify it in the last posting. Beancount will automatically balance the transaction by setting the amount of this posting to -20.80 USD, which is a gain of 20.80 USD (remember that the signs are inverted for income accounts). Specifying the sale price of 123.40 USD is optional, and it is ignored for the purpose of balancing the transaction, the cash deposit and commissions legs determine the profit. Receiving Dividends \uf0c1 Receiving dividends takes on two forms. First, you can receive dividends in cash, which will go into the cash account: 2014-02-16 * \"Dividends from LQD\" Income:US:ETrade:Dividends -87.45 USD Assets:US:ETrade:Cash 87.45 USD Note that the source of the dividends isn\u2019t specified here. You could use a sub-account of the income account to count it separately. Or you can receive dividends in shares reinvested: 2014-06-27 * \"Dividends reinvested\" Assets:US:Vanguard:VBMPX 1.77400 VBMPX {10.83 USD} Income:US:Vanguard:Dividends -19.21 USD This is booked similarly to a stock purchase, and you also have to provide the cost basis of the received units. This would typically happen in a non-taxable retirement account. Refer to the Trading with Beancount document for a more thorough discussion and numerous and more complex examples. Choosing a Date \uf0c1 Buying or selling a single lot of stock typically involves multiple events over time: the trade is placed, the trade is filled (usually on the same day), the trade is settled. Settlement usually occurs 2 or 3 business days after the trade is filled. For simplicity, I recommend using the trade date as the date of your transaction. In the US, this is the date that is recognized for tax purposes, and settlement has no impact on your account (brokers typically won\u2019t allow you to trade without the corresponding cash or margin anyhow). So normally I don\u2019t bother creating separate entries for settlement, it\u2019s not very useful. More complex schemes can be envisioned, e.g. you could store the settlement date as a metadata field and then use it in scripts later on, but that\u2019s beyond the scope of this document. Conclusion \uf0c1 This document is incomplete. I have many more example use cases that I\u2019m planning to add here as I complete them. I will be announcing those on the mailing-list as they materialize. In particular, the following topics will be discussed: Health Care Expenses, e.g., insurance premiums and rebates Taxes IRAs, 401k and other tax-deferred accounts Real Estate Options This is not strictly always true: in accounting for companies, some account types are held at their opposite value for reasons, usually to offset the value of another account of the same type. These are called \u201ccontra\u201d accounts. But as an individual, you\u2019re quite unlikely to have one of those. If you\u2019re setting up a chart of accounts for a company, Beancount doesn\u2019t actually care whether the balance is of one sign or other. You declare contra-accounts just like regular accounts. \u21a9 I am considering supporting an extended version of the Pad directive that can take a percentage value and make it possible to pad only a percentage of the full amount, to automate this. \u21a9 Yet another extension to Beancount involves support multiple Pad directives between two balance assertions and automatically support this spreading out of padding directives. \u21a9 If you\u2019re concerned about the issue of precision or rounding in balancing, see this document . \u21a9 Note that if the price database needs to invert the date its calculation may result in a price with a large number of digits. Beancount uses IEEE decimal objects and the default context of the Python implementation is 28 digits, so inverting 0.9 will result in 1.111111\u2026.111 with 28 digits. \u21a9","title":"Command Line Accounting Cookbook"},{"location":"command_line_accounting_cookbook.html#command-line-accounting-cookbook","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/cookbook Introduction A Note of Caution Account Naming Conventions Choosing an Account Type Choosing Opening Dates How to Deal with Cash Cash Withdrawals Tracking Cash Expenses Salary Income Employment Income Accounts Booking Salary Deposits Vacation Hours 401k Contributions Vesting Stock Grants Other Benefits Points Food Benefits Currency Transfers & Conversions Investing and Trading Accounts Setup Funds Transfers Making a Trade Receiving Dividends Conclusion","title":"Command-line Accounting Cookbook"},{"location":"command_line_accounting_cookbook.html#introduction","text":"The best way to learn the double-entry method is to look at real-world examples. The method is elegant, but it can seem unintuitive to the newcomer how transactions have to be posted in order to perform the various operations that one needs to do in counting for different types financial events. This is why I wrote this cookbook. It is not meant to be a comprehensive description of all the features supported, but rather a set of practical guidelines to help you solve problems. I think this will likely be the most useful document in the Beancount documentation set! All the examples here apply to any double-entry accounting system: Ledger, GnuCash, or even commercial systems. Some of the details may differ only slightly. This cookbook is written using the syntax and calculation method of the Beancount software. This document also assumes that you are already familiar with the general balancing concepts of the double-entry method and with at least some of the syntax of Beancount which is available from its user\u2019s manual or its cheat sheet . If you haven\u2019t begun writing down your first file, you will want to read Getting Started with Beancount and do that first. Command-line accounting systems are agnostic about the types of things they can count and allow you to get creative with the kinds of units that you can invent to track various kinds of things. For instance, you can count \u201cIRA contribution dollars,\u201d which are not real dollars, but which correspond to \u201cpossible contributions in real dollars,\u201d and you obtain accounts of assets, income and expenses types for them - it works. Please do realize that some of those clever tricks may not be possible in more traditional accounting systems. In addition, some of the operations that would normally require a manual process in these systems can be automated away for us, e.g., \u201cclosing a year\u201d is entirely done by the software at any point in time, and balance assertions provide a safeguard that allow us to change the details of past transactions with little risk, so there is no need to \u201creconcile\u201d by baking the past into a frozen state. More flexibility is at hand. Finally, if you have a transaction entry problem that is not covered in this document, please do leave a comment in the margin, or write up your problem to the Ledger mailing-list . I would like for this document to cover as many realistic scenarios as possible.","title":"Introduction"},{"location":"command_line_accounting_cookbook.html#a-note-of-caution","text":"While reading this, please take note that the author is a dilettante: I am a computer scientist, not an accountant. In fact, apart from a general course I took in college and having completed the first year of a CFA program, I have no real training in accounting. Despite this, I do have some practical experience in maintaining three set of books using this software: my personal ledger (8 years worth of full financial data for all accounts), a joint ledger with my spouse, and the books of a contracting and consulting company I used to own. I also used my double-entry system to communicate with my accountant for many years and he made suggestions. Nevertheless\u2026 I may be making fundamental mistakes here and there, and I would appreciate you leaving a comment in the margin if you find anything dubious.","title":"A Note of Caution"},{"location":"command_line_accounting_cookbook.html#account-naming-conventions","text":"You can define any account name you like, as long as it begins with one of the five categories: Assets, Liabilities, Income, Expenses, or Equity (note that you can customize those names with options - see the Language Syntax document for details). The accounts names are generally defined to have multiple name components, separated by a colon (:), which imply an accounts hierarchy, or \u201c chart of accounts \u201d: Assets:Component1:Component2:Component3:... Over time, I\u2019ve iterated over many ways of defining my account names and I have converged to the following convention for Assets, Liabilities, and Income accounts: Type : Country : Institution : Account : SubAccount What I like about this is that when you render a balance sheet, the tree that gets rendered nicely displays accounts by country first, then by institution. Some example account names: Assets:US:BofA:Savings ; Bank of America \u201cSavings\u201d account Assets:CA:RBC:Checking ; Royal Bank of Canada \u201cChecking\u201d account Liabilities:US:Amex:Platinum ; American Express Platinum credit card Liabilities:CA:RBC:Mortgage ; Mortgage loan account at RBC Income:US:ETrade:Interest ; Interest payments in E*Trade account Income:US:Acme:Salary ; Salary income from ACME corp. Sometimes I use a further sub-account or two, when it makes sense. For example, Vanguard internally keeps separate accounts depending on whether the contributions were from the employee or the employer\u2019s matching amount: Assets:US:Vanguard:Contrib401k:RGAGX ; My contributions to this fund Assets:US:Vanguard:Match401k:RGAGX ; Employer contributions For investment accounts, I tend organize all their contents by storing each particular type of stock in its own sub-account: Assets:US:ETrade:Cash ; The cash contents of the account Assets:US:ETrade:FB ; Shares of Facebook Assets:US:ETrade:AAPL ; Shares of Apple Assets:US:ETrade:MSFT ; Shares of Microsoft \u2026 This automatically organizes the balance sheet by types of shares, which I find really nice. Another convention that I like is to use the same institution component name when I have different related types of accounts. For instance, the E*Trade assets account above has associated income streams that would be booked under similarly named accounts: Income:US:ETrade:Interest ; Interest income from cash deposits Income:US:ETrade:Dividends ; Dividends received in this account Income:US:ETrade:PnL ; Capital gains or losses from trades \u2026 For \u201cExpenses\u201d accounts, I find that there are generally no relevant institutions. For those it makes more sense to treat them as categories and just have a simple hierarchy that corresponds to the kinds of expenses they count, some examples: Expenses:Sports:Scuba ; All matters of diving expenses Expenses:Transport:Train ; Train (mostly Amtrak, but not always) Expenses:Transport:Bus ; Various \u201cchinese bus\u201d companies Expenses:Transport:Flights ; Flights (various airlines) \u2026 I have a lot of these, like 250 or more. It is really up to you to decide how many to define and how finely to aggregate or \u201ccategorize\u201d your expenses this way. But of course, you should only define them as you need them; don\u2019t bother defining a huge list ahead of time. It\u2019s always easy to add new ones. It is worth noting that the institution does not have to be a \u201creal\u201d institution. For instance, I owned a condo unit in a building, and I used the Loft4530 \u201cinstitution\u201d for all its related accounts: Assets:CA:Loft4530:Property Assets:CA:Loft4530:Association Income:CA:Loft4530:Rental Expenses:Loft4530:Acquisition:Legal Expenses:Loft4530:Acquisition:SaleAgent Expenses:Loft4530:Loan-Interest Expenses:Loft4530:Electricity Expenses:Loft4530:Insurance Expenses:Loft4530:Taxes:Municipal Expenses:Loft4530:Taxes:School If you have all of your business in a single country and have no plans to move to another, you might want to skip the country component for brevity. Finally, for \u201cEquity\u201d accounts, well, \u2026. normally you don\u2019t end up defining many of them, because these are mostly created to report the net income and currency conversions from previous years or the current exercise period on the balance sheet. Typically you will need at least one, and it doesn\u2019t matter much what you name it: Equity:Opening-Balances ; Balances used to open accounts You can customize the name of the other Equity accounts that get automatically created for the balance sheet.","title":"Account Naming Conventions"},{"location":"command_line_accounting_cookbook.html#choosing-an-account-type","text":"Part of the art of learning what accounts to book transactions to is to come up with relevant account names and design a scheme for how money will flow between those accounts, by jotting down some example transactions. It can get a bit creative. As you\u2019re working out how to \u201ccount\u201d all the financial events in your life, you will often end up wondering what account type to select for some of the accounts. Should this be an \u201cAssets\u201d account? Or an \u201cIncome\u201d account? After all, other than for creating reports, Beancount doesn\u2019t treat any of these account types differently\u2026 But this does not mean that you can just use any type willy nilly. Whether an account appears in the balance sheet or income statement does matter\u2014there is usually a correct choice. When in doubt, here are some guidelines to choose an account type. First, if the amounts to be posted to the account are only relevant to be reported for a period of time , they should be one of the income statement accounts: Income or Expenses. On the other hand, if the amount always needs to be included in the total balance of an account, then it should be a balance sheet account: Assets or Liabilities. Second, if the amounts are generally 1 positive, or \u201cgood from your perspective,\u201d the account should be either an Assets or an Expenses account. If the amounts are generally negative, or \u201cbad from your perspective,\u201d the account should be either a Liabilities or an Income account. Based on these two indicators, you should be able to figure out any case. Let\u2019s work through some examples: A restaurant meal represents something that you obtained in exchange for some assets (cash) or a liability (paid by credit card). Nobody ever cares what the \u201csum total of all food since you were born\u201d amounts to. Only the transitional value matters: \u201cHow much did I spend in restaurants this month ?\u201d Or, since the beginning of the year ? Or, during this trip? This clearly points to an Expenses account. But you might wonder\u2026 this is a positive number, but it is money I spent? Yes, the account that you spent from was subtracted from (debited) in exchange for the expense you received . Think of the numbers in the expenses account as things you received that vanish into the ether right after you receive them. These meals are consumed.. and then they go somewhere. Okay, we\u2019ll stop the analogy here. You own some shares of a bond, and receive an interest payment. This interest is cash deposited in an Assets account, for example, a trading account. What is the other leg to be booked to?","title":"Choosing an Account Type"},{"location":"command_line_accounting_cookbook.html#choosing-opening-dates","text":"Some of the accounts you need to define don\u2019t correspond to real world accounts. The Expenses:Groceries account represents the sum total of grocery expenses since you started counting. Personally, I like to use my birth date on those. There\u2019s a rationale to it: it sums all the groceries you\u2019ve ever spent money on, and this started only when you came to this world. You can use this rationale on other accounts. For example, all the income accounts associated with an employer should probably be opened at the date you began the job, and end on the date you left. Makes sense.","title":"Choosing Opening Dates"},{"location":"command_line_accounting_cookbook.html#how-to-deal-with-cash","text":"Let\u2019s start with cash. I typically define two accounts at my birth date: 1973-04-27 open Assets:Cash 1973-04-27 open Assets:ForeignCash The first account is for active use, this represents my wallet, and usually contains only units of my operating currencies, that is, the commodities I usually think of as \u201ccash.\u201d For me, they are USD and CAD commodities. The second account is meant to hold all the paper bills that I keep stashed in a pocket from trips around the world, so they\u2019re out of the way in some other account and I don\u2019t see them in my cash balance. I transfer those to the main account when I do travel to such places, e.g., if I return to Japan, I\u2019ll move my JPY from Assets:ForeignCash to Assets:Cash right before the trip and use them during that time.","title":"How to Deal with Cash"},{"location":"command_line_accounting_cookbook.html#cash-withdrawals","text":"An ATM withdrawal from a checking account to cash will typically look like this: 2014-06-28 * \"DDA WITHDRAW 0609C\" Assets:CA:BofA:Checking -700.00 USD Assets:Cash You would see this transaction be imported in your checking account transactions download.","title":"Cash Withdrawals"},{"location":"command_line_accounting_cookbook.html#tracking-cash-expenses","text":"One mistake people make when you tell them you\u2019re tracking all of your financial accounts is to assume that you have to book every single little irrelevant cash transaction to a notebook. Not so! It is your choice to decide how many of these cash transactions to take down (or not). Personally, I try to minimize the amount of manual effort I put into updating my Ledger. My rule for dealing with cash is this: If it is for food or alcohol, I don\u2019t track it. If it is for something else, I keep the receipt and enter it later. This works for me, because the great majority of my cash expenses tend to be food (or maybe I just make it that way by paying for everything else with credit cards). Only a few receipts pile up somewhere on my desk for a couple of months before I bother to type them in. However, you will need to make occasional adjustments to your cash account to account for these expenses. I don\u2019t actually bother doing this very often\u2026 maybe once every three months, when I feel like it. The method I use is to take a snapshot of my wallet (manually, by counting the bills) and enter a corresponding balance assertion: 2014-05-12 balance Assets:Cash 234.13 USD Every time I do this I\u2019ll also add a cash distribution adjusted to balance the account: 2014-06-19 * \"Cash distribution\" Expenses:Food:Restaurant 402.30 USD Expenses:Food:Alcohol 100.00 USD Assets:Cash ; -502.30 USD 2014-06-20 balance Assets:Cash 194.34 USD If you wonder why the amounts in the cash account don\u2019t add up (234.13 -502.30 \u2260 194.34), it is because between the two assertions I added to the cash account by doing some ATM withdrawals against the checking account, and those appear somewhere else (in the checking account section). The withdrawal increased the balance of the cash account. It would appear if I rendered a journal for Assets:Cash . I could have made my life simpler and used a Pad directive if I had booked everything to food\u2014pad entries don\u2019t work solely at the beginning of an account\u2019s history, but also between any two balance assertions on the same account\u2014but I want to book 80% of it to food and 20% alcohol, to more accurately represent my real usage of cash 2 . Finally, if you end up with a long time period between the times that you do this, you may want to \u201cspread out\u201d your expenses by adding more than one cash distribution 3 manually, so that if you generate a monthly report, a large cash expense does not appear as a single lump in or outside that month.","title":"Tracking Cash Expenses"},{"location":"command_line_accounting_cookbook.html#salary-income","text":"Accounting for your salary is rewarding: you will be able to obtain a summary of income earned during the year as well as the detail of where the various deductions are going, and you will enjoy the satisfaction of seeing matching numbers from your Beancount reports when you receive your W-2 form from your employer (or on your T4 if you\u2019re located in Canada). I put all entries related to an employer in their own dedicated section. I start it by setting an event to the date I began working there, for example, using the hypothetical company \u201cHooli\u201d (from the Silicon Valley show): 2012-12-13 event \"employer\" \"Hooli Inc.\" This allows me to automatically calculate the number of days I\u2019ve been working there. When I leave a job, I\u2019ll change it to the new one, or an empty string, if I don\u2019t leave for another job: 2019-03-02 event \"employer\" \"\" This section will make several assumptions. The goal is to expose you to the various ideas you can use to account for your income correctly. You will almost certainly end up having to adapt these ideas to your specific situation.","title":"Salary Income"},{"location":"command_line_accounting_cookbook.html#employment-income-accounts","text":"Then you define accounts for your pay stubs. You need to make sure that you have an account corresponding to each line of your pay stub. For example, here are some of the income accounts I define for this employment income at Hooli Inc.: 2012-12-13 open Income:US:Hooli:Salary USD ; \"Regular Pay\" 2012-12-13 open Income:US:Hooli:ReferralBonus USD ; \"Referral bonuses\" 2012-12-13 open Income:US:Hooli:AnnualBonus USD ; \"Annual bonus\" 2012-12-13 open Income:US:Hooli:Match401k USD ; \"Employer 401k match\" 2012-12-13 open Income:US:Hooli:GroupTermLife USD ; \"Group Term Life\" These correspond to regular salary pay, bonuses received for employee referrals, annual bonus, receipts for 401k (Hooli in this example will match some percentage of your contributions to your retirement account), receipts for life insurance (it appears both as an income and an expense), and benefits paid for your gym subscription. There are more, but this is a good example. (In this example I wrote down the names used in the stub as a comment, but you could insert them as metadata instead if you prefer.) You will need to book taxes withheld at the source to accounts for that year (see the tax section for details on this): 2014-01-01 open Expenses:Taxes:TY2014:US:Medicare USD 2014-01-01 open Expenses:Taxes:TY2014:US:Federal USD 2014-01-01 open Expenses:Taxes:TY2014:US:StateNY USD 2014-01-01 open Expenses:Taxes:TY2014:US:CityNYC USD 2014-01-01 open Expenses:Taxes:TY2014:US:SDI USD 2014-01-01 open Expenses:Taxes:TY2014:US:SocSec USD These accounts are for Medicare taxes, Federal, New York State and NYC taxes (yes, New York City residents have an additional tax on top of state tax), state disability insurance (SDI) payments, and finally, taxes to pay for social security. You will also need to have some accounts defined elsewhere for the various expenses that are paid automatically from your pay: 2012-12-13 open Expenses:Health:Life:GroupTermLife USD ; \"Life Ins.\" 2012-12-13 open Expenses:Health:Dental:Insurance USD ; \"Dental\" 2012-12-13 open Expenses:Health:Medical:Insurance USD ; \"Medical\" 2012-12-13 open Expenses:Health:Vision:Insurance USD ; \"Vision\" 2012-12-13 open Expenses:Internet:Reimbursement USD ; \"Internet Reim\" 2012-12-13 open Expenses:Transportation:PreTax USD ; \"Transit PreTax\" These correspond to typical company group plan life insurance payments, premiums for dental, medical and vision insurances, reimbursements for home internet usage, and pre-tax payments for public transit (the city of New York allows you to pay for your MetroCard with pre-tax money through your employer).","title":"Employment Income Accounts"},{"location":"command_line_accounting_cookbook.html#booking-salary-deposits","text":"Then, when I import details for a payment via direct deposit to my checking account, it will look like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD If I haven\u2019t received my pay stub yet, I might book it temporarily to the salary account until I do: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD ! Income:US:Hooli:Salary When I receive or fetch my pay stub, I remove this and complete the rest of the postings. A realistic entry for a gross salary of $140,000 would look something like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD Income:US:Hooli:GroupTermLife -25.38 USD Income:US:Hooli:Salary -5384.62 USD Expenses:Health:Dental:Insurance 2.88 USD Expenses:Health:Life:GroupTermLife 25.38 USD Expenses:Internet:Reimbursement -34.65 USD Expenses:Health:Medical:Insurance 36.33 USD Expenses:Transportation:PreTax 56.00 USD Expenses:Health:Vision:Insurance 0.69 USD Expenses:Taxes:TY2014:US:Medicare 78.08 USD Expenses:Taxes:TY2014:US:Federal 1135.91 USD Expenses:Taxes:TY2014:US:CityNYC 75.03 USD Expenses:Taxes:TY2014:US:SDI 1.20 USD Expenses:Taxes:TY2014:US:StateNY 340.06 USD Expenses:Taxes:TY2014:US:SocSec 328.42 USD It\u2019s quite unusual for a salary payment to have no variation at all from its previous one: rounding up or down from the payroll processor will often result in a difference of a penny, social security payments will cap to their maximum, and there are various deductions that will occur from time to time, e.g., deductions on taxable benefits received. Moreover, contributions to a 401k will affect that amounts of taxes withheld at the source. Therefore, you end up having to look at each pay stub individually to enter its information correctly. But this is not as time-consuming as it sounds! Here\u2019s a trick: it\u2019s a lot easier to update your transactions if you list your postings in the same order as they appear on your pay stub. You just copy-paste the previous entry, read the pay stub from top to bottom and adjust the numbers accordingly. It takes a minute for each. It\u2019s worth noting some unusual things about the previous entry. The \u201cgroup term life\u201d entry has both a $25.38 income leg and an expense one. This is because Hooli pays for the premium (it reads exactly like that on the stubs.) Hooli also reimburses some of home internet, because I use it to deal with production issues. This appears as a negative posting to reduce the amount of my expense Expenses:Internet account.","title":"Booking Salary Deposits"},{"location":"command_line_accounting_cookbook.html#vacation-hours","text":"Our pay stubs also include accrued vacation and the total vacation balance, in vacation hours. You can also track these amounts on the same transactions. You need to declare corresponding accounts: 2012-12-13 open Income:US:Hooli:Vacation VACHR 2012-12-13 open Assets:US:Hooli:Vacation VACHR 2012-12-13 open Expenses:Vacation:Hooli VACHR Vacation that accrues is something you receive and thus is treated as Income in units of \u201cVACHR\u201d, and accumulates in an Assets account, which holds how many of these hours you currently have available to \u201cspend\u201d as time off. Updating the previous salary income transaction entry: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Hooli:Vacation 4.62 VACHR Income:US:Hooli:Vacation -4.62 VACHR 4.62 VACHR on a bi-weekly paycheck 26 times per year is 26 x 4.62 ~= 120 hours. At 8 hours per day, that is 15 work days, or 3 weeks, which is a standard vacation package for new US Hooligans in this example. When you do take time off, you book an expense against your accumulated vacation time: 2014-06-17 * \"Going to the beach today\" Assets:US:Hooli:Vacation -8 VACHR Expenses:Vacation:Hooli The Expenses account tracks how much vacation you\u2019ve used. From time to time you can check that the balance reported on your pay stub\u2014the amount of vacation left that your employer thinks you have\u2014is the same as that which you have accounted for: 2014-02-29 balance Assets:US:Hooli:Vacation 112.3400 VACHR You can \u201cprice\u201d your vacation hour units to your hourly rate, so that your vacations Assets account shows how much the company would pay you if you decided to quit. Assuming that $140,000/year salary, 40 hour weeks and 50 weeks of work, which is 2000 hours per year, we obtain a rate of $70/hour, which you enter like this: 2012-12-13 price VACHR 70.00 USD Similarly, if your vacation hours expires or caps, you can calculate how much dollar-equivalent you\u2019re forfeiting by working too much and giving up your vacation time. You would write off some of the VACHR from your Assets account into an income account (representing losses).","title":"Vacation Hours"},{"location":"command_line_accounting_cookbook.html#401k-contributions","text":"The 401k plan allows you to make contributions to a tax-deferred retirement account using pre-tax dollars. This is carried out via withholdings from your pay. To account for those, you simply include a posting with the corresponding contribution towards your retirement account: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD \u2026 If you\u2019re accounting for your available contributions (see the tax section of this document), you will want to reduce your \u201c401k contribution\u201d Assets account at the same time. You would add two more postings to the transaction: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD Assets:US:Federal:PreTax401k -1000.00 US401K Expenses:Taxes:TY2014:US:Federal:PreTax401k 1000.00 US401K \u2026 If your employer matches your contributions, this may not show on your pay stubs. Because these contributions are not taxable\u2014they are deposited directly to a tax-deferred account\u2014your employer does not have to include them in the withholding statement. You will see them appear directly in your investment account as deposits. You can book them like this to the retirement account\u2019s tax balance: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:Cash 1173.08 USD And then insert a second transaction when you invest this case, or directly purchasing assets from the contribution if you have specified an asset allocation and this is automated by the broker: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:VMBPX 106.741 VMBPX {10.99 USD} Note that the fund that manages your 401k accounts may be tracking your contributions and your employer\u2019s contributions in separate buckets. You would declare sub-accounts for this and make the corresponding changes: 2012-12-13 open Assets:US:Vanguard:PreTax401k:VMBPX VMBPX 2012-12-13 open Assets:US:Vanguard:Match401k:VMBPX VMBPX It is common for them to do this in order to track each source of contribution separately, because there are several constraints on rollovers to other accounts that depend on it.","title":"401k Contributions"},{"location":"command_line_accounting_cookbook.html#vesting-stock-grants","text":"See the dedicated document on this topic for more details.","title":"Vesting Stock Grants"},{"location":"command_line_accounting_cookbook.html#other-benefits","text":"You can go crazy with tracking benefits if you want. Here are a few wild ideas.","title":"Other Benefits"},{"location":"command_line_accounting_cookbook.html#points","text":"If your employer offers a sponsored massage therapy program on-site, you could presumably book a massage out of your paycheck or even from some internal website (if the company is modern), and you could pay for them using some sort of internal points system, say, \u201cHooli points\u201d. You could track those using a made-up currency, e.g., \u201cMASSA\u2019s\u201d and which could be priced at 0.50 USD, the price at which you could purchase them: 2012-12-13 open Assets:US:Hooli:Massage MASSA 2012-12-13 price MASSA 0.50 USD When I purchase new massage points, I 2013-03-15 * \"Buying points for future massages\" Liabilities:US:BofA:CreditCard -45.00 USD Assets:US:Hooli:Massage 90 MASSA {0.50 USD} If you\u2019re occasionally awarded some of these points, and you can track that in an Income account.","title":"Points"},{"location":"command_line_accounting_cookbook.html#food-benefits","text":"Like many of the larger technology companies, Hooli presumably provides free food for its employees. This saves time and encourages people to eat healthy. This is a bit of a trend in the tech world right now. This benefit does not show up anywhere, but if you want to price it as part of your compensation package, you can track it using an Income account: 2012-12-13 open Income:US:Hooli:Food Depending on how often you end up eating at work, you could guesstimate some monthly allowance per month: 2013-06-30 * \"Distribution for food eaten at Hooli\" Income:US:Hooli:Food -350 USD Expenses:Food:Restaurant","title":"Food Benefits"},{"location":"command_line_accounting_cookbook.html#currency-transfers-conversions","text":"If you convert between currencies, such as when performing an international transfer between banks, you need to provide the exchange rate to Beancount. It looks like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF The balance amount of the second posting is calculated as 10,000.00 USD x 0.90 CHF/USD = 9,000 CHF, and the transaction balances. Depending on your preference, you could have placed the rate on the other posting, like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF @ 1.11111 USD Assets:US:BofA:Checking 10000.00 USD The balance amount of the first posting is calculated as -9000.00 CHF x 1.11111 USD/CHF = 10000.00 USD 4 . Typically I will choose the rate that was reported to me and put it on the corresponding side. You may also want to use the direction that F/X markets use for trading the rate, for example, the Swiss franc trades as USD/CHF, so I would prefer the first transaction. The price database converts the rates in both directions, so it is not that important 5 . If you use wire transfers, which is typical for this type of money transfer, you might incur a fee: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9025.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF Expenses:Fees:Wires 25.00 CHF If you convert cash at one of these shady-looking currency exchange parlors found in tourist locations, it might look like this: 2014-03-03 * \"Changed some cash at the airport in Lausanne\" Assets:Cash -400.00 USD @ 0.90 CHF Assets:Cash 355.00 CHF Expenses:Fees:Services 5.00 CHF In any case, you should never convert currency units using the cost basis syntax, because the original conversion rate needs to be forgotten after depositing the units, and not kept around attached to simple currency. For example, this would be incorrect usage: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD {0.90 CHF} ; <-bad! If you did that by mistake, you would incur errors when you attempted to use the newly USD deposited: Beancount would require that you specify the cost of these \u201cUSD\u201d in CHF, e.g., \u201cdebit from my USD that I changed at 0.90 USD/CHF\u201d. Nobody does this in the real world, and neither should you when you represent your transactions: once the money has converted, it\u2019s just money in a different currency, with no associated cost. Finally, a rather subtle problem is that using these price conversions back and forth at different rates over time breaks the accounting equation to some extent: changes in exchange rate may create small amounts of money out of thin air and all the balances don\u2019t end up summing up to zero. However, this is not a problem, because Beancount implements an elegant solution to automatically correct for this problem, so you can use these conversions freely without having to worry about this: it inserts a special conversions entry on the balance sheet to invert the cumulative effect of conversions for the report and obtain a clean balance of zero. (A discussion of the conversions problem is beyond the scope of this cookbook; please refer to Solving the Conversions Problem if you\u2019d like to know more.)","title":"Currency Transfers & Conversions"},{"location":"command_line_accounting_cookbook.html#investing-and-trading","text":"Tracking trades and associated gains is a fairly involved topic. You will find a more complete introduction to profit and loss and a detailed discussion of various scenarios in the Trading with Beancount document, which is dedicated to this topic. Here we will discuss how to setup your account and provide simple example transactions to get you started.","title":"Investing and Trading"},{"location":"command_line_accounting_cookbook.html#accounts-setup","text":"You should create an account prefix to root various sub-accounts associated with your investment account. Say you have an account at ETrade, this could be \u201c Assets:US:ETrade \u201d. Choose an appropriate institution name. Your investment account will have a cash component. You should create a dedicated sub-account will represent uninvested cash deposits: 2013-02-01 open Assets:US:ETrade:Cash USD I recommend that you further define a sub-account for each of the commodity types that you will invest in. Although this is not strictly necessary\u2014Beancount accounts may contain any number of commodities\u2014it is a nice way to aggregate all the positions in that commodity together for reporting. Say you will buy shares of LQD and BND, two popular bond ETFs: 2013-02-01 open Assets:US:ETrade:LQD LQD 2013-02-01 open Assets:US:ETrade:BND BND This also helps produce nicer reports: balances are often shown at cost and it\u2019s nice to see the total cost aggregated by commodity for various reasons (i.e., each commodity provides exposure to different market characteristics). Using a dedicated sub-account for each commodity held within an institution is a good way to do that. Unless you have specific reasons not to do so, I highly suggest sticking with this by default (you can always change it later by renaming accounts). Specifying commodity constraints on your accounts will help you detect data entry mistakes. Stock trades tend to be a bit more involved than regular transactions, and this is certainly going to be helpful. Then, you will hopefully receive income in this account, in two forms: capital gains, or \u201cP&L\u201d, and dividends. I like to account for these by institution, because this is how they have to be declared for taxes. You may also receive interest income. Define these: 2013-02-01 open Income:US:ETrade:PnL USD 2013-02-01 open Income:US:ETrade:Dividends USD 2013-02-01 open Income:US:ETrade:Interest USD Finally, to account for transaction fees and commissions, you will need some general accounts to receive these: 1973-04-27 open Expenses:Financial:Fees 1973-04-27 open Expenses:Financial:Commissions","title":"Accounts Setup"},{"location":"command_line_accounting_cookbook.html#funds-transfers","text":"You will normally add some initial money in this account by making a transfer from an external account, say, a checking account: 2014-02-04 * \"Transferring money for investing\" Assets:US:BofA:Checking -2000.00 USD Assets:US:ETrade:Cash 2000.00 USD","title":"Funds Transfers"},{"location":"command_line_accounting_cookbook.html#making-a-trade","text":"Buying stock should have a posting that deposits the new commodity in the commodity\u2019s sub-account, and debits the cash account to the corresponding amounts plus commissions: 2014-02-16 * \"Buying some LQD\" Assets:US:ETrade:LQD 10 LQD {119.24 USD} Assets:US:ETrade:Cash -1199.35 USD Expenses:Financial:Commissions 6.95 USD Note that when you\u2019re buying units of a commodity, you are establishing a new trade lot in the account\u2019s inventory and it is necessary that you provide the cost of each unit (in this example, 119.24 USD per share of LQD). This allows us to account for capital gains correctly. Selling some of the same stock work similarly, except that an extra posting is added to absorb the capital gain or loss: 2014-02-16 * \"Selling some LQD\" Assets:US:ETrade:LQD -5 LQD {119.24 USD} @ 123.40 USD Assets:US:ETrade:Cash 610.05 USD Expenses:Financial:Commissions 6.95 USD Income:US:Etrade:PnL Note that the postings of shares removed from the Assets:US:ETrade:LQD account is a lot reduction and you must provide information to identify which lot you\u2019re reducing, in this case, by providing the per-share cost basis of 119.24 USD. I normally let Beancount calculate the capital gain or loss for me, which is why I don\u2019t specify it in the last posting. Beancount will automatically balance the transaction by setting the amount of this posting to -20.80 USD, which is a gain of 20.80 USD (remember that the signs are inverted for income accounts). Specifying the sale price of 123.40 USD is optional, and it is ignored for the purpose of balancing the transaction, the cash deposit and commissions legs determine the profit.","title":"Making a Trade"},{"location":"command_line_accounting_cookbook.html#receiving-dividends","text":"Receiving dividends takes on two forms. First, you can receive dividends in cash, which will go into the cash account: 2014-02-16 * \"Dividends from LQD\" Income:US:ETrade:Dividends -87.45 USD Assets:US:ETrade:Cash 87.45 USD Note that the source of the dividends isn\u2019t specified here. You could use a sub-account of the income account to count it separately. Or you can receive dividends in shares reinvested: 2014-06-27 * \"Dividends reinvested\" Assets:US:Vanguard:VBMPX 1.77400 VBMPX {10.83 USD} Income:US:Vanguard:Dividends -19.21 USD This is booked similarly to a stock purchase, and you also have to provide the cost basis of the received units. This would typically happen in a non-taxable retirement account. Refer to the Trading with Beancount document for a more thorough discussion and numerous and more complex examples.","title":"Receiving Dividends"},{"location":"command_line_accounting_cookbook.html#choosing-a-date","text":"Buying or selling a single lot of stock typically involves multiple events over time: the trade is placed, the trade is filled (usually on the same day), the trade is settled. Settlement usually occurs 2 or 3 business days after the trade is filled. For simplicity, I recommend using the trade date as the date of your transaction. In the US, this is the date that is recognized for tax purposes, and settlement has no impact on your account (brokers typically won\u2019t allow you to trade without the corresponding cash or margin anyhow). So normally I don\u2019t bother creating separate entries for settlement, it\u2019s not very useful. More complex schemes can be envisioned, e.g. you could store the settlement date as a metadata field and then use it in scripts later on, but that\u2019s beyond the scope of this document.","title":"Choosing a Date"},{"location":"command_line_accounting_cookbook.html#conclusion","text":"This document is incomplete. I have many more example use cases that I\u2019m planning to add here as I complete them. I will be announcing those on the mailing-list as they materialize. In particular, the following topics will be discussed: Health Care Expenses, e.g., insurance premiums and rebates Taxes IRAs, 401k and other tax-deferred accounts Real Estate Options This is not strictly always true: in accounting for companies, some account types are held at their opposite value for reasons, usually to offset the value of another account of the same type. These are called \u201ccontra\u201d accounts. But as an individual, you\u2019re quite unlikely to have one of those. If you\u2019re setting up a chart of accounts for a company, Beancount doesn\u2019t actually care whether the balance is of one sign or other. You declare contra-accounts just like regular accounts. \u21a9 I am considering supporting an extended version of the Pad directive that can take a percentage value and make it possible to pad only a percentage of the full amount, to automate this. \u21a9 Yet another extension to Beancount involves support multiple Pad directives between two balance assertions and automatically support this spreading out of padding directives. \u21a9 If you\u2019re concerned about the issue of precision or rounding in balancing, see this document . \u21a9 Note that if the price database needs to invert the date its calculation may result in a price with a large number of digits. Beancount uses IEEE decimal objects and the default context of the Python implementation is 28 digits, so inverting 0.9 will result in 1.111111\u2026.111 with 28 digits. \u21a9","title":"Conclusion"},{"location":"command_line_accounting_in_context.html","text":"Command-line Accounting in Context \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/motivation This text provides context and motivation for the usage of double-entry command-line accounting systems to manage personal finances. Motivation What exactly is \u201cAccounting\u201d? What can it do for me? Keeping Books Generating Reports Custom Scripting Filing Documents How the Pieces Fit Together Why not just use a spreadsheet? Why not just use a commercial app? How about Mint.com? How about Quicken? How about Quickbooks? How about GnuCash? Why build a computer language? Advantages of Command-Line Bookkeeping Why not just use an SQL database? But... I just want to do X? Why am I so Excited? Motivation \uf0c1 When I tell people about my command-line accounting hacking activities, I get a variety of reactions. Sometimes I hear echoes of desperation about their own finances: many people wish they had a better handle and understanding of their finances, they sigh and wish they were better organized. Other times, after I describe my process I get incredulous reactions: \u201cWhy do you even bother?\u201d Those are the people who feel that their financial life is so simple that it can be summarized in 5 minutes. These are most often blissfully unaware how many accounts they have and have only an imprecise idea of where they stand. They have at least 20 accounts to their name; it\u2019s only when we go through the details that they realize that their financial ecosystem is more complex than they thought. Finally, there are those who get really excited about the idea of using a powerful accounting system but who don\u2019t actually understand what it is that I\u2019m talking about. They might think it is a system to support investment portfolios, or something that I use to curate a budget like a stickler. Usually they end up thinking it is too complicated to actually get started with. This document attempts to explain what command-line bookkeeping is about in concrete terms, what you will get out of doing it, how the various software pieces fit together, and what kinds of results you can expect from this method. The fact is, the double-entry method is a basic technique that everyone should have been taught in high school. And using it for yourself is a simple and powerful process you can use to drive your entire financial life. What exactly is \u201cAccounting\u201d? \uf0c1 When one talks about \u201caccounting,\u201d they often implicitly refer to one or more of various distinct financial processes: Bookkeeping. Recording past transactions in a single place, a \u201cledger,\u201d also called \u201cthe books,\u201d as in, \u201cthe books of this company.\u201d Essentially, this means copying the amounts and category of financial transactions that occur in external account statements to a single system that includes all of the \u201caccounts\u201d relating to an entity and links them together. This is colloquially called \u201ckeeping books,\u201d or the activity of \u201cbookkeeping.\u201d Invoices. Preparing invoices and tracking payments. Contractors will often bring this up because it is a major concern of their activity: issuing requests to clients for payments for services rendered, and checking whether the corresponding payments have actually been received later on (and taking collection actions in case they haven\u2019t). If you\u2019re managing a company\u2019s finances, processing payroll is another aspect of this which is heavy in bookkeeping. Taxes. Finding or calculating taxable income, filling out tax forms and filing taxes to the various governmental authorities the entity is subject to. This process can be arduous, and for people who are beginning to see an increase in the complexity of their personal assets (many accounts of different types, new reporting requirements), it can be quite stressful. This is often the moment that they start thinking about organizing themselves. Expenses. Analyzing expenses, basically answering the question: \u201cWhere is my money going?\u201d A person with a small income and many credit cards may want to precisely track and calculate how much they\u2019re spending every month. This just develops awareness. Even in the presence of abundant resources, it is interesting to look at how much one is spending regularly, and where most of one\u2019s regular income is going, it often brings up surprises. Budgeting. Forecasting future expenses, allocating limited amounts to spend in various categories, and tracking how close one\u2019s actual spending is to those allocations. For individuals, this is usually in the context of trying to pay down debt or finding ways to save more. For companies, this occurs when planning for various projects. Investing. Summarizing and computing investment returns and capital gains. Many of us now manage our own savings via discount brokers, investing via ETFs and individually selected mutual funds. It is useful to be able to view asset distribution and risk exposure, as well as compute capital gains. Reporting. Public companies have regulatory requirements to provide transparency to their investors. As such, they usually report an annual income statement and a beginning and ending balance sheet . For an individual, those same reports are useful when applying for a personal loan or a mortgage at a bank, as they provide a window to someone\u2019s financial health situation. In this document, I will describe how command-line accounting can provide help and support for these activities. What can it do for me? \uf0c1 You might legitimately ask: sure, but why bother? We can look at the uses of accounting in terms of the questions it can answer for you: Where\u2019s my money, Lebowski? If you don\u2019t keep track of stuff, use cash a lot, have too many credit cards or if you are simply a disorganized and brilliant artist with his head in the clouds, you might wonder why there aren\u2019t as many beans left at the end of the month as you would like. An income statement will answer this question. I\u2019d like to be like Warren Buffet. How much do I have to save every month? I personally don\u2019t do a budget, but I know many people who do. If you set specific financial goals for a project or to save for later, the first thing you need to do is allocate a budget and set limits on your spending. Tracking your money is the first step towards doing that. You could even compute your returns in a way that can be compared against the market. I have some cash to invest. Given my portfolio, where should I invest it? Being able to report on the totality of your holdings, you can determine your asset class and currency exposures. This can help you decide where to place new savings in order to match a target portfolio allocation. How much am I worth? You have a 401k, an IRA and taxable accounts. A remaining student loan, or perhaps you\u2019re sitting on real-estate with two mortgages. Whatever. What\u2019s the total? It\u2019s really nice to obtain a single number that tells you how far you are from your retirement goals. Beancount can easily compute your net worth (to the cent). I still hurt from 2008. Are my investments safe? For example, I\u2019m managing my own money with various discount brokers, and many people are. I\u2019m basically running a miniature hedge fund using ETFs, where my goal is to be as diversified as possible between asset classes, sector exposure, currency exposure, etc. Plus, because of tax-deferred accounts I cannot do that in a single place. With Beancount you can generate a list of holdings and aggregate them by category. Taxes suck. Well, enough said. Why do they suck? Mostly because of uncertainty and doubt. It\u2019s not fun to not know what\u2019s going on. If you had all the data at your fingertips instantly it wouldn\u2019t be nearly as bad. What do you have to report? For some people there\u2019s a single stream of income, but for many others, it gets complicated (you might even be in that situation and you don\u2019t know it). Qualified vs. ordinary dividends, long-term vs. short-term capital gains, income from secondary sources, wash sales, etc. When it\u2019s time to do my taxes, I bring up the year\u2019s income statement, and I have a clear list of items to put in and deductions I can make. I want to buy a home. How much can I gather for a down payment? If you needed a lot of cash all of a sudden, how much could you afford? Well, if you can produce a list of your holdings, you can aggregate them \u201cby liquidity.\u201d This gives you an idea of available cash in case of an urgent need. Mr. Banker, please lend me some money. Impress the banker geek: just bring your balance sheet. Ideally the one generated fresh from that morning. You should have your complete balance sheet at any point in time. Instructions for your eventual passing. Making a will involves listing your assets. Make your children\u2019s lives easier should you pass by having a complete list of all the beans to be passed on or collected. The amounts are just part of the story: being able to list all the accounts and institutions will make it easier for someone to clear your assets. I can\u2019t remember if they paid me. If you send out invoices and have receivables, it\u2019s nice to have a method to track who has paid and who just says they\u2019ll pay. I\u2019m sure there is a lot more you can think of. Let me know. Keeping Books \uf0c1 Alright, so here\u2019s the part where I give you the insight of the method. The central and most basic activity that provides support for all the other ones is bookkeeping . A most important and fundamental realization is that this relatively simple act\u2014that of copying all of the financial transactions into a single integrated system\u2014allows you to produce reports that solve all of the other problems. You are building a corpus of data, a list of dated transaction objects that each represents movements of money between some of the accounts that you own. This data provides a full timeline of your financial activity. All you have to do is enter each transaction while respecting a simple constraint, that of the double-entry method , which is this: Each time an amount is posted to an account, it must have a corresponding inverse amount posted to some other account(s), and the sum of these amounts must be zero. That\u2019s it. This is the essence of the method, and an ensemble of transactions that respects this constraint acquires nice properties which are discussed in detail in some of my other documents. In order to generate sensible reports, all accounts are further labeled with one of four categories: Assets , Liabilities , Income and Expenses . A fifth category, Equity , exists only to summarize the history of all previous income and expense transactions. Command-line accounting systems are just simplistic computer languages for conveniently entering, reading and processing this transaction data, and ensure that the constraint of the double-entry method is respected. They\u2019re simple counting systems, that can add any kind of \u201cthing\u201d in dedicated counters (\u201caccounts\u201d). They\u2019re basically calculators with multiple buckets. Beancount and Ledger may differ slightly in their syntax and on some of their semantics, but the general principle is the same, and the simplifying assumptions are very similar. Here is a concrete example of what a transaction looks like, just so you can get a feeling for it. In a text file\u2014which is the input to a command-line accounting system\u2014something like the following is written and expresses a credit card transaction: 2014-05-23 * \"CAFE MOGADOR NEW YO\" \"Dinner with Caroline\" Liabilities:US:BofA:CreditCard -98.32 USD Expenses:Restaurant This is an example transaction with two \u201cpostings,\u201d or \u201clegs.\u201d The \u201c Expenses:Restaurant \u201d line is an account, not a category, though accounts often act like categories (there is no distinction between these two concepts). The amount on the expenses leg is left unspecified, and this is a convenience allowed by the input language. The software determines its amount automatically with the remainder of the balance which in this case will be 98.32 USD, so that -98.32 USD + 98.32 USD = 0 (remember that the sum of all postings must balance to zero\u2014this is enforced by the language). Most financial institutions provide some way for you to download a summary of account activity in some format or other. Much of the details of your transactions are automatically pulled in from such a downloaded file, using some custom script you write that converts it into the above syntax: A transaction date is always available from the downloadable files. The \u201c CAFE MOGADOR NEW YO \u201d bit is the \u201cmemo,\u201d also provided by the downloaded file, and those names are often good enough for you to figure out what the business you spent at was. Those memos are the same ugly names you would see appear on your credit card statements. This is attached to a transaction as a \u201cpayee\u201d attribute. I manually added \u201c Dinner with Caroline \u201d as a comment. I don\u2019t have to do this (it\u2019s optional), but I like to do it when I reconcile new transactions, it takes me only a minute and it helps me remember past events if I look for them. The importer brought in the Liabilities:US:BofA:CreditCard posting with its amount automatically, but I\u2019ve had to insert the Expenses:Restaurant account myself: I typed it in. I have shortcuts in my text editor that allow me to do that using account name completion, it takes a second and it\u2019s incredibly easy. Furthermore, pre-selecting this account could be automated as well, by running a simple learning algorithm on the previous history contained in the same input file (we tend to go to the same places all the time). The syntax gets a little bit more complicated, for example it allows you to represent stock purchases and sales, tracking the cost basis of your assets, and there are many other types of conveniences, like being able to define and count any kind of \u201cthing\u201d in an account (e.g., \u201cvacation hours accumulated\u201d), but this example captures the essence of what I do. I replicate all the transactions from all of the accounts that I own in this way, for the most part in an automated fashion. I spend about 1-2 hours every couple of weeks in order to update this input file, and only for the most used accounts (credit card and checking accounts). Other accounts I\u2019ll update every couple of months, or when I need to generate some reports. Because I have a solid understanding of my finances, this is not a burden anymore\u2026 it has become fun . What you obtain is a full history, a complete timeline of all the financial transactions from all the accounts over time, often connected together, your financial life, in a single text file . Generating Reports \uf0c1 So what\u2019s the point of manicuring this file? I have code that reads it, parses it, and that can serve various views and aggregations of it on a local web server running on my machine, or produce custom reports from this stream of data. The most useful views are undeniably the balance sheet and income statement , but there are others: Balance Sheet. A balance sheet lists the final balance of all of your assets and liabilities accounts on a single page. This is a snapshot of all your accounts at a single point in time, a well-understood overview of your financial situation. This shows your net worth (very precisely, if you update all your accounts) and is also what a banker would be interested in if you were to apply for a loan. Beancount can produce my balance sheet at any point in time, but most often I\u2019m interested in the \u201ccurrent\u201d or \u201clatest\u201d balance sheet. Income Statement. An income statement is a summary of all income and expenses that occur between two points in time. It renders the final balance for these accounts in format familiar to any accountant: income accounts on the left, expense accounts on the right. This provides insights on the changes that occurred during a time period, a delta of changes. This tells you how much you\u2019re earning and where your money is going, in detail. The difference between the two is how much you saved. Beancount can render such a statement for any arbitrary period of time, e.g., this year, or month by month. Journals. For each account (or category, if you like to think of them that way), I can render a list of all the transactions that posted changes to that account. I call this a journal (Ledger calls this a \u201cregister\u201d). If you\u2019re looking at your credit card account\u2019s journal, for instance, it should match that of your bank statement. On the other hand, if you look at your \u201crestaurants expense\u201d account, it should show all of your restaurant outings, across all methods of payment (including cash, if you choose to enter that). You can easily fetch any detail of your financial history, like \u201cWhere was that place I had dinner with Arthur in March three years ago?\u201d or \u201cI want to sell that couch\u2026 how much did I pay for it again?\u201d Payables and Receivables. If you have amounts known to be received, for example, you filed your taxes or sent an invoice and are expecting a payment, or you mailed a check and you are expecting it to be cashed at some point in the future, you can track this with dedicated accounts. Transactions have syntax that allows you to link many of them together and figure out what has been paid or received. Calculating taxes. If you enter your salary deposits with the detail from your pay stub, you can calculate exactly how much taxes you\u2019ve paid, and the amounts should match exactly those that will be reported on your employer\u2019s annual income reporting form (e.g., W2 form in the USA, T4 if in Canada, Form P60 in the UK, or otherwise if you live elsewhere). This is particularly convenient if you have to make tax installments, for instance. If you\u2019re a contractor, you will have to track many such things, including company expensable expenses. It is also useful to count dividends that you have to report as taxable income. Investing. I\u2019m not quite rich, but I manage my own assets using discount brokers and mainly ETFs. In addition, I take advantage of a variety of tax sheltered accounts, with limited choices in assets. This is pretty common now. You could say that I\u2019m managing a miniature hedge fund and you wouldn\u2019t be too far from reality. Using Beancount, I can produce a detailed list of all holdings, with reports on daily market value changes, capital gains, dividends, asset type distribution (e.g. stocks vs. fixed income), currency exposure, and I can figure out how much of my assets are liquid, e.g., how much I have available towards a downpayment on a house. Precisely, and at any point in time. This is nice. I can also produce various aggregations of the holdings, i.e., value by account, by type of instrument, by currency. Forecasting. The company I work for has an employee stock plan, with a vesting schedule. I can forecast my approximate expected income including the vesting shares under reasonable assumptions. I can answer the question: \u201cAt this rate, at which date do I reach a net worth of X?\u201d Say, if X is the amount which you require for retirement. Sharing Expenses. If you share expenses with others, like when you go on a trip with friends, or have a roommate, the double-entry method provides a natural and easy solution to reconciling expenses together. You can also produce reports of the various expenses incurred for clarity. No uncertainty. Budgeting. I make enough money that I don\u2019t particularly set specific goals myself, but I plan to support features to do this. You basically end up managing your personal finances like a company would\u2026 but it\u2019s very easy because you\u2019re using a simple and cleverly designed computer language that makes a lot of simplifications (doing away with the concepts of \u201ccredit and debits\u201d for example), reducing the process to its essential minimum. Custom Scripting \uf0c1 The applications are endless. I have all sorts of wild ideas for generating reports for custom projects. These are useful and fun experiments, \u201cchallenges\u201d as I call them. Some examples: I once owned a condo unit and I\u2019ve been doing double-entry bookkeeping throughout the period I owned it, through selling it. All of the corresponding accounts share the Loft4530 name in them. This means that I could potentially compute the precise internal rate of return on all of the cash flows related to it, including such petty things as replacement light bulbs expenses. To consider it as a pure investment. Just for fun. I can render a tree-map of my annual expenses and assets. This is a good visualization of these categories, that preserve their relative importance. I could look at average expenses with a correction for the time-value of money. This would be fun, tell me how my cost of living has changed over time. The beauty of it is that once you have the corpus of data, which is relatively easy to create if you maintain it incrementally, you can do all sorts of fun things by writing a little bit of Python code. I built Beancount to be able to do that in two ways: By providing a \u201cplugins\u201d system that allows you to filter a parsed set of transactions. This makes it possible for you to hack the syntax of Beancount and prototype new conveniences in data entry. Plugins provided by default provide extended features just by filtering and transforming the list of parsed transactions. And it\u2019s simple: all you have to do is implement a callback function with a particular signature and add a line to your input file. By writing scripts. You can parse and obtain the contents of a ledger with very little code. In Python, this looks no more complicated than this: import beancount.loader \u2026 entries, errors, options = beancount.loader.load_file('myfile.ledger') for entry in entries: \u2026 Voila. You\u2019re on your way to spitting out whatever output you want. You have access to all the libraries in the Python world, and my code is mostly functional, heavily documented and thoroughly unit-tested. You should be able to find your way easily. Moreover, if you\u2019re uncertain about using this system, you could just use it to begin entering your data and later write a script that converts it into something else. Filing Documents \uf0c1 If you have defined accounts for each of your real-world accounts, you have also created a natural method for organizing your statements and other paper documents. As we are communicating more and more by email with our accountants and institutions, it is becoming increasingly common to scan letters to PDFs and have those available as computer files. All the banks have now gone paperless, and you can download these statements if you care (I tend to do this once at the end of the year, for preservation, just in case). It\u2019s nice to be able to organize these nicely and retrieve those documents easily. What I do is keep a directory hierarchy mirroring the account names that I\u2019ve defined, something that looks like this: .../documents/ Assets/ US/ TDBank/ Checking/ 2014-04-08.statement.march.pdf 2014-05-07.statement.april.pdf \u2026 Liabilities/ US/ Amex/ Platinum/ 2014-04-17.March.pdf 2014-04-19.download.ofx \u2026 Expenses/ Health/ Medical/ 2014-04-02.anthem.eob-physiotherapy.pdf \u2026 These example files would correspond to accounts with names Assets:US:TDBank:Checking , Liabilities:US:Amex:Platinum , and Expenses:Health:Medical . I keep this directory under version control. As long as a file name begins with a date, such as \u201c 2014-04-02 \u201d, Beancount is able to find the files automatically and insert directives that are rendered in its web interface as part of an account\u2019s journal, which you can click on to view the document itself. This allows me to find all my statements in one place, and if I\u2019m searching for a document, it has a well-defined place where I know to find it. Moreover, the importing software I wrote is able to identify downloaded files and automatically move them into the corresponding account\u2019s directory. How the Pieces Fit Together \uf0c1 So I\u2019ve described what I do to organize my finances. Here I\u2019ll tell you about the various software pieces and how they fit together: Beancount is the core of the system. It reads the text file I update, parses the computer language I\u2019ve defined and produces reports from the resulting data structures. This software only reads the input file and does not communicate with other programs on purpose. It runs in isolation. Beancount\u2019s ingest package and tools help automate the updating of account data by extracting transactions from file downloads from banks and other institutions. These tools orchestrate the running of importers which you implement (this is where all the messy importing code lives, the code you need to make it easier to keep your text file up-to-date, which can parse OFX and CSV files, for instance). See bean-extract, bean-identify tools. The ingest package also helps with filing documents (see bean-file tool). Because it is able to identify which document belongs to which account, it can move the downloaded file to my documents archive automatically. This saves me time. Finally, in order to provide market values, a Beancount input file should have suitable price directives. Beancount also contains code to fetch latest or historical prices for the various commodities present in one\u2019s ledger file (see the bean-price tool). Like the extraction of transactions from OFX downloads, it also spits out Beancount input syntax used to define prices. See the diagram below for a pretty picture that illustrates how these work together. Why not just use a spreadsheet? \uf0c1 This is indeed a good question, and spreadsheets are incredibly useful for sure. I certainly would not be writing my own software if I could track my finances with a spreadsheet. The problem is that the intrinsic structure of the double-entry transactional data does not lend itself to a tabular representation. Each transaction has multiple attributes (date, narration, tags), and two or more legs, each of which has an associated amount and possibly a cost. If you put the dates on one axis and the accounts on the other, you would end up with a very sparse and very large table; that would not be very useful, and it would be incredibly difficult to edit. If on the other hand, you had a column dedicated to account names for each row, all of your computations would have to take the account cell into the calculation logic. It would be very difficult to deal with, if not impossible. All matters of data aggregations are performed relative to account names. Moreover, dealing with the accumulation of units with a cost basis would require difficult gymnastics. I don\u2019t even know how I would proceed forward to do this in a spreadsheet. A core part of command-line accounting systems is the inventory logic that allows you to track a cost basis for every unit held in an account and book reductions of positions against existing lots only. This allows the system to compute capital gains automatically. And this is related to the codes that enforce the constraint that all postings on a transaction balance out to zero. I believe that the \u201ctransaction <-> postings\u201d data representation combined with a sensible method for updating inventories is the essence of this system. The need for these is the justification to create a dedicated method to build this data, such as a computer language. Finally, having our own syntax offers the opportunity to provide other types of useful directives such as balance assertions, open/close dates, and sophisticated filtering of subsets of transactions. You just cannot do what we\u2019re doing in a spreadsheet. Why not just use a commercial app? \uf0c1 How about Mint.com? \uf0c1 Oftentimes people tell me they\u2019re using Mint.com to track their finances, usually with some amount of specific complaints following close behind. Online services such as Mint provide a subset of the functionality of double-entry accounting. The main focus of these services is the reporting of expenses by category, and the maintenance of a budget. As such, they do a great job at automating the download of your credit card and bank transactions. I think that if you want a low-maintenance option to tracking your finances and you don\u2019t have a problem with the obvious privacy risks, this is a fantastic option: it comes with a web interface, mobile apps, and updating your accounts probably requires clicking some buttons, providing some passwords, and a small amount of manual corrections every couple of weeks. I have several reservations about using it for myself, however: Passwords. I\u2019m just not comfortable enough with any commercial company to share the passwords to all my bank, credit card, and investment accounts. This sounds like an insane idea to me, a very scary one, and I\u2019m just not willing to put that much trust in any one company\u2019s hands. I don\u2019t care what they say: I worked for several software companies and I\u2019m aware of the disconnect between promises made by salespeople, website PR, and engineering reality. I also know the power of determined computer hackers. Insufficient reporting. Reporting is probably beautiful and colorful\u2014it\u2019s gorgeous on the website\u2014but certainly insufficient for all the reporting I want to do on my data. Reporting that doesn\u2019t do what they want it to is a common complaint I hear about these systems. With my system, I can always write a script and produce any kind of report I might possibly want in the future. For instance, spitting out a treemap visualization of my expenses instead of a useless pie chart. I can slice and dice my transactions in all kinds of unique ways. I can tag subsets of transactions for projects or trips and report on them. It\u2019s more powerful than generic reporting. Perennity. What if the company is not in business in 5 years? Where will my data be? If I spend any time at all curating my financial data, I want to ensure that it will be available to me forever, in an open format. In their favor, some of these sites probably have a downloadable option, but who uses it? Does it even work well? Would it include all of the transactions that they downloaded? I don\u2019t know. Not international. It probably does not work well with an international, multi-currency situation. These services target a \u201cmajority\u201d of users, most of which have all their affairs in a single country. I live in the USA, have a past history in Canada, which involves remaining tax-sheltered investment accounts, occasional expenses during visits which justify maintaining a credit card there, and a future history which might involve some years spent in another country such as Hong Kong or Australia. Will this work in Mint? Probably not. They might support Canada, but will they support accounts in both places? How about some other country I might want to move to? I want a single integrated view of all my accounts across all countries in all currencies forever, nothing less. My system supports that very well. (I\u2019m not aware of any double-entry system that deals with the international problem in a currency agnostic way as well as Beancount does.) Inability to deal with particular situations. What if I own real estate? Will I be able to price the value of my home at a reasonable amount, so it creates an accurate balance sheet? Regularly obtaining \u201ccomparables\u201d for my property from an eager real estate agent will tell me much more precisely how much my home is worth than services like Zillow ever could. I need to be able to input that for my balance sheet. What about those stock options from that privately held Australian company I used to work for? How do I price that? How about other intangible things, such as receivables from a personal loan I made to a friend? I doubt online services are able to provide you with the ability to enter those. If you want the whole picture with precision, you need to be able to make these adjustments. Custom tracking. Using \u201cimaginary currencies\u201d, I\u2019m able to track all kinds of other things than currencies and stocks. For example, by using an \u201c IRAUSD \u201d commodity in Beancount I\u2019m able to track how many 401k contributions I\u2019ve made at any point during the year. I can count the after-tax basis of a traditional IRA account similarly. I can even count my vacation hours using an imaginary \u201c VACHR \u201d currency and verify it against my pay stubs. Capital gains reporting. I haven\u2019t tried it myself, but I\u2019ve heard some discontent about Mint from others about its limited capabilities for capital gains reporting. Will it maintain trade lots? Will it handle average cost booking? How about PnL on FOREX gains? What about revenue received for that book I\u2019m selling via PayPal? I want to be able to use my accounting system as an input for my tax reporting. Cash transactions. How difficult is it to enter cash transactions? Do I have to log in, or start a slow, heavy program that will only work under Windows? With Beancount I bring up a file in a text editor, this is instant and easy. Is it even possible to enter custom cash entries in online services? In other words, I\u2019m a very sophisticated user and yes, a bit of a control freak . I\u2019m not lying about it. I don\u2019t have any ideological objections about using a commercial service, it is probably not for me. If it\u2019s good enough for you, suit yourself. Despite their beautiful and promising websites, I haven\u2019t heard of anyone being completely satisfied with these services. I hear a fair amount of complaining, some mild satisfaction, but I have yet to meet anyone who raves about it. How about Quicken? \uf0c1 Quicken is a single-entry system, that is, it replicates the transactions of a remote account locally, and allows you to add a label to each transaction to place it in a category. I believe it also has support for synchronizing investment accounts. This is not enough for me, I want to track all kinds of things, and I want to use the double-entry method, which provides an intrinsic check that I\u2019ve entered my data correctly. Single-entry accounting is just not good enough if you\u2019ve already crossed the bridge of understanding the double-entry method. How about Quickbooks? \uf0c1 So let\u2019s talk about sophisticated software that is good enough for a company. Why wouldn\u2019t I use that instead? If it\u2019s good enough for small businesses, it should be good enough for me, no? There\u2019s Quickbooks and other ones. Why don\u2019t I use them: It costs money. Commercial software comes at a price. Ok, I probably could afford to pay a few hundred dollars per year (Quickbooks 2014 looks like around 300$/year for the full set of features), but I don\u2019t really want to. Platform. These softwares usually run on Microsoft Windows and sometimes on Mac OS X . I\u2019m a software developer, I mostly use Linux , and a Macbook Air for a laptop, on which I get annoyed running anything other than tmux and a web browser . I\u2019m not going to reboot just to enter a quick cash transaction. Slow startup. I cannot speak specifically to Quickbooks\u2019 implementation, but virtually every software suite of commercial scope I\u2019ve had to use had a splash screen and a slow, slow startup that involved initializing tons of plugins. They assume you\u2019re going to spend hours in it, which is reasonable for commercial users, but not for me, if I want to do a quick update of my ledger. UIs are inadequate. I haven\u2019t seen their UI but given the nature of transactions and my desire to input precisely and search quickly and organize things, I want to be able to edit in as text. I imagine it would be inconvenient for me. With Emacs and org-mode , I can easily i-search my way to any transaction within seconds after opening my ledger file. Inflexible. How would I go about re-organizing all my account names? I think I\u2019m still learning about the double-entry bookkeeping method and I have made mistakes in the past, mistakes where I desire to revisit the way I organize my accounts in a hierarchy. With my text file, I was able to safely rename a large number of accounts several times, and evolve my chart-of-accounts to reflect my improving understanding of how to organize my financial tracking system. Text is powerful! How about GnuCash? \uf0c1 I don\u2019t like UIs; they\u2019re inconvenient. There\u2019s nothing quite like editing a text file if you are a programmer. Moreover, GnuCash does not deal with multiple currencies well. I find bugs in it within the first hour every time I kick the tires on it, which I do every couple of years, out of curiosity. Other programs, such as Skrooge, also take the heavy-handed big UI approach. Why build a computer language? \uf0c1 A bookkeeping system provides conditions for a solution that involves a simple computer language for many reasons. Single-entry bookkeeping is largely insufficient if you're trying to track everything holistically. Existing systems either limit themselves to expense categories with little checking beyond \"reconciling\" which sometimes involves freezing the past. If you're not doing the bookkeeping for a company, sometimes just changing the past and fixing the mistakes where they occurred makes more sense. More importantly, the single-entry method leaves us wanting for the natural error-checking mechanism involved in the double-entry system. The problem is also not solvable elegantly by using spreadsheets; the simple data structure that forms the basis of the double-entry system infers either a very sparse spreadsheet with accounts on one dimension and transactions on the other. For real-world usage, this is impractical. Another iteration on this theme would involve inserting the postings with two columns, one with the account and one with the amount, but the varying number of columns and the lookup code makes this inelegant as well. Plus, it's not obvious how you would deal with a large number of currencies. Programs that provide fancy graphical or web-based user interfaces are inevitably awkward, due to the nature of the problem: each transaction is organized by viewing it through the lens of one account's journal, but any of the accounts present in its postings provide equally valid views. Ideally, what you want, is just to look at the transaction. Organizing them for most convenient input has little to do with the order in which they are to be presented. Using text has a lot of advantages: You can easily used search-and-replace and/or sed to make global changes, for example, rename or reorganize your accounts; You can organize the transactions in the order that is most convenient for data entry; There are a number of existing tools to search the text; You can easily write various little tools to spit out the data syntax, i.e., for importing from other file types, or converting from other systems. Text is inherently open , that is the file format is one that you can read your data from and store anywhere else, not a blob of incomprehensible data that becomes unusable when the company that makes the software stops supporting it. Finally, systems that attempt to automate the process of importing all your data from automated sources (e.g., mint.com) have one major shortfall: most often it's not very easy or even possible to add information for accounts that aren't automated. It is my experience that in practice you will have some entries and accounts to track that will not have a nice downloadable file format, or that simply don't have a real-world counterpart. In order to produce a complete view of one's balance sheet, it is important to be able to enter all of an individual's account within a single system. In other words, custom accounts and manually entered transactions do matter a lot. For all the reasons mentioned above, I feel that a computer language is more appropriate to express this data structure than a heavy piece of software with a customized interface. Being able to easily bring up a text file and quickly type in a few lines of text to add a transaction is great\u2013it's fast and easy. The ledger file provides a naturally open way to express one's data, and can be source-controlled or shared between people as well. Multiple files can be merged together, and scripted manipulations on a source file can be used to reorganize one's data history. Furthermore, a read-only web interface that presents the various reports one expects and allows the user to explore different views on the dataset is sufficient for the purpose of viewing the data. Advantages of Command-Line Bookkeeping \uf0c1 In summary, there are many advantages to using a command-line accounting system over a commercial package that provides a user-interface: Fast. You don\u2019t have to fire up a slow program with a splash screen that will take a while to initialize in order to add something to your books. Bringing up a text file from a bookmark in your favorite editing program (I use Emacs) is easy and quick. And if you\u2019re normally using a text editor all day long, as many programmers do, you won\u2019t even blink before the file is in front of your eyes. It\u2019s quick and easy. Portable. It will work on all platforms. Beancount is written in Python 3 with some C extensions, and as such will work on Mac, Linux and Windows platforms. I am very careful and determined to keep external dependencies on third-party packages to an absolute minimum in order to avoid installation problems. It should be easy to install and update, and work everywhere the same. Openness. Your data is open, and will remain open forever . I plan on having my corpus of data until the day I die. With an open format you will never end up in a situation where your transactional data is sitting in a binary blob with an unknown format and the software goes unsupported. Furthermore, your data can be converted to other languages easily. You can easily invoke the parser I provide and write a script that spits it out in another program\u2019s input syntax. You can place it under version control. You can entirely reorganize the structure of your accounts by renaming strings with sed. Text is empowering. Customized. You can produce very customized reports, that address exactly the kinds of problems you are having. One complaint I often hear from people about other financial software is that it doesn\u2019t quite do what they want. With command-line accounting systems you can at least write it yourself, as a small extension that uses your corpus of data. Why not just use an SQL database? \uf0c1 I don\u2019t like to reinvent the wheel. If this problem could be solved by filling up an SQL database and then making queries on it, that\u2019s exactly what I would do. Creating a language is a large overhead, it needs to be maintained and evolved, it\u2019s not an easy task, but as it turns out, necessary and justified to solve this problem. The problem is due to a few reasons: Filtering occurs on a two-level data structure of transactions vs. their children postings, and it is inconvenient to represent the data in a single table upon which we could then make manipulations. You cannot simply work only with postings: in order to ensure that the reports balance, you need to be selecting complete transactions. When you select transactions, the semantics is \u201cselect all the postings for which the transaction has this or that property.\u201d One such property is \u201call transactions that have some posting with account X .\u201d These semantics are not obvious to implement with a database. The nature of the data structure makes it inconvenient. The wide variety of directives makes it difficult to design a single elegant table that can accommodate all of their data. Ideally we would want to define two tables for each type of directive: a table that holds all common data (date, source filename & lineno, directive type) and a table to hold the type-specific data. While this is possible, it steps away from the neat structure of a single table of data. Operations on inventories\u2014the data structure that holds the incrementally changing contents of accounts\u2014require special treatment that would be difficult to implement in a database. Lot reductions are constrained against a running inventory of lots, each of which has a specific cost basis. Some lot reductions trigger the merging of lots (for average cost booking). This requires some custom operations on these inventory objects. Aggregations (breakdowns) are hierarchical in nature. The tree-like structure of accounts allows us to perform operations on subtrees of postings. This would also not be easy to implement with tables. Finally, input would be difficult. By defining a language, we side-step the problem of having to build a custom user interface that would allow us to create the data. Nevertheless, I really like the idea of working with databases. A script (bean-sql) is provided to convert the contents of a Beancount file to an SQL database; it\u2019s not super elegant to carry out computations on those tables, but it\u2019s provided nonetheless. You should be able to play around with it, even if operations are difficult. I might even pre-compute some of the operation\u2019s outputs to make it easier to run SQL queries. But... I just want to do X ? \uf0c1 Some may infer that what we\u2019re doing must be terribly complicated given that they envision they might want to use such a system only for a single purpose. But the fact is, how many accounts you decide to track is a personal choice. You can choose to track as little as you want in as little detail as you deem sufficient. For example, if all that you\u2019re interested in is investing, then you can keep books on only your investment accounts. If you\u2019re interested in replacing your usage of Mint.com or Quicken , you can simply just replicate the statements for your credit cards and see your corresponding expenses. The simplicity of having to just replicate the transactions in all your accounts over doing a painful annual \u201cstarting from scratch\u201d evaluation of all your assets and expenses for the purpose of looking at your finances will convince you. Looking at your finances with a spreadsheet will require you to at least copy values from your portfolio. Every time you want to generate the report you\u2019ll have to update the values\u2026 with my method, you just update each account\u2019s full activity, and you can obtain the complete list holdings as a by-product. It\u2019s easy to bring everything up-to-date if you have a systematic method. To those I say: try it out, accounting just for the bits that you\u2019re interested in. Once you\u2019ll have a taste of how the double-entry method works and have learned a little bit about the language syntax, you will want to get a fuller picture. You will get sucked in. Why am I so Excited? \uf0c1 Okay, so I\u2019ve become an accounting nerd. I did not ask for it. Just kind-of happened while I wasn\u2019t looking (I still don\u2019t wear brown socks though). Why is it I can\u2019t stop talking about this stuff? I used to have a company. It was an umbrella for doing contract work (yes, I originally had bigger aspirations for it, but it ended up being just that. Bleh.) As a company owner in Canada, I had to periodically make five different kinds of tax installments to the Federal and Provincial governments, some every month, some every three months, and then it varied. An accountant was feeding me the \u201cmagic numbers\u201d to put in the forms, numbers which I was copying like a monkey, without fully understanding how they were going to get used later on. I had to count separately the various expenses I would incur in relation to my work (for deductions), and those which were only personal \u2014I did not have the enlightened view of getting separate credit cards so I was trying to track personal vs. company expenses from the same accounts. I also had to track transfers between company and personal accounts that would later on get reported as dividends or salary income. I often had multiple pending invoices that would take more than two months to get paid, from different clients. It was a demi-hell. You get the idea. I used to try to track all these things manually, using systems composed of little text files and spreadsheets and doing things very carefully and in a particular way. The thing is, although I am by nature quite methodical, I would sometimes, just sometimes forget to update one of the files. Things would fall out of sync. When this happened it was incredibly frustrating, I had to spend time digging around various statements and figure out where I had gone wrong. This was time-consuming and unpleasant. And of course, when something you have to do is unpleasant, you tend not to do it so well. I did not have a solid idea of the current state of affairs of my company during the year, I just worked and earned money for it. My accountant would draw a balance sheet once a year after doing taxes in July, and it was always a tiny bit of a surprise. I felt permanently somewhat confused, and frankly annoyed every time I had to deal with \u201cmoney things.\u201d It wasn\u2019t quite a nightmare, but it was definitely making me tired. I felt my life was so much simpler when I just had a job. But one day\u2026 one day\u2026 I saw the light: I discovered the double-entry method. I don\u2019t recall exactly how. I think it was on a website, yes\u2026 this site: dwmbeancounter.com (it\u2019s a wonderful site). I realized that using a single system , I could account for all of these problems at the same time, and in a way that would impose an inherent error-checking mechanism. I was blown away! I think I printed the whole thing on paper at the time and worked my way through every tutorial. This simple counting trick is exactly what I was in dire need for. But all the software I tried was either disappointing, broken, or too complicated. So I read up on Ledger. And I got in touch with its author . And then shortly after I started on Beancount 1 . I\u2019ll admit that going through the effort of designing my own system just to solve my accounting problems is a bit overkill, but I\u2019ve been known to be a little more than extreme about certain things , and I\u2019ve really enjoyed solving this problem. My life is fulfilled when I maintain a good balance of \u201clearning\u201d and \u201cdoing,\u201d and this falls squarely in the \u201cdoing\u201d domain. Ever since, I feel so excited about anything related to personal finance. Probably because it makes me so happy to have such a level of awareness about what\u2019s going on with mine. I even sometimes find myself loving spending money in a new way, just from knowing that I\u2019ll have to figure out how to account for it later. I feel so elated by the ability to solve these financial puzzles that I have had bouts of engaging complete weekends in building this software. They are small challenges with a truly practical application and a tangible consequence. Instant gratification. I get a feeling of empowerment . And I wish the same for you. For a full list of differences, refer to this document . \u21a9","title":"Command Line Accounting in Context"},{"location":"command_line_accounting_in_context.html#command-line-accounting-in-context","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/motivation This text provides context and motivation for the usage of double-entry command-line accounting systems to manage personal finances. Motivation What exactly is \u201cAccounting\u201d? What can it do for me? Keeping Books Generating Reports Custom Scripting Filing Documents How the Pieces Fit Together Why not just use a spreadsheet? Why not just use a commercial app? How about Mint.com? How about Quicken? How about Quickbooks? How about GnuCash? Why build a computer language? Advantages of Command-Line Bookkeeping Why not just use an SQL database? But... I just want to do X? Why am I so Excited?","title":"Command-line Accounting in Context"},{"location":"command_line_accounting_in_context.html#motivation","text":"When I tell people about my command-line accounting hacking activities, I get a variety of reactions. Sometimes I hear echoes of desperation about their own finances: many people wish they had a better handle and understanding of their finances, they sigh and wish they were better organized. Other times, after I describe my process I get incredulous reactions: \u201cWhy do you even bother?\u201d Those are the people who feel that their financial life is so simple that it can be summarized in 5 minutes. These are most often blissfully unaware how many accounts they have and have only an imprecise idea of where they stand. They have at least 20 accounts to their name; it\u2019s only when we go through the details that they realize that their financial ecosystem is more complex than they thought. Finally, there are those who get really excited about the idea of using a powerful accounting system but who don\u2019t actually understand what it is that I\u2019m talking about. They might think it is a system to support investment portfolios, or something that I use to curate a budget like a stickler. Usually they end up thinking it is too complicated to actually get started with. This document attempts to explain what command-line bookkeeping is about in concrete terms, what you will get out of doing it, how the various software pieces fit together, and what kinds of results you can expect from this method. The fact is, the double-entry method is a basic technique that everyone should have been taught in high school. And using it for yourself is a simple and powerful process you can use to drive your entire financial life.","title":"Motivation"},{"location":"command_line_accounting_in_context.html#what-exactly-is-accounting","text":"When one talks about \u201caccounting,\u201d they often implicitly refer to one or more of various distinct financial processes: Bookkeeping. Recording past transactions in a single place, a \u201cledger,\u201d also called \u201cthe books,\u201d as in, \u201cthe books of this company.\u201d Essentially, this means copying the amounts and category of financial transactions that occur in external account statements to a single system that includes all of the \u201caccounts\u201d relating to an entity and links them together. This is colloquially called \u201ckeeping books,\u201d or the activity of \u201cbookkeeping.\u201d Invoices. Preparing invoices and tracking payments. Contractors will often bring this up because it is a major concern of their activity: issuing requests to clients for payments for services rendered, and checking whether the corresponding payments have actually been received later on (and taking collection actions in case they haven\u2019t). If you\u2019re managing a company\u2019s finances, processing payroll is another aspect of this which is heavy in bookkeeping. Taxes. Finding or calculating taxable income, filling out tax forms and filing taxes to the various governmental authorities the entity is subject to. This process can be arduous, and for people who are beginning to see an increase in the complexity of their personal assets (many accounts of different types, new reporting requirements), it can be quite stressful. This is often the moment that they start thinking about organizing themselves. Expenses. Analyzing expenses, basically answering the question: \u201cWhere is my money going?\u201d A person with a small income and many credit cards may want to precisely track and calculate how much they\u2019re spending every month. This just develops awareness. Even in the presence of abundant resources, it is interesting to look at how much one is spending regularly, and where most of one\u2019s regular income is going, it often brings up surprises. Budgeting. Forecasting future expenses, allocating limited amounts to spend in various categories, and tracking how close one\u2019s actual spending is to those allocations. For individuals, this is usually in the context of trying to pay down debt or finding ways to save more. For companies, this occurs when planning for various projects. Investing. Summarizing and computing investment returns and capital gains. Many of us now manage our own savings via discount brokers, investing via ETFs and individually selected mutual funds. It is useful to be able to view asset distribution and risk exposure, as well as compute capital gains. Reporting. Public companies have regulatory requirements to provide transparency to their investors. As such, they usually report an annual income statement and a beginning and ending balance sheet . For an individual, those same reports are useful when applying for a personal loan or a mortgage at a bank, as they provide a window to someone\u2019s financial health situation. In this document, I will describe how command-line accounting can provide help and support for these activities.","title":"What exactly is \u201cAccounting\u201d?"},{"location":"command_line_accounting_in_context.html#what-can-it-do-for-me","text":"You might legitimately ask: sure, but why bother? We can look at the uses of accounting in terms of the questions it can answer for you: Where\u2019s my money, Lebowski? If you don\u2019t keep track of stuff, use cash a lot, have too many credit cards or if you are simply a disorganized and brilliant artist with his head in the clouds, you might wonder why there aren\u2019t as many beans left at the end of the month as you would like. An income statement will answer this question. I\u2019d like to be like Warren Buffet. How much do I have to save every month? I personally don\u2019t do a budget, but I know many people who do. If you set specific financial goals for a project or to save for later, the first thing you need to do is allocate a budget and set limits on your spending. Tracking your money is the first step towards doing that. You could even compute your returns in a way that can be compared against the market. I have some cash to invest. Given my portfolio, where should I invest it? Being able to report on the totality of your holdings, you can determine your asset class and currency exposures. This can help you decide where to place new savings in order to match a target portfolio allocation. How much am I worth? You have a 401k, an IRA and taxable accounts. A remaining student loan, or perhaps you\u2019re sitting on real-estate with two mortgages. Whatever. What\u2019s the total? It\u2019s really nice to obtain a single number that tells you how far you are from your retirement goals. Beancount can easily compute your net worth (to the cent). I still hurt from 2008. Are my investments safe? For example, I\u2019m managing my own money with various discount brokers, and many people are. I\u2019m basically running a miniature hedge fund using ETFs, where my goal is to be as diversified as possible between asset classes, sector exposure, currency exposure, etc. Plus, because of tax-deferred accounts I cannot do that in a single place. With Beancount you can generate a list of holdings and aggregate them by category. Taxes suck. Well, enough said. Why do they suck? Mostly because of uncertainty and doubt. It\u2019s not fun to not know what\u2019s going on. If you had all the data at your fingertips instantly it wouldn\u2019t be nearly as bad. What do you have to report? For some people there\u2019s a single stream of income, but for many others, it gets complicated (you might even be in that situation and you don\u2019t know it). Qualified vs. ordinary dividends, long-term vs. short-term capital gains, income from secondary sources, wash sales, etc. When it\u2019s time to do my taxes, I bring up the year\u2019s income statement, and I have a clear list of items to put in and deductions I can make. I want to buy a home. How much can I gather for a down payment? If you needed a lot of cash all of a sudden, how much could you afford? Well, if you can produce a list of your holdings, you can aggregate them \u201cby liquidity.\u201d This gives you an idea of available cash in case of an urgent need. Mr. Banker, please lend me some money. Impress the banker geek: just bring your balance sheet. Ideally the one generated fresh from that morning. You should have your complete balance sheet at any point in time. Instructions for your eventual passing. Making a will involves listing your assets. Make your children\u2019s lives easier should you pass by having a complete list of all the beans to be passed on or collected. The amounts are just part of the story: being able to list all the accounts and institutions will make it easier for someone to clear your assets. I can\u2019t remember if they paid me. If you send out invoices and have receivables, it\u2019s nice to have a method to track who has paid and who just says they\u2019ll pay. I\u2019m sure there is a lot more you can think of. Let me know.","title":"What can it do for me?"},{"location":"command_line_accounting_in_context.html#keeping-books","text":"Alright, so here\u2019s the part where I give you the insight of the method. The central and most basic activity that provides support for all the other ones is bookkeeping . A most important and fundamental realization is that this relatively simple act\u2014that of copying all of the financial transactions into a single integrated system\u2014allows you to produce reports that solve all of the other problems. You are building a corpus of data, a list of dated transaction objects that each represents movements of money between some of the accounts that you own. This data provides a full timeline of your financial activity. All you have to do is enter each transaction while respecting a simple constraint, that of the double-entry method , which is this: Each time an amount is posted to an account, it must have a corresponding inverse amount posted to some other account(s), and the sum of these amounts must be zero. That\u2019s it. This is the essence of the method, and an ensemble of transactions that respects this constraint acquires nice properties which are discussed in detail in some of my other documents. In order to generate sensible reports, all accounts are further labeled with one of four categories: Assets , Liabilities , Income and Expenses . A fifth category, Equity , exists only to summarize the history of all previous income and expense transactions. Command-line accounting systems are just simplistic computer languages for conveniently entering, reading and processing this transaction data, and ensure that the constraint of the double-entry method is respected. They\u2019re simple counting systems, that can add any kind of \u201cthing\u201d in dedicated counters (\u201caccounts\u201d). They\u2019re basically calculators with multiple buckets. Beancount and Ledger may differ slightly in their syntax and on some of their semantics, but the general principle is the same, and the simplifying assumptions are very similar. Here is a concrete example of what a transaction looks like, just so you can get a feeling for it. In a text file\u2014which is the input to a command-line accounting system\u2014something like the following is written and expresses a credit card transaction: 2014-05-23 * \"CAFE MOGADOR NEW YO\" \"Dinner with Caroline\" Liabilities:US:BofA:CreditCard -98.32 USD Expenses:Restaurant This is an example transaction with two \u201cpostings,\u201d or \u201clegs.\u201d The \u201c Expenses:Restaurant \u201d line is an account, not a category, though accounts often act like categories (there is no distinction between these two concepts). The amount on the expenses leg is left unspecified, and this is a convenience allowed by the input language. The software determines its amount automatically with the remainder of the balance which in this case will be 98.32 USD, so that -98.32 USD + 98.32 USD = 0 (remember that the sum of all postings must balance to zero\u2014this is enforced by the language). Most financial institutions provide some way for you to download a summary of account activity in some format or other. Much of the details of your transactions are automatically pulled in from such a downloaded file, using some custom script you write that converts it into the above syntax: A transaction date is always available from the downloadable files. The \u201c CAFE MOGADOR NEW YO \u201d bit is the \u201cmemo,\u201d also provided by the downloaded file, and those names are often good enough for you to figure out what the business you spent at was. Those memos are the same ugly names you would see appear on your credit card statements. This is attached to a transaction as a \u201cpayee\u201d attribute. I manually added \u201c Dinner with Caroline \u201d as a comment. I don\u2019t have to do this (it\u2019s optional), but I like to do it when I reconcile new transactions, it takes me only a minute and it helps me remember past events if I look for them. The importer brought in the Liabilities:US:BofA:CreditCard posting with its amount automatically, but I\u2019ve had to insert the Expenses:Restaurant account myself: I typed it in. I have shortcuts in my text editor that allow me to do that using account name completion, it takes a second and it\u2019s incredibly easy. Furthermore, pre-selecting this account could be automated as well, by running a simple learning algorithm on the previous history contained in the same input file (we tend to go to the same places all the time). The syntax gets a little bit more complicated, for example it allows you to represent stock purchases and sales, tracking the cost basis of your assets, and there are many other types of conveniences, like being able to define and count any kind of \u201cthing\u201d in an account (e.g., \u201cvacation hours accumulated\u201d), but this example captures the essence of what I do. I replicate all the transactions from all of the accounts that I own in this way, for the most part in an automated fashion. I spend about 1-2 hours every couple of weeks in order to update this input file, and only for the most used accounts (credit card and checking accounts). Other accounts I\u2019ll update every couple of months, or when I need to generate some reports. Because I have a solid understanding of my finances, this is not a burden anymore\u2026 it has become fun . What you obtain is a full history, a complete timeline of all the financial transactions from all the accounts over time, often connected together, your financial life, in a single text file .","title":"Keeping Books"},{"location":"command_line_accounting_in_context.html#generating-reports","text":"So what\u2019s the point of manicuring this file? I have code that reads it, parses it, and that can serve various views and aggregations of it on a local web server running on my machine, or produce custom reports from this stream of data. The most useful views are undeniably the balance sheet and income statement , but there are others: Balance Sheet. A balance sheet lists the final balance of all of your assets and liabilities accounts on a single page. This is a snapshot of all your accounts at a single point in time, a well-understood overview of your financial situation. This shows your net worth (very precisely, if you update all your accounts) and is also what a banker would be interested in if you were to apply for a loan. Beancount can produce my balance sheet at any point in time, but most often I\u2019m interested in the \u201ccurrent\u201d or \u201clatest\u201d balance sheet. Income Statement. An income statement is a summary of all income and expenses that occur between two points in time. It renders the final balance for these accounts in format familiar to any accountant: income accounts on the left, expense accounts on the right. This provides insights on the changes that occurred during a time period, a delta of changes. This tells you how much you\u2019re earning and where your money is going, in detail. The difference between the two is how much you saved. Beancount can render such a statement for any arbitrary period of time, e.g., this year, or month by month. Journals. For each account (or category, if you like to think of them that way), I can render a list of all the transactions that posted changes to that account. I call this a journal (Ledger calls this a \u201cregister\u201d). If you\u2019re looking at your credit card account\u2019s journal, for instance, it should match that of your bank statement. On the other hand, if you look at your \u201crestaurants expense\u201d account, it should show all of your restaurant outings, across all methods of payment (including cash, if you choose to enter that). You can easily fetch any detail of your financial history, like \u201cWhere was that place I had dinner with Arthur in March three years ago?\u201d or \u201cI want to sell that couch\u2026 how much did I pay for it again?\u201d Payables and Receivables. If you have amounts known to be received, for example, you filed your taxes or sent an invoice and are expecting a payment, or you mailed a check and you are expecting it to be cashed at some point in the future, you can track this with dedicated accounts. Transactions have syntax that allows you to link many of them together and figure out what has been paid or received. Calculating taxes. If you enter your salary deposits with the detail from your pay stub, you can calculate exactly how much taxes you\u2019ve paid, and the amounts should match exactly those that will be reported on your employer\u2019s annual income reporting form (e.g., W2 form in the USA, T4 if in Canada, Form P60 in the UK, or otherwise if you live elsewhere). This is particularly convenient if you have to make tax installments, for instance. If you\u2019re a contractor, you will have to track many such things, including company expensable expenses. It is also useful to count dividends that you have to report as taxable income. Investing. I\u2019m not quite rich, but I manage my own assets using discount brokers and mainly ETFs. In addition, I take advantage of a variety of tax sheltered accounts, with limited choices in assets. This is pretty common now. You could say that I\u2019m managing a miniature hedge fund and you wouldn\u2019t be too far from reality. Using Beancount, I can produce a detailed list of all holdings, with reports on daily market value changes, capital gains, dividends, asset type distribution (e.g. stocks vs. fixed income), currency exposure, and I can figure out how much of my assets are liquid, e.g., how much I have available towards a downpayment on a house. Precisely, and at any point in time. This is nice. I can also produce various aggregations of the holdings, i.e., value by account, by type of instrument, by currency. Forecasting. The company I work for has an employee stock plan, with a vesting schedule. I can forecast my approximate expected income including the vesting shares under reasonable assumptions. I can answer the question: \u201cAt this rate, at which date do I reach a net worth of X?\u201d Say, if X is the amount which you require for retirement. Sharing Expenses. If you share expenses with others, like when you go on a trip with friends, or have a roommate, the double-entry method provides a natural and easy solution to reconciling expenses together. You can also produce reports of the various expenses incurred for clarity. No uncertainty. Budgeting. I make enough money that I don\u2019t particularly set specific goals myself, but I plan to support features to do this. You basically end up managing your personal finances like a company would\u2026 but it\u2019s very easy because you\u2019re using a simple and cleverly designed computer language that makes a lot of simplifications (doing away with the concepts of \u201ccredit and debits\u201d for example), reducing the process to its essential minimum.","title":"Generating Reports"},{"location":"command_line_accounting_in_context.html#custom-scripting","text":"The applications are endless. I have all sorts of wild ideas for generating reports for custom projects. These are useful and fun experiments, \u201cchallenges\u201d as I call them. Some examples: I once owned a condo unit and I\u2019ve been doing double-entry bookkeeping throughout the period I owned it, through selling it. All of the corresponding accounts share the Loft4530 name in them. This means that I could potentially compute the precise internal rate of return on all of the cash flows related to it, including such petty things as replacement light bulbs expenses. To consider it as a pure investment. Just for fun. I can render a tree-map of my annual expenses and assets. This is a good visualization of these categories, that preserve their relative importance. I could look at average expenses with a correction for the time-value of money. This would be fun, tell me how my cost of living has changed over time. The beauty of it is that once you have the corpus of data, which is relatively easy to create if you maintain it incrementally, you can do all sorts of fun things by writing a little bit of Python code. I built Beancount to be able to do that in two ways: By providing a \u201cplugins\u201d system that allows you to filter a parsed set of transactions. This makes it possible for you to hack the syntax of Beancount and prototype new conveniences in data entry. Plugins provided by default provide extended features just by filtering and transforming the list of parsed transactions. And it\u2019s simple: all you have to do is implement a callback function with a particular signature and add a line to your input file. By writing scripts. You can parse and obtain the contents of a ledger with very little code. In Python, this looks no more complicated than this: import beancount.loader \u2026 entries, errors, options = beancount.loader.load_file('myfile.ledger') for entry in entries: \u2026 Voila. You\u2019re on your way to spitting out whatever output you want. You have access to all the libraries in the Python world, and my code is mostly functional, heavily documented and thoroughly unit-tested. You should be able to find your way easily. Moreover, if you\u2019re uncertain about using this system, you could just use it to begin entering your data and later write a script that converts it into something else.","title":"Custom Scripting"},{"location":"command_line_accounting_in_context.html#filing-documents","text":"If you have defined accounts for each of your real-world accounts, you have also created a natural method for organizing your statements and other paper documents. As we are communicating more and more by email with our accountants and institutions, it is becoming increasingly common to scan letters to PDFs and have those available as computer files. All the banks have now gone paperless, and you can download these statements if you care (I tend to do this once at the end of the year, for preservation, just in case). It\u2019s nice to be able to organize these nicely and retrieve those documents easily. What I do is keep a directory hierarchy mirroring the account names that I\u2019ve defined, something that looks like this: .../documents/ Assets/ US/ TDBank/ Checking/ 2014-04-08.statement.march.pdf 2014-05-07.statement.april.pdf \u2026 Liabilities/ US/ Amex/ Platinum/ 2014-04-17.March.pdf 2014-04-19.download.ofx \u2026 Expenses/ Health/ Medical/ 2014-04-02.anthem.eob-physiotherapy.pdf \u2026 These example files would correspond to accounts with names Assets:US:TDBank:Checking , Liabilities:US:Amex:Platinum , and Expenses:Health:Medical . I keep this directory under version control. As long as a file name begins with a date, such as \u201c 2014-04-02 \u201d, Beancount is able to find the files automatically and insert directives that are rendered in its web interface as part of an account\u2019s journal, which you can click on to view the document itself. This allows me to find all my statements in one place, and if I\u2019m searching for a document, it has a well-defined place where I know to find it. Moreover, the importing software I wrote is able to identify downloaded files and automatically move them into the corresponding account\u2019s directory.","title":"Filing Documents"},{"location":"command_line_accounting_in_context.html#how-the-pieces-fit-together","text":"So I\u2019ve described what I do to organize my finances. Here I\u2019ll tell you about the various software pieces and how they fit together: Beancount is the core of the system. It reads the text file I update, parses the computer language I\u2019ve defined and produces reports from the resulting data structures. This software only reads the input file and does not communicate with other programs on purpose. It runs in isolation. Beancount\u2019s ingest package and tools help automate the updating of account data by extracting transactions from file downloads from banks and other institutions. These tools orchestrate the running of importers which you implement (this is where all the messy importing code lives, the code you need to make it easier to keep your text file up-to-date, which can parse OFX and CSV files, for instance). See bean-extract, bean-identify tools. The ingest package also helps with filing documents (see bean-file tool). Because it is able to identify which document belongs to which account, it can move the downloaded file to my documents archive automatically. This saves me time. Finally, in order to provide market values, a Beancount input file should have suitable price directives. Beancount also contains code to fetch latest or historical prices for the various commodities present in one\u2019s ledger file (see the bean-price tool). Like the extraction of transactions from OFX downloads, it also spits out Beancount input syntax used to define prices. See the diagram below for a pretty picture that illustrates how these work together.","title":"How the Pieces Fit Together"},{"location":"command_line_accounting_in_context.html#why-not-just-use-a-spreadsheet","text":"This is indeed a good question, and spreadsheets are incredibly useful for sure. I certainly would not be writing my own software if I could track my finances with a spreadsheet. The problem is that the intrinsic structure of the double-entry transactional data does not lend itself to a tabular representation. Each transaction has multiple attributes (date, narration, tags), and two or more legs, each of which has an associated amount and possibly a cost. If you put the dates on one axis and the accounts on the other, you would end up with a very sparse and very large table; that would not be very useful, and it would be incredibly difficult to edit. If on the other hand, you had a column dedicated to account names for each row, all of your computations would have to take the account cell into the calculation logic. It would be very difficult to deal with, if not impossible. All matters of data aggregations are performed relative to account names. Moreover, dealing with the accumulation of units with a cost basis would require difficult gymnastics. I don\u2019t even know how I would proceed forward to do this in a spreadsheet. A core part of command-line accounting systems is the inventory logic that allows you to track a cost basis for every unit held in an account and book reductions of positions against existing lots only. This allows the system to compute capital gains automatically. And this is related to the codes that enforce the constraint that all postings on a transaction balance out to zero. I believe that the \u201ctransaction <-> postings\u201d data representation combined with a sensible method for updating inventories is the essence of this system. The need for these is the justification to create a dedicated method to build this data, such as a computer language. Finally, having our own syntax offers the opportunity to provide other types of useful directives such as balance assertions, open/close dates, and sophisticated filtering of subsets of transactions. You just cannot do what we\u2019re doing in a spreadsheet.","title":"Why not just use a spreadsheet?"},{"location":"command_line_accounting_in_context.html#why-not-just-use-a-commercial-app","text":"","title":"Why not just use a commercial app?"},{"location":"command_line_accounting_in_context.html#how-about-mintcom","text":"Oftentimes people tell me they\u2019re using Mint.com to track their finances, usually with some amount of specific complaints following close behind. Online services such as Mint provide a subset of the functionality of double-entry accounting. The main focus of these services is the reporting of expenses by category, and the maintenance of a budget. As such, they do a great job at automating the download of your credit card and bank transactions. I think that if you want a low-maintenance option to tracking your finances and you don\u2019t have a problem with the obvious privacy risks, this is a fantastic option: it comes with a web interface, mobile apps, and updating your accounts probably requires clicking some buttons, providing some passwords, and a small amount of manual corrections every couple of weeks. I have several reservations about using it for myself, however: Passwords. I\u2019m just not comfortable enough with any commercial company to share the passwords to all my bank, credit card, and investment accounts. This sounds like an insane idea to me, a very scary one, and I\u2019m just not willing to put that much trust in any one company\u2019s hands. I don\u2019t care what they say: I worked for several software companies and I\u2019m aware of the disconnect between promises made by salespeople, website PR, and engineering reality. I also know the power of determined computer hackers. Insufficient reporting. Reporting is probably beautiful and colorful\u2014it\u2019s gorgeous on the website\u2014but certainly insufficient for all the reporting I want to do on my data. Reporting that doesn\u2019t do what they want it to is a common complaint I hear about these systems. With my system, I can always write a script and produce any kind of report I might possibly want in the future. For instance, spitting out a treemap visualization of my expenses instead of a useless pie chart. I can slice and dice my transactions in all kinds of unique ways. I can tag subsets of transactions for projects or trips and report on them. It\u2019s more powerful than generic reporting. Perennity. What if the company is not in business in 5 years? Where will my data be? If I spend any time at all curating my financial data, I want to ensure that it will be available to me forever, in an open format. In their favor, some of these sites probably have a downloadable option, but who uses it? Does it even work well? Would it include all of the transactions that they downloaded? I don\u2019t know. Not international. It probably does not work well with an international, multi-currency situation. These services target a \u201cmajority\u201d of users, most of which have all their affairs in a single country. I live in the USA, have a past history in Canada, which involves remaining tax-sheltered investment accounts, occasional expenses during visits which justify maintaining a credit card there, and a future history which might involve some years spent in another country such as Hong Kong or Australia. Will this work in Mint? Probably not. They might support Canada, but will they support accounts in both places? How about some other country I might want to move to? I want a single integrated view of all my accounts across all countries in all currencies forever, nothing less. My system supports that very well. (I\u2019m not aware of any double-entry system that deals with the international problem in a currency agnostic way as well as Beancount does.) Inability to deal with particular situations. What if I own real estate? Will I be able to price the value of my home at a reasonable amount, so it creates an accurate balance sheet? Regularly obtaining \u201ccomparables\u201d for my property from an eager real estate agent will tell me much more precisely how much my home is worth than services like Zillow ever could. I need to be able to input that for my balance sheet. What about those stock options from that privately held Australian company I used to work for? How do I price that? How about other intangible things, such as receivables from a personal loan I made to a friend? I doubt online services are able to provide you with the ability to enter those. If you want the whole picture with precision, you need to be able to make these adjustments. Custom tracking. Using \u201cimaginary currencies\u201d, I\u2019m able to track all kinds of other things than currencies and stocks. For example, by using an \u201c IRAUSD \u201d commodity in Beancount I\u2019m able to track how many 401k contributions I\u2019ve made at any point during the year. I can count the after-tax basis of a traditional IRA account similarly. I can even count my vacation hours using an imaginary \u201c VACHR \u201d currency and verify it against my pay stubs. Capital gains reporting. I haven\u2019t tried it myself, but I\u2019ve heard some discontent about Mint from others about its limited capabilities for capital gains reporting. Will it maintain trade lots? Will it handle average cost booking? How about PnL on FOREX gains? What about revenue received for that book I\u2019m selling via PayPal? I want to be able to use my accounting system as an input for my tax reporting. Cash transactions. How difficult is it to enter cash transactions? Do I have to log in, or start a slow, heavy program that will only work under Windows? With Beancount I bring up a file in a text editor, this is instant and easy. Is it even possible to enter custom cash entries in online services? In other words, I\u2019m a very sophisticated user and yes, a bit of a control freak . I\u2019m not lying about it. I don\u2019t have any ideological objections about using a commercial service, it is probably not for me. If it\u2019s good enough for you, suit yourself. Despite their beautiful and promising websites, I haven\u2019t heard of anyone being completely satisfied with these services. I hear a fair amount of complaining, some mild satisfaction, but I have yet to meet anyone who raves about it.","title":"How about Mint.com?"},{"location":"command_line_accounting_in_context.html#how-about-quicken","text":"Quicken is a single-entry system, that is, it replicates the transactions of a remote account locally, and allows you to add a label to each transaction to place it in a category. I believe it also has support for synchronizing investment accounts. This is not enough for me, I want to track all kinds of things, and I want to use the double-entry method, which provides an intrinsic check that I\u2019ve entered my data correctly. Single-entry accounting is just not good enough if you\u2019ve already crossed the bridge of understanding the double-entry method.","title":"How about Quicken?"},{"location":"command_line_accounting_in_context.html#how-about-quickbooks","text":"So let\u2019s talk about sophisticated software that is good enough for a company. Why wouldn\u2019t I use that instead? If it\u2019s good enough for small businesses, it should be good enough for me, no? There\u2019s Quickbooks and other ones. Why don\u2019t I use them: It costs money. Commercial software comes at a price. Ok, I probably could afford to pay a few hundred dollars per year (Quickbooks 2014 looks like around 300$/year for the full set of features), but I don\u2019t really want to. Platform. These softwares usually run on Microsoft Windows and sometimes on Mac OS X . I\u2019m a software developer, I mostly use Linux , and a Macbook Air for a laptop, on which I get annoyed running anything other than tmux and a web browser . I\u2019m not going to reboot just to enter a quick cash transaction. Slow startup. I cannot speak specifically to Quickbooks\u2019 implementation, but virtually every software suite of commercial scope I\u2019ve had to use had a splash screen and a slow, slow startup that involved initializing tons of plugins. They assume you\u2019re going to spend hours in it, which is reasonable for commercial users, but not for me, if I want to do a quick update of my ledger. UIs are inadequate. I haven\u2019t seen their UI but given the nature of transactions and my desire to input precisely and search quickly and organize things, I want to be able to edit in as text. I imagine it would be inconvenient for me. With Emacs and org-mode , I can easily i-search my way to any transaction within seconds after opening my ledger file. Inflexible. How would I go about re-organizing all my account names? I think I\u2019m still learning about the double-entry bookkeeping method and I have made mistakes in the past, mistakes where I desire to revisit the way I organize my accounts in a hierarchy. With my text file, I was able to safely rename a large number of accounts several times, and evolve my chart-of-accounts to reflect my improving understanding of how to organize my financial tracking system. Text is powerful!","title":"How about Quickbooks?"},{"location":"command_line_accounting_in_context.html#how-about-gnucash","text":"I don\u2019t like UIs; they\u2019re inconvenient. There\u2019s nothing quite like editing a text file if you are a programmer. Moreover, GnuCash does not deal with multiple currencies well. I find bugs in it within the first hour every time I kick the tires on it, which I do every couple of years, out of curiosity. Other programs, such as Skrooge, also take the heavy-handed big UI approach.","title":"How about GnuCash?"},{"location":"command_line_accounting_in_context.html#why-build-a-computer-language","text":"A bookkeeping system provides conditions for a solution that involves a simple computer language for many reasons. Single-entry bookkeeping is largely insufficient if you're trying to track everything holistically. Existing systems either limit themselves to expense categories with little checking beyond \"reconciling\" which sometimes involves freezing the past. If you're not doing the bookkeeping for a company, sometimes just changing the past and fixing the mistakes where they occurred makes more sense. More importantly, the single-entry method leaves us wanting for the natural error-checking mechanism involved in the double-entry system. The problem is also not solvable elegantly by using spreadsheets; the simple data structure that forms the basis of the double-entry system infers either a very sparse spreadsheet with accounts on one dimension and transactions on the other. For real-world usage, this is impractical. Another iteration on this theme would involve inserting the postings with two columns, one with the account and one with the amount, but the varying number of columns and the lookup code makes this inelegant as well. Plus, it's not obvious how you would deal with a large number of currencies. Programs that provide fancy graphical or web-based user interfaces are inevitably awkward, due to the nature of the problem: each transaction is organized by viewing it through the lens of one account's journal, but any of the accounts present in its postings provide equally valid views. Ideally, what you want, is just to look at the transaction. Organizing them for most convenient input has little to do with the order in which they are to be presented. Using text has a lot of advantages: You can easily used search-and-replace and/or sed to make global changes, for example, rename or reorganize your accounts; You can organize the transactions in the order that is most convenient for data entry; There are a number of existing tools to search the text; You can easily write various little tools to spit out the data syntax, i.e., for importing from other file types, or converting from other systems. Text is inherently open , that is the file format is one that you can read your data from and store anywhere else, not a blob of incomprehensible data that becomes unusable when the company that makes the software stops supporting it. Finally, systems that attempt to automate the process of importing all your data from automated sources (e.g., mint.com) have one major shortfall: most often it's not very easy or even possible to add information for accounts that aren't automated. It is my experience that in practice you will have some entries and accounts to track that will not have a nice downloadable file format, or that simply don't have a real-world counterpart. In order to produce a complete view of one's balance sheet, it is important to be able to enter all of an individual's account within a single system. In other words, custom accounts and manually entered transactions do matter a lot. For all the reasons mentioned above, I feel that a computer language is more appropriate to express this data structure than a heavy piece of software with a customized interface. Being able to easily bring up a text file and quickly type in a few lines of text to add a transaction is great\u2013it's fast and easy. The ledger file provides a naturally open way to express one's data, and can be source-controlled or shared between people as well. Multiple files can be merged together, and scripted manipulations on a source file can be used to reorganize one's data history. Furthermore, a read-only web interface that presents the various reports one expects and allows the user to explore different views on the dataset is sufficient for the purpose of viewing the data.","title":"Why build a computer language?"},{"location":"command_line_accounting_in_context.html#advantages-of-command-line-bookkeeping","text":"In summary, there are many advantages to using a command-line accounting system over a commercial package that provides a user-interface: Fast. You don\u2019t have to fire up a slow program with a splash screen that will take a while to initialize in order to add something to your books. Bringing up a text file from a bookmark in your favorite editing program (I use Emacs) is easy and quick. And if you\u2019re normally using a text editor all day long, as many programmers do, you won\u2019t even blink before the file is in front of your eyes. It\u2019s quick and easy. Portable. It will work on all platforms. Beancount is written in Python 3 with some C extensions, and as such will work on Mac, Linux and Windows platforms. I am very careful and determined to keep external dependencies on third-party packages to an absolute minimum in order to avoid installation problems. It should be easy to install and update, and work everywhere the same. Openness. Your data is open, and will remain open forever . I plan on having my corpus of data until the day I die. With an open format you will never end up in a situation where your transactional data is sitting in a binary blob with an unknown format and the software goes unsupported. Furthermore, your data can be converted to other languages easily. You can easily invoke the parser I provide and write a script that spits it out in another program\u2019s input syntax. You can place it under version control. You can entirely reorganize the structure of your accounts by renaming strings with sed. Text is empowering. Customized. You can produce very customized reports, that address exactly the kinds of problems you are having. One complaint I often hear from people about other financial software is that it doesn\u2019t quite do what they want. With command-line accounting systems you can at least write it yourself, as a small extension that uses your corpus of data.","title":"Advantages of Command-Line Bookkeeping"},{"location":"command_line_accounting_in_context.html#why-not-just-use-an-sql-database","text":"I don\u2019t like to reinvent the wheel. If this problem could be solved by filling up an SQL database and then making queries on it, that\u2019s exactly what I would do. Creating a language is a large overhead, it needs to be maintained and evolved, it\u2019s not an easy task, but as it turns out, necessary and justified to solve this problem. The problem is due to a few reasons: Filtering occurs on a two-level data structure of transactions vs. their children postings, and it is inconvenient to represent the data in a single table upon which we could then make manipulations. You cannot simply work only with postings: in order to ensure that the reports balance, you need to be selecting complete transactions. When you select transactions, the semantics is \u201cselect all the postings for which the transaction has this or that property.\u201d One such property is \u201call transactions that have some posting with account X .\u201d These semantics are not obvious to implement with a database. The nature of the data structure makes it inconvenient. The wide variety of directives makes it difficult to design a single elegant table that can accommodate all of their data. Ideally we would want to define two tables for each type of directive: a table that holds all common data (date, source filename & lineno, directive type) and a table to hold the type-specific data. While this is possible, it steps away from the neat structure of a single table of data. Operations on inventories\u2014the data structure that holds the incrementally changing contents of accounts\u2014require special treatment that would be difficult to implement in a database. Lot reductions are constrained against a running inventory of lots, each of which has a specific cost basis. Some lot reductions trigger the merging of lots (for average cost booking). This requires some custom operations on these inventory objects. Aggregations (breakdowns) are hierarchical in nature. The tree-like structure of accounts allows us to perform operations on subtrees of postings. This would also not be easy to implement with tables. Finally, input would be difficult. By defining a language, we side-step the problem of having to build a custom user interface that would allow us to create the data. Nevertheless, I really like the idea of working with databases. A script (bean-sql) is provided to convert the contents of a Beancount file to an SQL database; it\u2019s not super elegant to carry out computations on those tables, but it\u2019s provided nonetheless. You should be able to play around with it, even if operations are difficult. I might even pre-compute some of the operation\u2019s outputs to make it easier to run SQL queries.","title":"Why not just use an SQL database?"},{"location":"command_line_accounting_in_context.html#but-i-just-want-to-do-x","text":"Some may infer that what we\u2019re doing must be terribly complicated given that they envision they might want to use such a system only for a single purpose. But the fact is, how many accounts you decide to track is a personal choice. You can choose to track as little as you want in as little detail as you deem sufficient. For example, if all that you\u2019re interested in is investing, then you can keep books on only your investment accounts. If you\u2019re interested in replacing your usage of Mint.com or Quicken , you can simply just replicate the statements for your credit cards and see your corresponding expenses. The simplicity of having to just replicate the transactions in all your accounts over doing a painful annual \u201cstarting from scratch\u201d evaluation of all your assets and expenses for the purpose of looking at your finances will convince you. Looking at your finances with a spreadsheet will require you to at least copy values from your portfolio. Every time you want to generate the report you\u2019ll have to update the values\u2026 with my method, you just update each account\u2019s full activity, and you can obtain the complete list holdings as a by-product. It\u2019s easy to bring everything up-to-date if you have a systematic method. To those I say: try it out, accounting just for the bits that you\u2019re interested in. Once you\u2019ll have a taste of how the double-entry method works and have learned a little bit about the language syntax, you will want to get a fuller picture. You will get sucked in.","title":"But... I just want to do X?"},{"location":"command_line_accounting_in_context.html#why-am-i-so-excited","text":"Okay, so I\u2019ve become an accounting nerd. I did not ask for it. Just kind-of happened while I wasn\u2019t looking (I still don\u2019t wear brown socks though). Why is it I can\u2019t stop talking about this stuff? I used to have a company. It was an umbrella for doing contract work (yes, I originally had bigger aspirations for it, but it ended up being just that. Bleh.) As a company owner in Canada, I had to periodically make five different kinds of tax installments to the Federal and Provincial governments, some every month, some every three months, and then it varied. An accountant was feeding me the \u201cmagic numbers\u201d to put in the forms, numbers which I was copying like a monkey, without fully understanding how they were going to get used later on. I had to count separately the various expenses I would incur in relation to my work (for deductions), and those which were only personal \u2014I did not have the enlightened view of getting separate credit cards so I was trying to track personal vs. company expenses from the same accounts. I also had to track transfers between company and personal accounts that would later on get reported as dividends or salary income. I often had multiple pending invoices that would take more than two months to get paid, from different clients. It was a demi-hell. You get the idea. I used to try to track all these things manually, using systems composed of little text files and spreadsheets and doing things very carefully and in a particular way. The thing is, although I am by nature quite methodical, I would sometimes, just sometimes forget to update one of the files. Things would fall out of sync. When this happened it was incredibly frustrating, I had to spend time digging around various statements and figure out where I had gone wrong. This was time-consuming and unpleasant. And of course, when something you have to do is unpleasant, you tend not to do it so well. I did not have a solid idea of the current state of affairs of my company during the year, I just worked and earned money for it. My accountant would draw a balance sheet once a year after doing taxes in July, and it was always a tiny bit of a surprise. I felt permanently somewhat confused, and frankly annoyed every time I had to deal with \u201cmoney things.\u201d It wasn\u2019t quite a nightmare, but it was definitely making me tired. I felt my life was so much simpler when I just had a job. But one day\u2026 one day\u2026 I saw the light: I discovered the double-entry method. I don\u2019t recall exactly how. I think it was on a website, yes\u2026 this site: dwmbeancounter.com (it\u2019s a wonderful site). I realized that using a single system , I could account for all of these problems at the same time, and in a way that would impose an inherent error-checking mechanism. I was blown away! I think I printed the whole thing on paper at the time and worked my way through every tutorial. This simple counting trick is exactly what I was in dire need for. But all the software I tried was either disappointing, broken, or too complicated. So I read up on Ledger. And I got in touch with its author . And then shortly after I started on Beancount 1 . I\u2019ll admit that going through the effort of designing my own system just to solve my accounting problems is a bit overkill, but I\u2019ve been known to be a little more than extreme about certain things , and I\u2019ve really enjoyed solving this problem. My life is fulfilled when I maintain a good balance of \u201clearning\u201d and \u201cdoing,\u201d and this falls squarely in the \u201cdoing\u201d domain. Ever since, I feel so excited about anything related to personal finance. Probably because it makes me so happy to have such a level of awareness about what\u2019s going on with mine. I even sometimes find myself loving spending money in a new way, just from knowing that I\u2019ll have to figure out how to account for it later. I feel so elated by the ability to solve these financial puzzles that I have had bouts of engaging complete weekends in building this software. They are small challenges with a truly practical application and a tangible consequence. Instant gratification. I get a feeling of empowerment . And I wish the same for you. For a full list of differences, refer to this document . \u21a9","title":"Why am I so Excited?"},{"location":"exporting_your_portfolio.html","text":"Exporting Your Portfolio \uf0c1 Martin Blais , December 2015 (v2) http://furius.ca/beancount/doc/export Overview \uf0c1 This document explains how to export your portfolio of holdings from Beancount to a Google Finance portfolio (and eventually to other portfolio tracking websites). Note: This is the second version of this document, rewritten in Dec 2015, after greatly simplifying the process of exporting portfolios and completely separating the specification of stock tickers for price downloads. This new, simplified version only uses a single metadata field name: \u201cexport\u201d. The previous document can be found here . Portfolio Tracking Tools \uf0c1 There are multiple websites on the internet that allow someone to create a portfolio of investments (or upload a list of transactions to create such a portfolio) and that reports on the changes in the portfolio due to price movements, shows you unrealized capital gains, etc. One such website is the Google Finance portal. Another example is the Yahoo Finance one. These are convenient because they allow you to monitor the impact of price changes on your entire portfolio of assets, across all accounts, during the day or otherwise. However, each of these sites expects their users to use their interfaces and workflows to painfully enter each of the positions one-by-one. A great advantage of using Beancount is that you should never have to enter this type of information manually; instead, you should be able to extract it and upload it to one of these sites. You can be independent of the particular portfolio tracking service you use and should be able to switch between them without losing any data; Beancount can serve as your pristine source for your list of holdings as your needs evolve. Google Finance supports an \u201cimport\u201d feature to create portfolio data which supports the Microsoft OFX financial interchange data format. In this document, we show how we built a Beancount report that exports the portfolio of holdings to OFX for creating a Google Finance portfolio. Exporting to Google Finance \uf0c1 Exporting your Holdings to OFX \uf0c1 First, create an OFX file corresponding to your Beancount holdings. You can use this command to do this: bean-report file.beancount export_portfolio > portfolio.ofx See the report\u2019s own help for options: bean-report file.beancount export_portfolio --help Importing the OFX File in Google Finance \uf0c1 Then we have to import that OFX file in a web-based portfolio. Visit http://finance.google.com and click on \u201cPortfolios\u201d on the left (or simply visit https://www.google.com/finance/portfolio , this works as of Jan 2015) If you have an existing, previously imported portfolio, click on \u201cDelete Portfolio\u201d to get rid of it. Click on \u201cImport Transactions\u201d, then \u201cChoose File\u201d and select the portfolio.ofx file you exported to, then click on \u201cPreview Import\u201d. You should see a list of imported lots, with familiar stock symbols and names, and Type \u201cBuy\u201d with realistic Shares and Price columns. If not, see the note below. Otherwise, scroll to the bottom of the page and click \u201cImport\u201d. Your portfolio should now appear. You are done. You should never bother updating this portfolio directly using the website\u2026 instead, update your Beancount ledger file, re-export to a new OFX file, delete the previous portfolio and re-import a brand new one over it. Your pristine source is always your Beancount file, ideally you should never have to be worried about corrupting or deleting the portfolio data in any external website. Controlling Exported Commodities \uf0c1 Declaring Your Commodities \uf0c1 Generally, we recommend that you explicitly declare each of the commodities used in your input file. It is a neat place to attach information about those commodities, metadata that you should be able to use later on from bean-query or in scripts that you make. For example, you could declare a human-readable description of the commodity, and some other attributes, like this: 2001-09-06 commodity XIN name: \"iShares MSCI EAFE Index ETF (CAD-Hedged)\" class: \"Stock\" type: \"ETF\" ... Beancount will work with or without these declarations (it automatically generates Commodity directives if you haven\u2019t provided them). If you like to be strict and have a bit of discipline, you can require that each commodity be declared by using a plugin that will issue an error when an undeclared commodity appears: plugin \"beancount.plugins.check_commodity\" You can use any date for that Commodity directive. I recommend using the date of the commodity\u2019s inception, or perhaps when it was first introduced by the issuing country, if it is a currency. You can find a suitable date on Wikipedia or on the issuer\u2019s websites. Google Finance may have the date itself. What Happens by Default \uf0c1 By default, all holdings are exported as positions with a ticker symbol named the same as the Beancount commodity that you used to define them. If you have a holding of \u201cAAPL\u201d units, it will create an export entry for \u201cAAPL\u201d. The export code attempts to export all holdings by default. However, in any but the simplest unambiguous cases, this is probably not good enough to produce a working Google Finance portfolio. The name for each commodity that you use in your Beancount input file may or may not correspond to a financial instrument in the Google Finance database; due to the very large number of symbols supported in its database, just specifying the ticker symbol is often ambiguous. Google Finance attempts to resolve an ambiguous symbol string to the most likely instrument in its database. It is possible that it resolves it to a different financial instrument from the one you intended. So even if you use the same basic symbol that is used by the exchange, you often still need to disambiguate the symbol by specifying which exchange or symbology it lives in. Google provides a list of these symbol spaces . Here is a real-life example. The symbol for the \u201c CAD-Hedged MSCI EAFE Index \u201d ETF product issued by iShares/Blackrock is \u201c XIN \u201d on the Toronto Stock Exchange ( TSE ). If you just looked up \u201cXIN\u201d on Google Finance , it would choose to resolve it by default to the more likely \u201c NYSE:XIN \u201d symbol ( Xinyuan Real Estate Co. on the New York Stock Exchange ). So you need to disambiguate it by specifying that the desired ETF ticker for this instrument is \u201c TSE:XIN \u201d. Explicitly Specifying Exported Symbols \uf0c1 You can specify which exchange-specific symbol is used to export a commodity by attaching an \u201c export \u201d metadata field to each of your Commodity directives, like this: 2001-09-06 commodity XIN ... export: \"TSE:XIN\" The \u201c export \u201d field is used to map your commodity name to the corresponding instrument in the Google Finance system. If a holding in that commodity needs to be exported, this code is used instead of the Beancount currency name. The symbology used by Google Finance appears to follow the following syntax: Exchange:Symbol \uf0c1 where Exchange is a code either for the exchange where the stock trades, or for another source of financial data, e.g. \u201c MUTF \u201d for \u201cmutual funds in the US\u201d, and more . Symbol is a name that is unique within that exchange. I recommend searching for each of your financial instruments in Google Finance, confirming that the instrument corresponds to your instrument (by inspecting the full name, description and price), and inserting the corresponding code like this. Exporting to a Cash Equivalent \uf0c1 To account for positions that aren\u2019t supported in Google Finance, the export report can convert a holding to its cash-equivalent value. This is also useful for cash positions (e.g., cash sitting idle in a savings or checking account). For example, I hold units of an insurance policy investment vehicle (this is common in Canada, for example, with London Life). This is a financial instrument, but each particular policy issuance has its own associated value\u2014there is no public source of data for each of those products, it\u2019s rather opaque, I can obtain its value with my annual statement, but definitely not in Google Finance. But I\u2019d still like for the asset\u2019s value to be reflected in my portfolio. The way you tell the export code to make this conversion is to specify a special value of \u201cCASH\u201d for the \u201cexport\u201d field, like this: 1878-01-01 commodity LDNLIFE export: \"CASH\" This would convert holdings in LDNLIFE commodities to their corresponding quoted value before exporting, using the price nearest to the date of exporting. Note that attempting to convert to cash a commodity that does not have a corresponding cost or price available for us to determine its value will generate an error. A price must be present to make the conversion. Simple currencies should also be marked as cash in order to be exported: 1999-01-01 commodity EUR name: \"European Union Euro currency\" export: \"CASH\" Finally, all converted holdings are agglomerated into a single cash position. There is no point in exporting these cash entries to separate OFX entries because the Google Finance code will agglomerate them to a single one anyhow. Declaring Money Instruments \uf0c1 There is a small hiccup in this cash conversion story: the Google Finance importer does not appear to correctly grok an OFX position in \u201ccash\u201d amounts in the importer; I think this is probably just a bug in Google Finance\u2019s import code (or perhaps I haven\u2019t found the correct OFX field values to make this work). Instead, in order to insert a cash position the exporter uses a cash-equivalent commodity which always prices at 1 unit of the currency, e.g. $1.00 for US dollars. For example, for US dollars I I use VMMXX which is a Vanguard Prime Money Market Fund, and for Canadian dollars I use IGI806 . A good type of commodity for this is some sort of Money Market fund. It doesn\u2019t matter so much which one you use, as long as it prices very close to 1. Find one. If you want to include cash commodities, you need to find such a commodity for each of the cash currencies you have on your books and tell Beancount about them. Typically that will be only one or two currencies. You declare them by append the special value \u201c MONEY \u201d for the \u201c export \u201d field, specifying which currency this commodity represents, like this: 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" 1900-01-01 commodity IGI806 export: \"MUTF_CA:IGI806 (MONEY:CAD)\" Ignoring Commodities \uf0c1 Finally, some commodities held in a ledger should be ignored. This is the case for the imaginary commodities used in mirror accounting, for example, to track unvested shares of an employment stock plan, or commodities used to track amounts contributed to a retirement account, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" You tell the export code to ignore a commodity specifying the special value \u201c IGNORE \u201d for the \u201c export \u201d field, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" export: \"IGNORE\" All holdings in units of RSPCAD will thus not be exported. The question of whether some commodities should be exported or not sometimes presents interesting choices. Here is an example: I track my accumulated vacation hours in an asset account. The units are \u201c VACHR \u201d. I associate with this commodity a price that is roughly equivalent to my net hourly salary. This gives me a rough idea how much vacation time money is accumulated on the books, e.g. if I quit my job, how much I\u2019d get paid. Do I want to them included in my total net worth? Should the value from those hours be reflected in the value of my exported portfolio? I think that largely depends on whether I plan to use up those vacations before I leave this job or not, whether I want to have this accumulated value show up on my balance sheet. Comparing with Net Worth \uf0c1 The end result is that the sum total of all your exported positions plus the cash position should approximate the value of all your assets, and the total value calculated by the Google Finance website should be very close to the one reported by this report: bean-report file.beancount networth As a point of comparison, the value of my own portfolio is usually close to within a few hundred US dollars. Details of the OFX Export \uf0c1 Import Failures \uf0c1 Exporting a portfolio with symbols that Google Finance does not recognize fatally trips up Google\u2019s import feature. Google Finance then proceeds to fail to recognize your entire file. I recommend that you use explicit exchange:symbol names on all commodities that get exported in order to avoid this problem, as is described further in this document. Google Finance can also be a little bit finicky about the format of the particular OFX file you give it to import. The export_portfolio command attempts to avoid OFX features that would break it but it\u2019s fragile, and it\u2019s possible that the particulars of your portfolio\u2019s contents triggers output that fails to import. If this is the case, at step (4) above, instead of a list of stock symbols you would see a long list of positions that look like XML tags (this is how failure manifests itself). If that is the case, send email to the mailing-list (best if you can isolate the positions that trigger breakage and have the capability to diff files and do some troubleshooting). Mutual Funds vs. Stocks \uf0c1 The OFX format distinguishes between stocks and mutual funds. In practice, the Google Finance importer does not appear to distinguish between these two (at least it appears to behave the same way), so this is likely an irrelevant implementation detail. Nevertheless, the export code is able to honor the OFX convention of distinguishing between \u201cBUYMF\u201d vs. \u201cBUYSTOCK\u201d XML elements. To this effect, the export code attempts to classify which commodities represent mutual funds by inspecting whether the ticker associated with the commodity begins with the letters \u201cMUTF\u201d and is followed by a colon. For example, \u201c MUTF:RGAGX \u201d and \u201c MUTF_CA:RBF1005 \" will both be detected as mutual funds, for example. Debugging the Export \uf0c1 In order to debug how each of your holdings gets exported, use the --debug flag, which will print a detailed account of how each holding is handled by the export script to stderr : bean-report file.beancount export_portfolio --debug 2>&1 >/dev/null | more The script should print the list of exported positions and their corresponding holdings, then the list of converted positions and their corresponding holdings (usually many cash positions are aggregated together) and finally, the list of ignored holdings. This should be enough to explain the entire contents of the exported portfolio. Purchase Dates \uf0c1 Beancount does not currently have all the lot purchase dates, so the purchase dates are exported as if purchased the day before the export. Eventually, when the purchase date is available in Beancount (pending the inventory booking changes ) the actual lot purchase date will probably be used in the export format. However, it\u2019s not yet clear that using the correct date is the right thing to do, because Google Finance might insist on inserting cash for dividends since the reported purchase date\u2026 but Beancount already takes care of inserting a special lot for cash that should already include this. We shall see when we get there. Disable Dividends \uf0c1 Under the \u201cEdit Portfolio\u201d option there is a checkbox that appears to disable the calculation of dividends offered. It would be nice to find a way to automatically disable this checkbox upon import. Automate Upload \uf0c1 It would be nice to automate the replacement of the portfolio with a Python script. Unfortunately, the Google Finance API has been deprecated. Maybe someone can write a screen-scraping routine to do this. Summary \uf0c1 Each holding\u2019s export can be controlled by how its commodity is treated, in one of the following ways: Exported to a portfolio position. This is the default, but you should specify the ticker symbol using the \u201c ticker \u201d or \u201c export \u201d metadata fields, in \u201c ExchangeCode:Symbol \u201d format. Converted to cash and exported to a money market cash-equivalent position, by setting the value of the \u201c export \u201d metadata field to the special value \u201c CASH \u201d. Ignored by specifying the \u201c export \u201d metadata field to the special value \u201c IGNORE \u201d. Provided as Money Instrument , to be used for cash-equivalent value of each holding intended to be converted to cash and included in the portfolio. These are identified by a special value \u201c( MONEY:) \u201d in the \u201c export \u201d metadata field.","title":"Exporting Your Portfolio"},{"location":"exporting_your_portfolio.html#exporting-your-portfolio","text":"Martin Blais , December 2015 (v2) http://furius.ca/beancount/doc/export","title":"Exporting Your Portfolio"},{"location":"exporting_your_portfolio.html#overview","text":"This document explains how to export your portfolio of holdings from Beancount to a Google Finance portfolio (and eventually to other portfolio tracking websites). Note: This is the second version of this document, rewritten in Dec 2015, after greatly simplifying the process of exporting portfolios and completely separating the specification of stock tickers for price downloads. This new, simplified version only uses a single metadata field name: \u201cexport\u201d. The previous document can be found here .","title":"Overview"},{"location":"exporting_your_portfolio.html#portfolio-tracking-tools","text":"There are multiple websites on the internet that allow someone to create a portfolio of investments (or upload a list of transactions to create such a portfolio) and that reports on the changes in the portfolio due to price movements, shows you unrealized capital gains, etc. One such website is the Google Finance portal. Another example is the Yahoo Finance one. These are convenient because they allow you to monitor the impact of price changes on your entire portfolio of assets, across all accounts, during the day or otherwise. However, each of these sites expects their users to use their interfaces and workflows to painfully enter each of the positions one-by-one. A great advantage of using Beancount is that you should never have to enter this type of information manually; instead, you should be able to extract it and upload it to one of these sites. You can be independent of the particular portfolio tracking service you use and should be able to switch between them without losing any data; Beancount can serve as your pristine source for your list of holdings as your needs evolve. Google Finance supports an \u201cimport\u201d feature to create portfolio data which supports the Microsoft OFX financial interchange data format. In this document, we show how we built a Beancount report that exports the portfolio of holdings to OFX for creating a Google Finance portfolio.","title":"Portfolio Tracking Tools"},{"location":"exporting_your_portfolio.html#exporting-to-google-finance","text":"","title":"Exporting to Google Finance"},{"location":"exporting_your_portfolio.html#exporting-your-holdings-to-ofx","text":"First, create an OFX file corresponding to your Beancount holdings. You can use this command to do this: bean-report file.beancount export_portfolio > portfolio.ofx See the report\u2019s own help for options: bean-report file.beancount export_portfolio --help","title":"Exporting your Holdings to OFX"},{"location":"exporting_your_portfolio.html#importing-the-ofx-file-in-google-finance","text":"Then we have to import that OFX file in a web-based portfolio. Visit http://finance.google.com and click on \u201cPortfolios\u201d on the left (or simply visit https://www.google.com/finance/portfolio , this works as of Jan 2015) If you have an existing, previously imported portfolio, click on \u201cDelete Portfolio\u201d to get rid of it. Click on \u201cImport Transactions\u201d, then \u201cChoose File\u201d and select the portfolio.ofx file you exported to, then click on \u201cPreview Import\u201d. You should see a list of imported lots, with familiar stock symbols and names, and Type \u201cBuy\u201d with realistic Shares and Price columns. If not, see the note below. Otherwise, scroll to the bottom of the page and click \u201cImport\u201d. Your portfolio should now appear. You are done. You should never bother updating this portfolio directly using the website\u2026 instead, update your Beancount ledger file, re-export to a new OFX file, delete the previous portfolio and re-import a brand new one over it. Your pristine source is always your Beancount file, ideally you should never have to be worried about corrupting or deleting the portfolio data in any external website.","title":"Importing the OFX File in Google Finance"},{"location":"exporting_your_portfolio.html#controlling-exported-commodities","text":"","title":"Controlling Exported Commodities"},{"location":"exporting_your_portfolio.html#declaring-your-commodities","text":"Generally, we recommend that you explicitly declare each of the commodities used in your input file. It is a neat place to attach information about those commodities, metadata that you should be able to use later on from bean-query or in scripts that you make. For example, you could declare a human-readable description of the commodity, and some other attributes, like this: 2001-09-06 commodity XIN name: \"iShares MSCI EAFE Index ETF (CAD-Hedged)\" class: \"Stock\" type: \"ETF\" ... Beancount will work with or without these declarations (it automatically generates Commodity directives if you haven\u2019t provided them). If you like to be strict and have a bit of discipline, you can require that each commodity be declared by using a plugin that will issue an error when an undeclared commodity appears: plugin \"beancount.plugins.check_commodity\" You can use any date for that Commodity directive. I recommend using the date of the commodity\u2019s inception, or perhaps when it was first introduced by the issuing country, if it is a currency. You can find a suitable date on Wikipedia or on the issuer\u2019s websites. Google Finance may have the date itself.","title":"Declaring Your Commodities"},{"location":"exporting_your_portfolio.html#what-happens-by-default","text":"By default, all holdings are exported as positions with a ticker symbol named the same as the Beancount commodity that you used to define them. If you have a holding of \u201cAAPL\u201d units, it will create an export entry for \u201cAAPL\u201d. The export code attempts to export all holdings by default. However, in any but the simplest unambiguous cases, this is probably not good enough to produce a working Google Finance portfolio. The name for each commodity that you use in your Beancount input file may or may not correspond to a financial instrument in the Google Finance database; due to the very large number of symbols supported in its database, just specifying the ticker symbol is often ambiguous. Google Finance attempts to resolve an ambiguous symbol string to the most likely instrument in its database. It is possible that it resolves it to a different financial instrument from the one you intended. So even if you use the same basic symbol that is used by the exchange, you often still need to disambiguate the symbol by specifying which exchange or symbology it lives in. Google provides a list of these symbol spaces . Here is a real-life example. The symbol for the \u201c CAD-Hedged MSCI EAFE Index \u201d ETF product issued by iShares/Blackrock is \u201c XIN \u201d on the Toronto Stock Exchange ( TSE ). If you just looked up \u201cXIN\u201d on Google Finance , it would choose to resolve it by default to the more likely \u201c NYSE:XIN \u201d symbol ( Xinyuan Real Estate Co. on the New York Stock Exchange ). So you need to disambiguate it by specifying that the desired ETF ticker for this instrument is \u201c TSE:XIN \u201d.","title":"What Happens by Default"},{"location":"exporting_your_portfolio.html#explicitly-specifying-exported-symbols","text":"You can specify which exchange-specific symbol is used to export a commodity by attaching an \u201c export \u201d metadata field to each of your Commodity directives, like this: 2001-09-06 commodity XIN ... export: \"TSE:XIN\" The \u201c export \u201d field is used to map your commodity name to the corresponding instrument in the Google Finance system. If a holding in that commodity needs to be exported, this code is used instead of the Beancount currency name. The symbology used by Google Finance appears to follow the following syntax:","title":"Explicitly Specifying Exported Symbols"},{"location":"exporting_your_portfolio.html#exchangesymbol","text":"where Exchange is a code either for the exchange where the stock trades, or for another source of financial data, e.g. \u201c MUTF \u201d for \u201cmutual funds in the US\u201d, and more . Symbol is a name that is unique within that exchange. I recommend searching for each of your financial instruments in Google Finance, confirming that the instrument corresponds to your instrument (by inspecting the full name, description and price), and inserting the corresponding code like this.","title":"Exchange:Symbol"},{"location":"exporting_your_portfolio.html#exporting-to-a-cash-equivalent","text":"To account for positions that aren\u2019t supported in Google Finance, the export report can convert a holding to its cash-equivalent value. This is also useful for cash positions (e.g., cash sitting idle in a savings or checking account). For example, I hold units of an insurance policy investment vehicle (this is common in Canada, for example, with London Life). This is a financial instrument, but each particular policy issuance has its own associated value\u2014there is no public source of data for each of those products, it\u2019s rather opaque, I can obtain its value with my annual statement, but definitely not in Google Finance. But I\u2019d still like for the asset\u2019s value to be reflected in my portfolio. The way you tell the export code to make this conversion is to specify a special value of \u201cCASH\u201d for the \u201cexport\u201d field, like this: 1878-01-01 commodity LDNLIFE export: \"CASH\" This would convert holdings in LDNLIFE commodities to their corresponding quoted value before exporting, using the price nearest to the date of exporting. Note that attempting to convert to cash a commodity that does not have a corresponding cost or price available for us to determine its value will generate an error. A price must be present to make the conversion. Simple currencies should also be marked as cash in order to be exported: 1999-01-01 commodity EUR name: \"European Union Euro currency\" export: \"CASH\" Finally, all converted holdings are agglomerated into a single cash position. There is no point in exporting these cash entries to separate OFX entries because the Google Finance code will agglomerate them to a single one anyhow.","title":"Exporting to a Cash Equivalent"},{"location":"exporting_your_portfolio.html#declaring-money-instruments","text":"There is a small hiccup in this cash conversion story: the Google Finance importer does not appear to correctly grok an OFX position in \u201ccash\u201d amounts in the importer; I think this is probably just a bug in Google Finance\u2019s import code (or perhaps I haven\u2019t found the correct OFX field values to make this work). Instead, in order to insert a cash position the exporter uses a cash-equivalent commodity which always prices at 1 unit of the currency, e.g. $1.00 for US dollars. For example, for US dollars I I use VMMXX which is a Vanguard Prime Money Market Fund, and for Canadian dollars I use IGI806 . A good type of commodity for this is some sort of Money Market fund. It doesn\u2019t matter so much which one you use, as long as it prices very close to 1. Find one. If you want to include cash commodities, you need to find such a commodity for each of the cash currencies you have on your books and tell Beancount about them. Typically that will be only one or two currencies. You declare them by append the special value \u201c MONEY \u201d for the \u201c export \u201d field, specifying which currency this commodity represents, like this: 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" 1900-01-01 commodity IGI806 export: \"MUTF_CA:IGI806 (MONEY:CAD)\"","title":"Declaring Money Instruments"},{"location":"exporting_your_portfolio.html#ignoring-commodities","text":"Finally, some commodities held in a ledger should be ignored. This is the case for the imaginary commodities used in mirror accounting, for example, to track unvested shares of an employment stock plan, or commodities used to track amounts contributed to a retirement account, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" You tell the export code to ignore a commodity specifying the special value \u201c IGNORE \u201d for the \u201c export \u201d field, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" export: \"IGNORE\" All holdings in units of RSPCAD will thus not be exported. The question of whether some commodities should be exported or not sometimes presents interesting choices. Here is an example: I track my accumulated vacation hours in an asset account. The units are \u201c VACHR \u201d. I associate with this commodity a price that is roughly equivalent to my net hourly salary. This gives me a rough idea how much vacation time money is accumulated on the books, e.g. if I quit my job, how much I\u2019d get paid. Do I want to them included in my total net worth? Should the value from those hours be reflected in the value of my exported portfolio? I think that largely depends on whether I plan to use up those vacations before I leave this job or not, whether I want to have this accumulated value show up on my balance sheet.","title":"Ignoring Commodities"},{"location":"exporting_your_portfolio.html#comparing-with-net-worth","text":"The end result is that the sum total of all your exported positions plus the cash position should approximate the value of all your assets, and the total value calculated by the Google Finance website should be very close to the one reported by this report: bean-report file.beancount networth As a point of comparison, the value of my own portfolio is usually close to within a few hundred US dollars.","title":"Comparing with Net Worth"},{"location":"exporting_your_portfolio.html#details-of-the-ofx-export","text":"","title":"Details of the OFX Export"},{"location":"exporting_your_portfolio.html#import-failures","text":"Exporting a portfolio with symbols that Google Finance does not recognize fatally trips up Google\u2019s import feature. Google Finance then proceeds to fail to recognize your entire file. I recommend that you use explicit exchange:symbol names on all commodities that get exported in order to avoid this problem, as is described further in this document. Google Finance can also be a little bit finicky about the format of the particular OFX file you give it to import. The export_portfolio command attempts to avoid OFX features that would break it but it\u2019s fragile, and it\u2019s possible that the particulars of your portfolio\u2019s contents triggers output that fails to import. If this is the case, at step (4) above, instead of a list of stock symbols you would see a long list of positions that look like XML tags (this is how failure manifests itself). If that is the case, send email to the mailing-list (best if you can isolate the positions that trigger breakage and have the capability to diff files and do some troubleshooting).","title":"Import Failures"},{"location":"exporting_your_portfolio.html#mutual-funds-vs-stocks","text":"The OFX format distinguishes between stocks and mutual funds. In practice, the Google Finance importer does not appear to distinguish between these two (at least it appears to behave the same way), so this is likely an irrelevant implementation detail. Nevertheless, the export code is able to honor the OFX convention of distinguishing between \u201cBUYMF\u201d vs. \u201cBUYSTOCK\u201d XML elements. To this effect, the export code attempts to classify which commodities represent mutual funds by inspecting whether the ticker associated with the commodity begins with the letters \u201cMUTF\u201d and is followed by a colon. For example, \u201c MUTF:RGAGX \u201d and \u201c MUTF_CA:RBF1005 \" will both be detected as mutual funds, for example.","title":"Mutual Funds vs. Stocks"},{"location":"exporting_your_portfolio.html#debugging-the-export","text":"In order to debug how each of your holdings gets exported, use the --debug flag, which will print a detailed account of how each holding is handled by the export script to stderr : bean-report file.beancount export_portfolio --debug 2>&1 >/dev/null | more The script should print the list of exported positions and their corresponding holdings, then the list of converted positions and their corresponding holdings (usually many cash positions are aggregated together) and finally, the list of ignored holdings. This should be enough to explain the entire contents of the exported portfolio.","title":"Debugging the Export"},{"location":"exporting_your_portfolio.html#purchase-dates","text":"Beancount does not currently have all the lot purchase dates, so the purchase dates are exported as if purchased the day before the export. Eventually, when the purchase date is available in Beancount (pending the inventory booking changes ) the actual lot purchase date will probably be used in the export format. However, it\u2019s not yet clear that using the correct date is the right thing to do, because Google Finance might insist on inserting cash for dividends since the reported purchase date\u2026 but Beancount already takes care of inserting a special lot for cash that should already include this. We shall see when we get there.","title":"Purchase Dates"},{"location":"exporting_your_portfolio.html#disable-dividends","text":"Under the \u201cEdit Portfolio\u201d option there is a checkbox that appears to disable the calculation of dividends offered. It would be nice to find a way to automatically disable this checkbox upon import.","title":"Disable Dividends"},{"location":"exporting_your_portfolio.html#automate-upload","text":"It would be nice to automate the replacement of the portfolio with a Python script. Unfortunately, the Google Finance API has been deprecated. Maybe someone can write a screen-scraping routine to do this.","title":"Automate Upload"},{"location":"exporting_your_portfolio.html#summary","text":"Each holding\u2019s export can be controlled by how its commodity is treated, in one of the following ways: Exported to a portfolio position. This is the default, but you should specify the ticker symbol using the \u201c ticker \u201d or \u201c export \u201d metadata fields, in \u201c ExchangeCode:Symbol \u201d format. Converted to cash and exported to a money market cash-equivalent position, by setting the value of the \u201c export \u201d metadata field to the special value \u201c CASH \u201d. Ignored by specifying the \u201c export \u201d metadata field to the special value \u201c IGNORE \u201d. Provided as Money Instrument , to be used for cash-equivalent value of each holding intended to be converted to cash and included in the portfolio. These are identified by a special value \u201c( MONEY:) \u201d in the \u201c export \u201d metadata field.","title":"Summary"},{"location":"external_contributions.html","text":"External Contributions to Beancount \uf0c1 Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/contrib Links to codes written by other people that build on top of or that are related to Beancount and/or Ledgerhub. Indexes \uf0c1 This document contains only packages that were discussed or have had an announcement sent to the mailing-list. You will be able to find other packages on public indices: PyPI: You can find a lot of other Beancount-related projects at PyPI . GitHub: A search for \"beancount\" as of September 2020 brings up 318 projects. Books and Articles \uf0c1 Managing Personal Finances using Python (Siddhant Goel): a 2020 book on plain-text accounting, and Beancount. The Five Minute Ledger Update (RedStreet) A series of articles showing how to automate downloading data from institutions (banks, credit cards, brokerages, etc.) so that ledger updates can be done in under five minutes. Mailing list thread . Tax Loss Harvesting with Beancount (RedStreet): An article about TLH from a US perspective, includes requirements, wash sale subtleties and safe to sell/buy dates, and comparisons to robo-advisors. (Related: fava_investor TLH module . for fava and plain-beancount command line version). Scaled Estimates of Mutual Fund NAVs (RedStreet) : Problem: Mutual fund NAVs (at least in the US) are updated exactly once per day, at the end of the day. When needing to make financial decisions when the trading window is still open (eg: when tax loss harvesting), and the end-of-day NAV is not yet available, it is sometimes useful to make a trivial estimate of that NAV, especially on days when there are huge changes in the market. A Shortcut to Scrape Trade History from Fidelity (David Avraamides) I wrote up a description of how I use a Shortcut to scrape trade history from Fidelity\u2019s website, run it through a Python script to convert to Beancount\u2019s ledger format, and then save it in the clipboard so I can paste it into a ledger file. Lazy Beancount (Vasily M) / Evernight/lazy-beancount : Opinionated guide on how to start (and continue) tracking personal finances using the open-source Beancount accounting system. It comes together with some code. The primary goal of this guide is to provide you a way to start managing your own finances using plain-text accounting gradually and incrementally. Also with various useful tools already included and set up. Plugins \uf0c1 split_transactions : Johann Kl\u00e4hn wrote a plugin that can split a single transaction into many against a limbo account, as would be done for depreciation. zerosum : Red S wrote a plugin to match up transactions that when taken together should sum up to zero and move them to a separate account. effective_dates : Red S wrote a plugin to book different legs of a transaction to different dates beancount-plugins : Dave Stephens created a repository to share various of his plugins related to depreciation. beancount-plugins-zack : Stefano Zacchiroli created this repository to share his plugins. Contains sorting of directives and more. b eancount-oneliner : Akuukis created a plugin to write an entry in one line ( PyPi ). beancount-interpolate : Akuukis created plugins for Beancount to interpolate transactions (recur, split, depr, spread) ( PyPi ). metadata-spray : Add metadata across entries by regex expression rather than having explicit entries (by Vivek Gani). Akuukis/beancount_share : A beancount plugin to share expenses among multiple partners within one ledger. This plugin is very powerful and most probably can deal with all of your sharing needs. w1ndy/beancount_balexpr (Di Weng): A plugin that provides \"balance expressions\" to be run against the Beancount entries, as a Custom directive. See this thread . autobean.narration (Archimedes Smith): Allows to annotate each posting in a concise way by populating per-posting narration metadata from inline comments. autobean.sorted : Checks that transactions are in non-descending order in each file. Helps identifying misplaced or misdated directives, by warning on those directives not following a non-descending order by date in the file. hoostus/beancount-asset-transfer-plugin : A plugin to automatically generate in-kind transfers between two beancount accounts, while preserving the cost basis and acquisition date. PhracturedBlue/fava-portfolio-summary (Phractured Blue): Fava Plugin to show portfolio summaries with rate of return. rename_accounts : Plugin from Red S to rename accounts. E.g.: rename \u201cExpenses:Taxes\u201d to \u201cIncome:Taxes\u201d is helpful for expense analysis. More here . Long_short capital gains classifier : Plugin from Red S to classify capital gains into long and short based on duration the assets were held, and into gains and losses based on value. Autoclose_tree : Automatically closes all of an account's descendants when an account is closed. Evernight/beancount-valuation (Vasily M) : A Beancount plugin to track total value of the opaque fund. You can use it instead of the balance operation to assert total value of the account. If the value of the account is currently different, it will instead alter price of the underlying synthetical commodity created by the plugin used for technical purposes. Tools \uf0c1 alfred-beancount (Yue Wu): An add-on to the \u201cAlfred\u201d macOS tool to quickly enter transactions in one\u2019s Beancount file. Supports full account names and payees match. bean-add (Simon Volpert): A Beancount transaction entry assistant. hoostus/fincen_114 (Justus Pendleton): An FBAR / FinCEN 114 report generator. ghislainbourgeois/beancount_portfolio_allocation ( Ghislain Bourgeois ): A quick way to figure out the asset allocations in different portfolios. hoostus/portfolio-returns (Justus Pendleton): portfolio returns calculator costflow/syntax (Leplay Li): A product that allows users to keep plain text accounting from their favorite messaging apps. A syntax for converting one-line message to beancount/*ledger format. process control chart (Justus Pendleton): Spending relative to portfolio size. Thread. Pinto (Sean Leavey): Supercharged command line interface for Beancount. Supports automatic insertions of transactions in ledger file. PhracturedBlue/fava-encrypt : A docker-base solution for keeping Fava online while keeping beancount data encrypted at rest. See this thread for context. kubauk/beancount-import-gmail : beancount-import-gmail uses the gmail API and OAuth to log into your mailbox and download order details which are then used to augment your transactions for easier classification. sulemankm/budget_report : A very simple command-line budget tracking tool for beancount ledger files. dyumnin/dyu_accounting : Accounting setup to automate generation of various financial statements for Compliance with Indian Govt. Gains Minimizer (RedStreet): Automatically determine lots to sell to minimize capital gains taxes. Live example. beanahead (Marcus Read): Adds the ability to include future transactions (automatically generates regular transactions, provides for ad hoc expected transactions, expected transactions are reconciled against imported transactions; all functionality accessible via cli). autobean-format (Archimedes Smith): Yet another formatter for beancount,, powered by earlier project autobean-refactor, a library for parsing and programmatically manipulating beancount files. based on a proper parser, allowing it to format every corner of your ledger, including arithmetic expressions. akirak/flymake-bean-check (Akira Komamura): flymake support for Emacs. bean-download (Red Street): A downloader that ships with beancount-reds-importers that you can configure to run arbitrary commands to download your account statements. It now has a new feature: the needs-update subcommand. gerdemb/beanpost (Ben Gerdemann): Beanpost consists of a PostgreSQL schema and import/export commands that let you transfer data between a beancount file and a PostgreSQL database. Much of Beancount's functionality is implemented using custom PostgreSQL functions, allowing for complex queries and data manipulation. This setup provides a flexible backend that can integrate with other tools like web apps or reporting systems LaunchPlatform/beanhub-cli (Fang-Pen Lin): Command line tools for BeanHub or Beancount users. zacchiro/beangrep : Beangrep is a grep-like filter for the Beancount plain text accounting system. Alternative Parsers \uf0c1 Bison \uf0c1 The Beancount v2 parser uses GNU flex + GNU bison (for maximum portability). The Beancount v3 parser uses RE/flex + GNU bison (for Unicode and C++). Using Antlr \uf0c1 jord1e/jbeancount (Jordie Biemold) / using Antlr: An alternative parser for Beancount input syntax in Java (using the Antlr4 parser generator). This provides access to parsed Beancount data - without the effect of plugins - from JVM languages. See this post for details. Using Tree-sitter \uf0c1 polarmutex/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the Beancount syntax. https://github.com/dnicolodi/tree-sitter-beancount (Daniele Nicolodi): Another tree-sitter based parser for the Beancount syntax. In Rust \uf0c1 jcornaz/beancount-parser (Jonathan Cornaz): A beancount file parser library for Rust. Uses nom. beancount_parser_lima (Simon Guest): A zero-copy parser for Beancount in Rust. It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive). Uses Logos , Chumsky , and Ariadne . Importers \uf0c1 reds importers : Simple importers and tools for several US based institutions, and various file types. Emphasizes ease of writing your own importers by providing well maintained common libraries for banks, credit cards, and investment houses, and for various file types, which minimizes the institution specific code you need to write. This is a reference implementation of the principles expressed in The Five Minute Ledger Update . Contributions welcome. By RedStreet plaid2text : An importer from Plaid which stores the transactions to a Mongo DB and is able to render it to Beancount syntax. By Micah Duke. jbms/beancount-import : A tool for semi-automatically importing transactions from external data sources, with support for merging and reconciling imported transactions with each other and with existing transactions in the beancount journal. The UI is web based. ( Announcement , link to previous version ). By Jeremy Maitin-Shepard. awesome-beancount : A collection of importers for Chinese banks + tips and tricks. By Zhuoyun Wei . beansoup : Filippo Tampieri is sharing some of his Beancount importers and auto-completer in this project. montaropdf/beancount-importers : An importer to extract overtime and vacation from a timesheet format for invoicing customers. siddhantgoel/beancount-dkb (Siddhant Goel): importer for DKB CSV files. prabusw/beancount-importer-zerodha : Importer for the Indian broker Zerodha. swapi/beancount-utils : Another importer for Zerodha. Dr-Nuke/drnuke-bean (Dr Nuke): An importer for IBKR, based on the flex query (API-like) and one for Swiss PostFinance. Beanborg (Luciano Fiandesio): Beanborg automatically imports financial transactions from external CSV files into the Beancount bookkeeping system. szabootibor/beancount-degiro ( PyPI ): Importer for the trading accounts of the Dutch broker Degiro. siddhantgoel/beancount-ing-diba ( PyPI ): ING account importer (NL). PaulsTek/csv2bean : Asimple application to preprocess csv files using google sheets in Go. ericaltendorf/magicbeans (Eric Altendorf): Beancount importers for crypto data. Detailed lot tracking and capital gains/losses reporting for crypto assets. \" I wrote it because I was not satisfied with the accuracy or transparency of existing commercial services for crypto tax reporting.\" OSadovy/uabean (Oleksii Sadovyi): A set of Beancount importers and scripts for popular Ukrainian banks and more. fdavies93/seneca (Frank Davies): Importer for Wise. Multi-currency transfers. LaunchPlatform/beanhub-import : New beancount importer with a UI. rlan/beancount-multitool (Rick Lan): Beancount Multitool is a command-line-interface (CLI) tool that converts financial data from financial institutions to Beancount files (supported: JA Bank \uff2a\uff21\u30cd\u30c3\u30c8\u30d0\u30f3\u30af, Rakuten Card \u697d\u5929\u30ab\u30fc\u30c9, Rakuten Bank \u697d\u5929\u9280\u884c, SBI Shinsei Bank \u65b0\u751f\u9280\u884c). Associated post: https://www.linkedin.com/feed/update/urn:li:activity:7198125470662500352/ LaunchPlatform/beanhub-import (Fang-Pen Lin): Beanhub-import is a simple, declarative, smart, and easy-to-use library for importing extracted transactions from beanhub-extract. It generates Beancount transactions based on predefined rules. Converters \uf0c1 plaid2text : Python Scripts to export Plaid transactions and transform them into Ledger or Beancount syntax formatted files. gnucash-to-beancount : A script from Henrique Bastos to convert a GNUcash SQLite database into an equivalent Beancount input file. debanjum/gnucash-to-beancount : A fork of the above. andrewStein/gnucash-to-beancount : A further fork from the above two, which fixes a lot of issues (see this thread ). hoostus/beancount-ynab : A converter from YNAB to Beancount. hoostus/beancount-ynab5 : Same convert for YNAB from the same author, but for the more recent version 5. ledger2beancount : A script to convert ledger files to beancount. It was developed by Stefano Zacchiroli and Martin Michlmayr. smart_importer : A smart importer for beancount and fava, with intelligent suggestions for account names. By Johannes Harms. beancount-export-patreon.js : JavaScript that will export your Patreon transactions so you can see details of exactly who you've been giving money to. By kanepyork@gmail. alensiljak/pta-converters (Alen \u0160iljak): GnuCash -> Beancount converter (2019). grostim/Beancount-myTools (Timothee Gros): Personal importer tools of the author for French banks. Downloaders \uf0c1 bean-download (RedStreet): bean-download is a tool to conveniently download your transactions from supporting institutions. You configure it with a list of your institutions and arbitrary commands to download them, typically via ofxget . It downloads all of them in parallel, names them appropriately and puts them in the directory of your choice, from which you can then import. The tool is installed as a part of beancount-reds-importers . See accompanying article . ofx-summarize (RedStreet): When building importers, it helps to be able to peek into a .ofx or a .qfx file that you are trying to import. The ofx-summarize command does just that. It ships with beancount-reds-importers , and should be available by simply invoking the command. Running the command on a file shows you a few transactions in the file. What is very useful is to be able to explore your .ofx file via the python debugger or interpreter. Price Sources \uf0c1 hoostus/beancount-price-sources : A Morningstar price fetcher which aggregates multiple exchanges, including non-US ones. andyjscott/beancount-financequote : Finance::Quote support for bean-price. aamerabbas/beancount-coinmarketcap : Price fetcher for coinmarketcap ( see post ). grostim/Beancount-myTools/.../iexcloud.py : Price fetcher for iexcloud by Timothee Gros. xuhcc/beancount-cryptoassets (Kirill Goncharov): Price sources for cryptocurrencies. xuhcc/beancount-ethereum-importer (Kirill Goncharov): Ethereum transaction importer for Beancount. Includes a script that downloads transactions from Etherscan and an importer for downloaded transactions. xuhcc/beancount-exchangerates (Kirill Goncharov): Price source for http://exchangeratesapi.io . tarioch/beancounttools (Patrick Ruckstuhl): Price sources and importers. https://gitlab.com/chrisberkhout/pricehist (Chris Berkhout): A command-line tool that can fetch daily historical prices from multiple sources and output them in several formats. Supports some sources for CoinDesk, European Central Bank, Alpha Vantage, CoinMarketCap. The user can request a specific price type such as high, low, open, close or adjusted close. It can also be used through bean-price. Development \uf0c1 Py3k type annotations : Yuchen Ying is implementing python3 type annotations for Beancount. bryall/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the beancount syntax. jmgilman/beancount-stubs : Typing .pyi stubs for some of the Beancount source. Documentation \uf0c1 Beancount Documentation ( Kirill Goncharov ): Official conversion of the Beancount documentation from Google Docs source to Markdown and HTML. This includes most of the Google Docs documents and is maintained in a Beancount org repo here by Kirill Goncharov. Beancount Source Code Documentation ( Dominik Aumayr ): A Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . SQL queries for Beancount (Dominik Aumayr): Example SQL queries. Beancount \u2014\u2014 \u547d\u4ee4\u884c\u590d\u5f0f\u7c3f\u8bb0 (Zhuoyun Wei): A tutorial (blog post) in Chinese on how to use Beancount. Managing my personal finances with Beancount (Alex Johnstone) Counting beans\u2014and more\u2014with Beancount (LWN) Interfaces / Web \uf0c1 fava: A web interface for Beancount (Dominik Aumayr, Jakob Schnitzer): Beancount comes with its own simple web front-end (\u201cbean-web\u201d) intended merely as a thin shell to invoke and display HTML versions of its reports. \u201cFava\u201d is an alternative web application front-end with more & different features, intended initially as a playground and proof-of-concept to explore a newer, better design for presenting the contents of a Beancount file. Fava Classy Portfolio (Vivek Gani): Classy Portfolio is an Extension for Fava, a web interface for the Beancount plaintext accounting software. The extension displays a list of different portfolios (e.g. 'taxable' vs. 'retirement'), with breakdowns using 'asset-class' and 'asset-subclass' metadata labels on commodities. Fava Investor project (RedStreet): Fava_investor aims to be a comprehensive set of reports, analyses, and tools for investments, for Beancount and Fava. It is a collection of modules, with each module offering a Fava plugin, a Beancount library, and a Beancount based CLI (command line interface). Current modules include: Visual, tree structured asset allocation by class, asset allocation by account, tax loss harvester, cash drag analysis. Fava Miler (RedStreet): Airline miles and rewards points: expiration and value reporting. Fava Envelope (Brian Ryall): A beancount fava extension to add an envelope budgeting capability to fava and beancount. It is developed as a Fava plugin and CLI. scauligi/refried (Sunjay Cauligi): An envelope budgeting plugin for Fava, inspired by YNAB: all expense accounts become individual budgeting categories, budgeting is carried out using transactions to these accounts, and the plugin automaticallyapplies a tag to all rebudget transactions so they can easily be filtered out. Provides budget and account views like YNAB. BeanHub.io : A web front-end for Beancount content. \" Since I started using Beancount, I have dreamed of making it fully automatic. For a few years now, I've been building tools for that goal. Connecting to the bank and fetching data directly from there is one of the goals I want to achieve. I built this feature and have been testing it for a while for my accounting book. Now my Beancount books are 80% fully automatic. I can open my repository, and the transactions from the bank will automatically appear as a new commit like this without me lifting a finger. The whole import system is based on our open-source beanhub-import and beanhub-extract. The only proprietary part in the import flow is the Plaid integration. So, suppose you don't trust me and still want to import transactions automatically. As long as you connect to Plaid and write CSV files based on the transactions you fetched from Plaid, you should be able to have the same automatic transaction importing system without using the BeanHub service. \" Blog posts: https://beanhub.io/blog/2024/06/24/introduction-of-beanhub-connect/ https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/ https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/ jmgilman/bdantic : A package for extending beancount with pydantic . With this package you can convert your ledger to JSON, and more. autobean/refactor (Archimedes Smith): Tooling to programmatically edit one's ledger, including formatting, sorting, refactoring, rearranging accounts, optimizing via plugins, migration from v2, inserting transactions in a ledger on import, and more. seltzered/beancolage (Vivek Gani): An Eclipse Theia (vendor-agnostic vscode) app that tries to bundle existing beancount-based packages such as vscode-beancount and Fava. aaronstacy.com/personal-finances-dashboard : HTML + D3.js visualization dashboard for Beancount data. Mobile/Phone Data Entry \uf0c1 Beancount Mobile App (Kirill Goncharov): A mobile data entry app for Beancount. (Currently only Android is supported.) Repo: https://github.com/xuhcc/beancount-mobile ( Announcement ). http://costflow.io : Plain Text Accounting in WeChat. \" Send a message to our bot in Telegram, Facebook Messenger, Whatsapp, LINE, WeChat, etc. Costflow will transform your message into Beancount / Ledger / hledger format transaction magically. Append the transaction to the file in your Dropbox / Google Drive. With the help of their apps, the file will be synced to your computer. \"","title":"External Contributions"},{"location":"external_contributions.html#external-contributions-to-beancount","text":"Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/contrib Links to codes written by other people that build on top of or that are related to Beancount and/or Ledgerhub.","title":"External Contributions to Beancount"},{"location":"external_contributions.html#indexes","text":"This document contains only packages that were discussed or have had an announcement sent to the mailing-list. You will be able to find other packages on public indices: PyPI: You can find a lot of other Beancount-related projects at PyPI . GitHub: A search for \"beancount\" as of September 2020 brings up 318 projects.","title":"Indexes"},{"location":"external_contributions.html#books-and-articles","text":"Managing Personal Finances using Python (Siddhant Goel): a 2020 book on plain-text accounting, and Beancount. The Five Minute Ledger Update (RedStreet) A series of articles showing how to automate downloading data from institutions (banks, credit cards, brokerages, etc.) so that ledger updates can be done in under five minutes. Mailing list thread . Tax Loss Harvesting with Beancount (RedStreet): An article about TLH from a US perspective, includes requirements, wash sale subtleties and safe to sell/buy dates, and comparisons to robo-advisors. (Related: fava_investor TLH module . for fava and plain-beancount command line version). Scaled Estimates of Mutual Fund NAVs (RedStreet) : Problem: Mutual fund NAVs (at least in the US) are updated exactly once per day, at the end of the day. When needing to make financial decisions when the trading window is still open (eg: when tax loss harvesting), and the end-of-day NAV is not yet available, it is sometimes useful to make a trivial estimate of that NAV, especially on days when there are huge changes in the market. A Shortcut to Scrape Trade History from Fidelity (David Avraamides) I wrote up a description of how I use a Shortcut to scrape trade history from Fidelity\u2019s website, run it through a Python script to convert to Beancount\u2019s ledger format, and then save it in the clipboard so I can paste it into a ledger file. Lazy Beancount (Vasily M) / Evernight/lazy-beancount : Opinionated guide on how to start (and continue) tracking personal finances using the open-source Beancount accounting system. It comes together with some code. The primary goal of this guide is to provide you a way to start managing your own finances using plain-text accounting gradually and incrementally. Also with various useful tools already included and set up.","title":"Books and Articles"},{"location":"external_contributions.html#plugins","text":"split_transactions : Johann Kl\u00e4hn wrote a plugin that can split a single transaction into many against a limbo account, as would be done for depreciation. zerosum : Red S wrote a plugin to match up transactions that when taken together should sum up to zero and move them to a separate account. effective_dates : Red S wrote a plugin to book different legs of a transaction to different dates beancount-plugins : Dave Stephens created a repository to share various of his plugins related to depreciation. beancount-plugins-zack : Stefano Zacchiroli created this repository to share his plugins. Contains sorting of directives and more. b eancount-oneliner : Akuukis created a plugin to write an entry in one line ( PyPi ). beancount-interpolate : Akuukis created plugins for Beancount to interpolate transactions (recur, split, depr, spread) ( PyPi ). metadata-spray : Add metadata across entries by regex expression rather than having explicit entries (by Vivek Gani). Akuukis/beancount_share : A beancount plugin to share expenses among multiple partners within one ledger. This plugin is very powerful and most probably can deal with all of your sharing needs. w1ndy/beancount_balexpr (Di Weng): A plugin that provides \"balance expressions\" to be run against the Beancount entries, as a Custom directive. See this thread . autobean.narration (Archimedes Smith): Allows to annotate each posting in a concise way by populating per-posting narration metadata from inline comments. autobean.sorted : Checks that transactions are in non-descending order in each file. Helps identifying misplaced or misdated directives, by warning on those directives not following a non-descending order by date in the file. hoostus/beancount-asset-transfer-plugin : A plugin to automatically generate in-kind transfers between two beancount accounts, while preserving the cost basis and acquisition date. PhracturedBlue/fava-portfolio-summary (Phractured Blue): Fava Plugin to show portfolio summaries with rate of return. rename_accounts : Plugin from Red S to rename accounts. E.g.: rename \u201cExpenses:Taxes\u201d to \u201cIncome:Taxes\u201d is helpful for expense analysis. More here . Long_short capital gains classifier : Plugin from Red S to classify capital gains into long and short based on duration the assets were held, and into gains and losses based on value. Autoclose_tree : Automatically closes all of an account's descendants when an account is closed. Evernight/beancount-valuation (Vasily M) : A Beancount plugin to track total value of the opaque fund. You can use it instead of the balance operation to assert total value of the account. If the value of the account is currently different, it will instead alter price of the underlying synthetical commodity created by the plugin used for technical purposes.","title":"Plugins"},{"location":"external_contributions.html#tools","text":"alfred-beancount (Yue Wu): An add-on to the \u201cAlfred\u201d macOS tool to quickly enter transactions in one\u2019s Beancount file. Supports full account names and payees match. bean-add (Simon Volpert): A Beancount transaction entry assistant. hoostus/fincen_114 (Justus Pendleton): An FBAR / FinCEN 114 report generator. ghislainbourgeois/beancount_portfolio_allocation ( Ghislain Bourgeois ): A quick way to figure out the asset allocations in different portfolios. hoostus/portfolio-returns (Justus Pendleton): portfolio returns calculator costflow/syntax (Leplay Li): A product that allows users to keep plain text accounting from their favorite messaging apps. A syntax for converting one-line message to beancount/*ledger format. process control chart (Justus Pendleton): Spending relative to portfolio size. Thread. Pinto (Sean Leavey): Supercharged command line interface for Beancount. Supports automatic insertions of transactions in ledger file. PhracturedBlue/fava-encrypt : A docker-base solution for keeping Fava online while keeping beancount data encrypted at rest. See this thread for context. kubauk/beancount-import-gmail : beancount-import-gmail uses the gmail API and OAuth to log into your mailbox and download order details which are then used to augment your transactions for easier classification. sulemankm/budget_report : A very simple command-line budget tracking tool for beancount ledger files. dyumnin/dyu_accounting : Accounting setup to automate generation of various financial statements for Compliance with Indian Govt. Gains Minimizer (RedStreet): Automatically determine lots to sell to minimize capital gains taxes. Live example. beanahead (Marcus Read): Adds the ability to include future transactions (automatically generates regular transactions, provides for ad hoc expected transactions, expected transactions are reconciled against imported transactions; all functionality accessible via cli). autobean-format (Archimedes Smith): Yet another formatter for beancount,, powered by earlier project autobean-refactor, a library for parsing and programmatically manipulating beancount files. based on a proper parser, allowing it to format every corner of your ledger, including arithmetic expressions. akirak/flymake-bean-check (Akira Komamura): flymake support for Emacs. bean-download (Red Street): A downloader that ships with beancount-reds-importers that you can configure to run arbitrary commands to download your account statements. It now has a new feature: the needs-update subcommand. gerdemb/beanpost (Ben Gerdemann): Beanpost consists of a PostgreSQL schema and import/export commands that let you transfer data between a beancount file and a PostgreSQL database. Much of Beancount's functionality is implemented using custom PostgreSQL functions, allowing for complex queries and data manipulation. This setup provides a flexible backend that can integrate with other tools like web apps or reporting systems LaunchPlatform/beanhub-cli (Fang-Pen Lin): Command line tools for BeanHub or Beancount users. zacchiro/beangrep : Beangrep is a grep-like filter for the Beancount plain text accounting system.","title":"Tools"},{"location":"external_contributions.html#alternative-parsers","text":"","title":"Alternative Parsers"},{"location":"external_contributions.html#bison","text":"The Beancount v2 parser uses GNU flex + GNU bison (for maximum portability). The Beancount v3 parser uses RE/flex + GNU bison (for Unicode and C++).","title":"Bison"},{"location":"external_contributions.html#using-antlr","text":"jord1e/jbeancount (Jordie Biemold) / using Antlr: An alternative parser for Beancount input syntax in Java (using the Antlr4 parser generator). This provides access to parsed Beancount data - without the effect of plugins - from JVM languages. See this post for details.","title":"Using Antlr"},{"location":"external_contributions.html#using-tree-sitter","text":"polarmutex/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the Beancount syntax. https://github.com/dnicolodi/tree-sitter-beancount (Daniele Nicolodi): Another tree-sitter based parser for the Beancount syntax.","title":"Using Tree-sitter"},{"location":"external_contributions.html#in-rust","text":"jcornaz/beancount-parser (Jonathan Cornaz): A beancount file parser library for Rust. Uses nom. beancount_parser_lima (Simon Guest): A zero-copy parser for Beancount in Rust. It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive). Uses Logos , Chumsky , and Ariadne .","title":"In Rust"},{"location":"external_contributions.html#importers","text":"reds importers : Simple importers and tools for several US based institutions, and various file types. Emphasizes ease of writing your own importers by providing well maintained common libraries for banks, credit cards, and investment houses, and for various file types, which minimizes the institution specific code you need to write. This is a reference implementation of the principles expressed in The Five Minute Ledger Update . Contributions welcome. By RedStreet plaid2text : An importer from Plaid which stores the transactions to a Mongo DB and is able to render it to Beancount syntax. By Micah Duke. jbms/beancount-import : A tool for semi-automatically importing transactions from external data sources, with support for merging and reconciling imported transactions with each other and with existing transactions in the beancount journal. The UI is web based. ( Announcement , link to previous version ). By Jeremy Maitin-Shepard. awesome-beancount : A collection of importers for Chinese banks + tips and tricks. By Zhuoyun Wei . beansoup : Filippo Tampieri is sharing some of his Beancount importers and auto-completer in this project. montaropdf/beancount-importers : An importer to extract overtime and vacation from a timesheet format for invoicing customers. siddhantgoel/beancount-dkb (Siddhant Goel): importer for DKB CSV files. prabusw/beancount-importer-zerodha : Importer for the Indian broker Zerodha. swapi/beancount-utils : Another importer for Zerodha. Dr-Nuke/drnuke-bean (Dr Nuke): An importer for IBKR, based on the flex query (API-like) and one for Swiss PostFinance. Beanborg (Luciano Fiandesio): Beanborg automatically imports financial transactions from external CSV files into the Beancount bookkeeping system. szabootibor/beancount-degiro ( PyPI ): Importer for the trading accounts of the Dutch broker Degiro. siddhantgoel/beancount-ing-diba ( PyPI ): ING account importer (NL). PaulsTek/csv2bean : Asimple application to preprocess csv files using google sheets in Go. ericaltendorf/magicbeans (Eric Altendorf): Beancount importers for crypto data. Detailed lot tracking and capital gains/losses reporting for crypto assets. \" I wrote it because I was not satisfied with the accuracy or transparency of existing commercial services for crypto tax reporting.\" OSadovy/uabean (Oleksii Sadovyi): A set of Beancount importers and scripts for popular Ukrainian banks and more. fdavies93/seneca (Frank Davies): Importer for Wise. Multi-currency transfers. LaunchPlatform/beanhub-import : New beancount importer with a UI. rlan/beancount-multitool (Rick Lan): Beancount Multitool is a command-line-interface (CLI) tool that converts financial data from financial institutions to Beancount files (supported: JA Bank \uff2a\uff21\u30cd\u30c3\u30c8\u30d0\u30f3\u30af, Rakuten Card \u697d\u5929\u30ab\u30fc\u30c9, Rakuten Bank \u697d\u5929\u9280\u884c, SBI Shinsei Bank \u65b0\u751f\u9280\u884c). Associated post: https://www.linkedin.com/feed/update/urn:li:activity:7198125470662500352/ LaunchPlatform/beanhub-import (Fang-Pen Lin): Beanhub-import is a simple, declarative, smart, and easy-to-use library for importing extracted transactions from beanhub-extract. It generates Beancount transactions based on predefined rules.","title":"Importers"},{"location":"external_contributions.html#converters","text":"plaid2text : Python Scripts to export Plaid transactions and transform them into Ledger or Beancount syntax formatted files. gnucash-to-beancount : A script from Henrique Bastos to convert a GNUcash SQLite database into an equivalent Beancount input file. debanjum/gnucash-to-beancount : A fork of the above. andrewStein/gnucash-to-beancount : A further fork from the above two, which fixes a lot of issues (see this thread ). hoostus/beancount-ynab : A converter from YNAB to Beancount. hoostus/beancount-ynab5 : Same convert for YNAB from the same author, but for the more recent version 5. ledger2beancount : A script to convert ledger files to beancount. It was developed by Stefano Zacchiroli and Martin Michlmayr. smart_importer : A smart importer for beancount and fava, with intelligent suggestions for account names. By Johannes Harms. beancount-export-patreon.js : JavaScript that will export your Patreon transactions so you can see details of exactly who you've been giving money to. By kanepyork@gmail. alensiljak/pta-converters (Alen \u0160iljak): GnuCash -> Beancount converter (2019). grostim/Beancount-myTools (Timothee Gros): Personal importer tools of the author for French banks.","title":"Converters"},{"location":"external_contributions.html#downloaders","text":"bean-download (RedStreet): bean-download is a tool to conveniently download your transactions from supporting institutions. You configure it with a list of your institutions and arbitrary commands to download them, typically via ofxget . It downloads all of them in parallel, names them appropriately and puts them in the directory of your choice, from which you can then import. The tool is installed as a part of beancount-reds-importers . See accompanying article . ofx-summarize (RedStreet): When building importers, it helps to be able to peek into a .ofx or a .qfx file that you are trying to import. The ofx-summarize command does just that. It ships with beancount-reds-importers , and should be available by simply invoking the command. Running the command on a file shows you a few transactions in the file. What is very useful is to be able to explore your .ofx file via the python debugger or interpreter.","title":"Downloaders"},{"location":"external_contributions.html#price-sources","text":"hoostus/beancount-price-sources : A Morningstar price fetcher which aggregates multiple exchanges, including non-US ones. andyjscott/beancount-financequote : Finance::Quote support for bean-price. aamerabbas/beancount-coinmarketcap : Price fetcher for coinmarketcap ( see post ). grostim/Beancount-myTools/.../iexcloud.py : Price fetcher for iexcloud by Timothee Gros. xuhcc/beancount-cryptoassets (Kirill Goncharov): Price sources for cryptocurrencies. xuhcc/beancount-ethereum-importer (Kirill Goncharov): Ethereum transaction importer for Beancount. Includes a script that downloads transactions from Etherscan and an importer for downloaded transactions. xuhcc/beancount-exchangerates (Kirill Goncharov): Price source for http://exchangeratesapi.io . tarioch/beancounttools (Patrick Ruckstuhl): Price sources and importers. https://gitlab.com/chrisberkhout/pricehist (Chris Berkhout): A command-line tool that can fetch daily historical prices from multiple sources and output them in several formats. Supports some sources for CoinDesk, European Central Bank, Alpha Vantage, CoinMarketCap. The user can request a specific price type such as high, low, open, close or adjusted close. It can also be used through bean-price.","title":"Price Sources"},{"location":"external_contributions.html#development","text":"Py3k type annotations : Yuchen Ying is implementing python3 type annotations for Beancount. bryall/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the beancount syntax. jmgilman/beancount-stubs : Typing .pyi stubs for some of the Beancount source.","title":"Development"},{"location":"external_contributions.html#documentation","text":"Beancount Documentation ( Kirill Goncharov ): Official conversion of the Beancount documentation from Google Docs source to Markdown and HTML. This includes most of the Google Docs documents and is maintained in a Beancount org repo here by Kirill Goncharov. Beancount Source Code Documentation ( Dominik Aumayr ): A Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . SQL queries for Beancount (Dominik Aumayr): Example SQL queries. Beancount \u2014\u2014 \u547d\u4ee4\u884c\u590d\u5f0f\u7c3f\u8bb0 (Zhuoyun Wei): A tutorial (blog post) in Chinese on how to use Beancount. Managing my personal finances with Beancount (Alex Johnstone) Counting beans\u2014and more\u2014with Beancount (LWN)","title":"Documentation"},{"location":"external_contributions.html#interfaces-web","text":"fava: A web interface for Beancount (Dominik Aumayr, Jakob Schnitzer): Beancount comes with its own simple web front-end (\u201cbean-web\u201d) intended merely as a thin shell to invoke and display HTML versions of its reports. \u201cFava\u201d is an alternative web application front-end with more & different features, intended initially as a playground and proof-of-concept to explore a newer, better design for presenting the contents of a Beancount file. Fava Classy Portfolio (Vivek Gani): Classy Portfolio is an Extension for Fava, a web interface for the Beancount plaintext accounting software. The extension displays a list of different portfolios (e.g. 'taxable' vs. 'retirement'), with breakdowns using 'asset-class' and 'asset-subclass' metadata labels on commodities. Fava Investor project (RedStreet): Fava_investor aims to be a comprehensive set of reports, analyses, and tools for investments, for Beancount and Fava. It is a collection of modules, with each module offering a Fava plugin, a Beancount library, and a Beancount based CLI (command line interface). Current modules include: Visual, tree structured asset allocation by class, asset allocation by account, tax loss harvester, cash drag analysis. Fava Miler (RedStreet): Airline miles and rewards points: expiration and value reporting. Fava Envelope (Brian Ryall): A beancount fava extension to add an envelope budgeting capability to fava and beancount. It is developed as a Fava plugin and CLI. scauligi/refried (Sunjay Cauligi): An envelope budgeting plugin for Fava, inspired by YNAB: all expense accounts become individual budgeting categories, budgeting is carried out using transactions to these accounts, and the plugin automaticallyapplies a tag to all rebudget transactions so they can easily be filtered out. Provides budget and account views like YNAB. BeanHub.io : A web front-end for Beancount content. \" Since I started using Beancount, I have dreamed of making it fully automatic. For a few years now, I've been building tools for that goal. Connecting to the bank and fetching data directly from there is one of the goals I want to achieve. I built this feature and have been testing it for a while for my accounting book. Now my Beancount books are 80% fully automatic. I can open my repository, and the transactions from the bank will automatically appear as a new commit like this without me lifting a finger. The whole import system is based on our open-source beanhub-import and beanhub-extract. The only proprietary part in the import flow is the Plaid integration. So, suppose you don't trust me and still want to import transactions automatically. As long as you connect to Plaid and write CSV files based on the transactions you fetched from Plaid, you should be able to have the same automatic transaction importing system without using the BeanHub service. \" Blog posts: https://beanhub.io/blog/2024/06/24/introduction-of-beanhub-connect/ https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/ https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/ jmgilman/bdantic : A package for extending beancount with pydantic . With this package you can convert your ledger to JSON, and more. autobean/refactor (Archimedes Smith): Tooling to programmatically edit one's ledger, including formatting, sorting, refactoring, rearranging accounts, optimizing via plugins, migration from v2, inserting transactions in a ledger on import, and more. seltzered/beancolage (Vivek Gani): An Eclipse Theia (vendor-agnostic vscode) app that tries to bundle existing beancount-based packages such as vscode-beancount and Fava. aaronstacy.com/personal-finances-dashboard : HTML + D3.js visualization dashboard for Beancount data.","title":"Interfaces / Web"},{"location":"external_contributions.html#mobilephone-data-entry","text":"Beancount Mobile App (Kirill Goncharov): A mobile data entry app for Beancount. (Currently only Android is supported.) Repo: https://github.com/xuhcc/beancount-mobile ( Announcement ). http://costflow.io : Plain Text Accounting in WeChat. \" Send a message to our bot in Telegram, Facebook Messenger, Whatsapp, LINE, WeChat, etc. Costflow will transform your message into Beancount / Ledger / hledger format transaction magically. Append the transaction to the file in your Dropbox / Google Drive. With the help of their apps, the file will be synced to your computer. \"","title":"Mobile/Phone Data Entry"},{"location":"fetching_prices_in_beancount.html","text":"Prices in Beancount \uf0c1 Martin Blais , December 2015 http://furius.ca/beancount/doc/prices Introduction \uf0c1 Processing a Beancount file is, by definition, constrained to the contents of the file itself. In particular, the latest prices of commodities are never fetched automatically from the internet. This is by design, so that any run of a report is deterministic and can also be run offline. No surprises. However, we do need access to price information in order to compute market values of assets. To this end, Beancount provides a Price directive which can be used to fill its in-memory price database by inserting these price points inline in the input file: 2015-11-20 price ITOT 95.46 USD 2015-11-20 price LQD 115.63 USD 2015-11-21 price USD 1.33495 CAD \u2026 Of course, you could do this manually, looking up the prices online and writing the directives yourself. But for assets which are traded publicly you can automate it, by invoking some code that will download prices and write out the directives for you. Beancount comes with some tools to help you do this. This document describes these tools. The Problem \uf0c1 In the context of maintaining a Beancount file, we have a few particular needs to address. First, we cannot expect the user to always update the input file in a timely manner. This means that we have to be able to fetch not only the latest prices, but also historical prices, from the past. Beancount provides an interface to provide these prices and a few implementations (fetching from Yahoo! Finance and from Google Finance). Second, we don't want to store too many prices for holdings which aren't relevant to us at a particular point in time. The list of assets held in a file varies over time. Ideally we would want to include the list of prices for just those assets, while they are held. The Beancount price maintenance tool is able to figure out which commodities it needs to fetch prices for at a particular date. Third, we don't want to fetch the same price if it already appears in the input file. The tools detect this and skip those prices. There's also a caching mechanism that avoids redundant network calls. Finally, while we provide some basic implementations of price sources, we cannot provide such codes for all possible online sources and websites. The problem is analogous to that of importing and extracting data from various institutions. In order to address that, we provide a mechanism for extensibility , a way that you can implement your own price source fetcher in a Python module and point to it from your input file by specifying the module's name as the source for that commodity. The \u201cbean-price\u201d Tool \uf0c1 Beancount comes with a \u201cbean-price\u201d command-line tool that integrates the ideas above. By default, this script accepts a list of Beancount input filenames, and fetches prices required to compute latest market values for current positions held in accounts: bean-price /home/joe/finances/joe.beancount It is also possible to provide a list of specific price fetching jobs to run, e.g., bean-price -e USD:google/TSE:XUS CAD:mysources.morningstar/RBF1005 These jobs are run concurrently so it should be fairly fast. Source Strings \uf0c1 The general format of each of these \"job source strings\" is :/[^] For example: USD:beancount.prices.sources.google/NASDAQ:AAPL The \u201cquote-currency\u201d is the currency the Commodity is quoted in. For example, shares of Apple are quoted in US dollars. The \"module\" is the name of a Python module that contains a Source class which can be instantiated and which connect to a data source to extract price data. These modules are automatically imported and a Source class therein instantiated in order to pull the price from the particular online source they support. This allows you to write your own fetcher codes without having to modify this script. Your code can be placed anywhere on your Python PYTHONPATH and you should not have to modify Beancount itself for this to work. The \u201csymbol\u201d is a string that is fed to the price fetcher to lookup the currency. For example, Apple shares trade on the Nasdaq, and the corresponding symbol in the Google Finance source is \u201cNASDAQ:AAPL\u201d. Other price sources may have a different symbology, e.g., some may require the asset's CUSIP. Default implementations of price sources are provided; we provide fetchers for Yahoo! Finance or Google Finance, which cover a large universe of common public investment types (e.g. stock and some mutual funds). As a convenience, the module name is always first searched under the \" beancount.prices.sources \" package, where those implementations live. This is how, for example, in order to use the provided Yahoo! Finance data fetcher you don't have to write all of \" beancount.prices.sources.yahoo/AAPL \" but you can simply use \" yahoo/AAPL \". Fallback Sources \uf0c1 In practice, fetching prices online often fails. Data sources typically only support some limited number of assets and even then, the support may vary between assets. As an example, Google Finance supports historical prices for stocks, but does not return historical prices for currency instruments (these restrictions may be more related to contractual arrangements between them and the data providers upstream than with technical limitations). To this extent, a source string may provide multiple sources for the data, separated with commas. For example: USD:google/CURRENCY:GBPUSD,yahoo/GBPUSD Each source is tried in turn, and if one fails to return a valid price, the next source is tried as a fallback. The hope is that at least one of the specified sources will work out. Inverted Prices \uf0c1 Sometimes, prices are only available for the inverse of an instrument. This is often the case for currencies. For example, the price of Canadian dollars quoted in US dollars is provided by the USD/CAD market, which gives the price of a US dollar in Canadian dollars (the inverse). In order use this, you can prepend \" ^ \" to the instrument name to instruct the tool to compute the inverse of the fetched price: USD:google/^CURRENCY:USDCAD If a source price is to be inverted, like this, the precision could be different than what is fetched. For instance, if the price of USD/CAD is 1.32759, for the above directive to price the \u201cCAD\u201d instrument it would output this: 2015-10-28 price CAD 0.753244601119 USD By default, inverted rates will be rounded similarly to how other Price directives were rounding those numbers. As you may now, Beancount's in-memory price database works in both directions (the reciprocals of all rates are stored automatically). So if you prefer to have the output Price entries with swapped currencies instead of inverting the rate number itself, you can use the --swap-inverted option. In the previous example for the price of CAD, it would output this: 2015-10-28 price USD 1.32759 CAD Date \uf0c1 By default, the latest prices for the assets are pulled in. You can use an option to fetch prices for a desired date in the past instead: bean-price --date=2015-02-03 \u2026 If you are using an input file to specify the list of prices to be fetched, the tool will figure out the list of assets held on the books at that time and fetch historical prices for those assets only. Caching \uf0c1 Prices are automatically cached (if current and latest, prices are cached for only a short period of time, about half an hour). This is convenient when you're having to run the script multiple times in a row for troubleshooting. You can disable the cache with an option: bean-price --no-cache You can also instruct the script to clear the cache before fetching its prices: bean-price --clear-cache Prices from a Beancount Input File \uf0c1 Generally, one uses a Beancount input file to specify the list of currencies to fetch. In order to do that, you should have Commodity directives in your input file for each of the currencies you mean to fetch prices for, like this: 2007-07-20 commodity VEA price: \"USD:google/NYSEARCA:VEA\" The \"price\" metadata should contain a list of price source strings. For example, a stock product might look like this: 2007-07-20 commodity CAD price: \"USD:google/CURRENCY:USDCAD,yahoo/USDCAD\" While a currency may have multiple target currencies it needs to get converted to: 1990-01-01 commodity GBP price: \"USD:yahoo/GBPUSD CAD:yahoo/GBPCAD CHF:yahoo/GBPCHF\" Which Assets are Fetched \uf0c1 There are many ways to compute a list of commodities with needed prices from a Beancount input file: Commodity directives. The list of all Commodity directives with \u201cprice\u201d metadata present in the file. For each of those holdings, the directive is consulted and its \" price \" metadata field is used to specify where to fetch prices from. Commodities of assets held at cost. Prices for all the holdings that were seen held at cost at a particular date. Because these holdings are held at cost, we can assume there is a corresponding time-varying price for their commodity. Converted commodities. Prices for holdings which were price-converted from some other commodity in the past (i.e., converting some cash in a currency from another). By default, the list of tickers to be fetched includes only the intersection of these three lists. This is because the most common usage of this script is to fetch missing prices for a particular date, and only the needed ones. Inactive commodities. You can use the \u201c --inactive \u201d option to fetch the entire set of prices from (1), regardless of asset holdings determined in (2) and (3). Undeclared commodities. Commodities without a corresponding \u201cCommodity\u201d directive will be ignored by default. To include the full list of commodities seen in an input file, use the \u201c --undeclared \u201d option. Clobber. Existing price directives for the same data are excluded by default, since the price is already in the file. You can use \u201c --clobber \u201d to ignore existing price directives and avoid filtering out what is fetched. Finally, you can use \u201c--all\u201d to include inactive and undeclared commodities and allow clobbering existing ones. You probably don't want to use that other than for testing. If you'd like to do some troubleshooting and print out the list of seen commodities, use the \u201c --verbose \u201d option twice, i.e., \u201c -vv \u201d. You can also just print the list of prices to be fetched with the \u201c --dry-run \u201d option, which stops short of actually fetching the missing prices. Conclusion \uf0c1 Writing Your Own Script \uf0c1 If the workflow defined by this tool does not fit your needs and you would like to cook up your own script, you should not have to start from scratch; you should be able to reuse the existing price fetching code to do that. I'm hoping to provide a few examples of such scripts in the experiments directory. For example, given an existing file it would be convenient to fetch all prices of assets every Friday in order to fill up a history of missing prices. Another example would be to fetch all price directives required in order to correctly compute investment returns in the presence of contributions and distributions. Contributions \uf0c1 If this workflow suits your needs well and you'd like to contribute some price source fetcher to Beancount, please contact the mailing-list. I'm open to including very general fetchers that have been very carefully unit-tested and used for a while.","title":"Fetching Prices in Beancount"},{"location":"fetching_prices_in_beancount.html#prices-in-beancount","text":"Martin Blais , December 2015 http://furius.ca/beancount/doc/prices","title":"Prices in Beancount"},{"location":"fetching_prices_in_beancount.html#introduction","text":"Processing a Beancount file is, by definition, constrained to the contents of the file itself. In particular, the latest prices of commodities are never fetched automatically from the internet. This is by design, so that any run of a report is deterministic and can also be run offline. No surprises. However, we do need access to price information in order to compute market values of assets. To this end, Beancount provides a Price directive which can be used to fill its in-memory price database by inserting these price points inline in the input file: 2015-11-20 price ITOT 95.46 USD 2015-11-20 price LQD 115.63 USD 2015-11-21 price USD 1.33495 CAD \u2026 Of course, you could do this manually, looking up the prices online and writing the directives yourself. But for assets which are traded publicly you can automate it, by invoking some code that will download prices and write out the directives for you. Beancount comes with some tools to help you do this. This document describes these tools.","title":"Introduction"},{"location":"fetching_prices_in_beancount.html#the-problem","text":"In the context of maintaining a Beancount file, we have a few particular needs to address. First, we cannot expect the user to always update the input file in a timely manner. This means that we have to be able to fetch not only the latest prices, but also historical prices, from the past. Beancount provides an interface to provide these prices and a few implementations (fetching from Yahoo! Finance and from Google Finance). Second, we don't want to store too many prices for holdings which aren't relevant to us at a particular point in time. The list of assets held in a file varies over time. Ideally we would want to include the list of prices for just those assets, while they are held. The Beancount price maintenance tool is able to figure out which commodities it needs to fetch prices for at a particular date. Third, we don't want to fetch the same price if it already appears in the input file. The tools detect this and skip those prices. There's also a caching mechanism that avoids redundant network calls. Finally, while we provide some basic implementations of price sources, we cannot provide such codes for all possible online sources and websites. The problem is analogous to that of importing and extracting data from various institutions. In order to address that, we provide a mechanism for extensibility , a way that you can implement your own price source fetcher in a Python module and point to it from your input file by specifying the module's name as the source for that commodity.","title":"The Problem"},{"location":"fetching_prices_in_beancount.html#the-bean-price-tool","text":"Beancount comes with a \u201cbean-price\u201d command-line tool that integrates the ideas above. By default, this script accepts a list of Beancount input filenames, and fetches prices required to compute latest market values for current positions held in accounts: bean-price /home/joe/finances/joe.beancount It is also possible to provide a list of specific price fetching jobs to run, e.g., bean-price -e USD:google/TSE:XUS CAD:mysources.morningstar/RBF1005 These jobs are run concurrently so it should be fairly fast.","title":"The \u201cbean-price\u201d Tool"},{"location":"fetching_prices_in_beancount.html#source-strings","text":"The general format of each of these \"job source strings\" is :/[^] For example: USD:beancount.prices.sources.google/NASDAQ:AAPL The \u201cquote-currency\u201d is the currency the Commodity is quoted in. For example, shares of Apple are quoted in US dollars. The \"module\" is the name of a Python module that contains a Source class which can be instantiated and which connect to a data source to extract price data. These modules are automatically imported and a Source class therein instantiated in order to pull the price from the particular online source they support. This allows you to write your own fetcher codes without having to modify this script. Your code can be placed anywhere on your Python PYTHONPATH and you should not have to modify Beancount itself for this to work. The \u201csymbol\u201d is a string that is fed to the price fetcher to lookup the currency. For example, Apple shares trade on the Nasdaq, and the corresponding symbol in the Google Finance source is \u201cNASDAQ:AAPL\u201d. Other price sources may have a different symbology, e.g., some may require the asset's CUSIP. Default implementations of price sources are provided; we provide fetchers for Yahoo! Finance or Google Finance, which cover a large universe of common public investment types (e.g. stock and some mutual funds). As a convenience, the module name is always first searched under the \" beancount.prices.sources \" package, where those implementations live. This is how, for example, in order to use the provided Yahoo! Finance data fetcher you don't have to write all of \" beancount.prices.sources.yahoo/AAPL \" but you can simply use \" yahoo/AAPL \".","title":"Source Strings"},{"location":"fetching_prices_in_beancount.html#fallback-sources","text":"In practice, fetching prices online often fails. Data sources typically only support some limited number of assets and even then, the support may vary between assets. As an example, Google Finance supports historical prices for stocks, but does not return historical prices for currency instruments (these restrictions may be more related to contractual arrangements between them and the data providers upstream than with technical limitations). To this extent, a source string may provide multiple sources for the data, separated with commas. For example: USD:google/CURRENCY:GBPUSD,yahoo/GBPUSD Each source is tried in turn, and if one fails to return a valid price, the next source is tried as a fallback. The hope is that at least one of the specified sources will work out.","title":"Fallback Sources"},{"location":"fetching_prices_in_beancount.html#inverted-prices","text":"Sometimes, prices are only available for the inverse of an instrument. This is often the case for currencies. For example, the price of Canadian dollars quoted in US dollars is provided by the USD/CAD market, which gives the price of a US dollar in Canadian dollars (the inverse). In order use this, you can prepend \" ^ \" to the instrument name to instruct the tool to compute the inverse of the fetched price: USD:google/^CURRENCY:USDCAD If a source price is to be inverted, like this, the precision could be different than what is fetched. For instance, if the price of USD/CAD is 1.32759, for the above directive to price the \u201cCAD\u201d instrument it would output this: 2015-10-28 price CAD 0.753244601119 USD By default, inverted rates will be rounded similarly to how other Price directives were rounding those numbers. As you may now, Beancount's in-memory price database works in both directions (the reciprocals of all rates are stored automatically). So if you prefer to have the output Price entries with swapped currencies instead of inverting the rate number itself, you can use the --swap-inverted option. In the previous example for the price of CAD, it would output this: 2015-10-28 price USD 1.32759 CAD","title":"Inverted Prices"},{"location":"fetching_prices_in_beancount.html#date","text":"By default, the latest prices for the assets are pulled in. You can use an option to fetch prices for a desired date in the past instead: bean-price --date=2015-02-03 \u2026 If you are using an input file to specify the list of prices to be fetched, the tool will figure out the list of assets held on the books at that time and fetch historical prices for those assets only.","title":"Date"},{"location":"fetching_prices_in_beancount.html#caching","text":"Prices are automatically cached (if current and latest, prices are cached for only a short period of time, about half an hour). This is convenient when you're having to run the script multiple times in a row for troubleshooting. You can disable the cache with an option: bean-price --no-cache You can also instruct the script to clear the cache before fetching its prices: bean-price --clear-cache","title":"Caching"},{"location":"fetching_prices_in_beancount.html#prices-from-a-beancount-input-file","text":"Generally, one uses a Beancount input file to specify the list of currencies to fetch. In order to do that, you should have Commodity directives in your input file for each of the currencies you mean to fetch prices for, like this: 2007-07-20 commodity VEA price: \"USD:google/NYSEARCA:VEA\" The \"price\" metadata should contain a list of price source strings. For example, a stock product might look like this: 2007-07-20 commodity CAD price: \"USD:google/CURRENCY:USDCAD,yahoo/USDCAD\" While a currency may have multiple target currencies it needs to get converted to: 1990-01-01 commodity GBP price: \"USD:yahoo/GBPUSD CAD:yahoo/GBPCAD CHF:yahoo/GBPCHF\"","title":"Prices from a Beancount Input File"},{"location":"fetching_prices_in_beancount.html#which-assets-are-fetched","text":"There are many ways to compute a list of commodities with needed prices from a Beancount input file: Commodity directives. The list of all Commodity directives with \u201cprice\u201d metadata present in the file. For each of those holdings, the directive is consulted and its \" price \" metadata field is used to specify where to fetch prices from. Commodities of assets held at cost. Prices for all the holdings that were seen held at cost at a particular date. Because these holdings are held at cost, we can assume there is a corresponding time-varying price for their commodity. Converted commodities. Prices for holdings which were price-converted from some other commodity in the past (i.e., converting some cash in a currency from another). By default, the list of tickers to be fetched includes only the intersection of these three lists. This is because the most common usage of this script is to fetch missing prices for a particular date, and only the needed ones. Inactive commodities. You can use the \u201c --inactive \u201d option to fetch the entire set of prices from (1), regardless of asset holdings determined in (2) and (3). Undeclared commodities. Commodities without a corresponding \u201cCommodity\u201d directive will be ignored by default. To include the full list of commodities seen in an input file, use the \u201c --undeclared \u201d option. Clobber. Existing price directives for the same data are excluded by default, since the price is already in the file. You can use \u201c --clobber \u201d to ignore existing price directives and avoid filtering out what is fetched. Finally, you can use \u201c--all\u201d to include inactive and undeclared commodities and allow clobbering existing ones. You probably don't want to use that other than for testing. If you'd like to do some troubleshooting and print out the list of seen commodities, use the \u201c --verbose \u201d option twice, i.e., \u201c -vv \u201d. You can also just print the list of prices to be fetched with the \u201c --dry-run \u201d option, which stops short of actually fetching the missing prices.","title":"Which Assets are Fetched"},{"location":"fetching_prices_in_beancount.html#conclusion","text":"","title":"Conclusion"},{"location":"fetching_prices_in_beancount.html#writing-your-own-script","text":"If the workflow defined by this tool does not fit your needs and you would like to cook up your own script, you should not have to start from scratch; you should be able to reuse the existing price fetching code to do that. I'm hoping to provide a few examples of such scripts in the experiments directory. For example, given an existing file it would be convenient to fetch all prices of assets every Friday in order to fill up a history of missing prices. Another example would be to fetch all price directives required in order to correctly compute investment returns in the presence of contributions and distributions.","title":"Writing Your Own Script"},{"location":"fetching_prices_in_beancount.html#contributions","text":"If this workflow suits your needs well and you'd like to contribute some price source fetcher to Beancount, please contact the mailing-list. I'm open to including very general fetchers that have been very carefully unit-tested and used for a while.","title":"Contributions"},{"location":"fund_accounting_with_beancount.html","text":"Fund Accounting with Beancount \uf0c1 Martin Blais , Carl Hauser, August 2014 http://furius.ca/beancount/doc/proposal-funds A discussion about how to carry out fund accounting within Beancount, various approaches, solutions and possible extensions. Motivation \uf0c1 Multiple users are attempting to solve the problem of fund accounting using command-line accounting systems, partially because this type of accounting occurs in the context of non-profit organizations that have small budgets and would prefer to use free software, and partially because the flexibility and customization required appear to be a good fit for command-line bookkeeping systems. What is Fund Accounting? \uf0c1 For example, see this thread : \u201cAnother religious duty I compute is effectively tithing (we call it Huq\u00faqu'll\u00e1h, and it's computed differently, but that's another story). In order to compute the tithe owed, I accrue 19% of every deposit to a virtual account, and then subtract from that account 19% of every needful expenditure. The total remaining at the end of the year is what I owe in tithe. This tithing account is not a real account, as it exists in no financial institution; but it is real enough as a personal duty. By using virtual account, I can track this \"side-band\" Liability, and then pay it off from an assets account when the time comes. If I report with --real I will simply see how much I've paid to this Liability; and if I report without --real I see how much Huq\u00faqu'll\u00e1h is presently owed.\u201d \u2014 John Wiegley Here\u2019s another description, as a comment from another user: [...] basically the idea that you split your financial life into separate pots called \"funds\". Each fund has its own chart of accounts (to a certain extent) and each fund obeys Assets+Liabilities+Equities == 0. This is often needed in non-profits where money given for specific purposes has to be accounted separately. The one area of non-separation is that actual asset accounts (e.g. bank accounts) and actual liability accounts (credit cards) may hold or owe money on behalf of multiple funds, so you can't use entirely separate ledger files. At our church we use a program called PowerChurchPlus for this and it works really well. My wife is now treasurer for a community music organization and facing the same problem but on such a small scale that the cost of a commercial program isn't warranted. I've seen what was posted in the ledger-cli list about non-profit accounting using C++ ledger and it just looks like it requires way too much discipline to use the tags correctly and consistently. The fund idea is much easier to explain and use (and the balance account invariant naturally enforced). So I was looking at the Beancount code to see whether I could adapt it to support fund accounting. I think the answer is \"yes and relatively easily so\". Furthermore, I think this idea has uses even for individuals: a couple of scenarios present themselves. First, I live in a community property state. My wife and I are each likely to inherit property which will be ours individually, but we also have the community property. These can each be treated as a separate fund and we will be able to report on them separately but also together for understanding our overall financial situation. Similarly, it potentially makes sense to account for retirement money with a separate fund for each person. \u2014 Carl Hauser From the PowerChurchPlus 11.5 Manual (PowerChurch, Inc. 2013): \u201cIn accounting, the term fund has a very specific meaning. An Accounting Fund is a self-balancing Chart of Accounts. [...] In accounting we need to think of a fund as a division, or sub-group of your church. Each Accounting Fund has its own assets, liabilities, equity, income, transfer and expense accounts. So when would you use an additional fund? If you have an area of your church that needs to produce its own reporting, you would need to use an additional fund. For example, if your church operates a preschool or play school, you might want to set up an additional fund to keep their finances separate from the church's. Depending on your needs, you might want to setup a separate fund for the men's or women's group. You might even setup a new fund to keep track of all fixed assets separately from the daily operating accounts.\u201d This is an interesting and apparently common problem. We will describe use cases in the rest of this section. Joint Account Management \uf0c1 I have personally used this \u201cfund accounting\u201d idea to manage a joint account that I had with my ex-wife, where we would both hold individual accounts\u2014we were both working professionals\u2014 and chip in to the joint account as needed. This section describes how I did this 1 . The accounting for the joint account was held in a separate file. Two sub-accounts were created to hold each of our \u201cportions\u201d: 2010-01-01 open Assets:BofA:Joint 2010-01-01 open Assets:BofA:Joint:Martin 2010-01-01 open Assets:BofA:Joint:Judie Transfers to the joint account were directly booked into one of the two sub-account: 2012-09-07 * \"Online Xfer Transfer from CK 345\" Assets:BofA:Joint:Judie 1000.00 USD Income:Contributions:Judie When we would incur expenses, we would reduce the asset account with two legs, one for each subaccount. We often booked them 2:1 to account for difference in income, or I just booked many of the transactions to myself (the fact that it was precisely accounted for does not imply that we weren\u2019t being generous to each other in that way): 2013-04-27 * \"Urban Vets for Grumpy\" Expenses:Medical:Cat 100.00 USD Assets:BofA:Joint:Martin -50 USD Assets:BofA:Joint:Judie -50 USD 2013-05-30 * \"Takahachi\" \"Dinner\" Expenses:Food:Restaurant 65.80 USD Assets:BofA:Joint:Judie -25.00 USD Assets:BofA:Joint:Martin It was convenient to elide one of the two amounts, as we weren\u2019t being very precise about this. Handling Multiple Funds \uf0c1 (Contributed from Carl Hauser) Here\u2019s the model used in the PowerChurchPlus system that is mentioned above (replacing the account numbers it uses with Beancount-style names). \u201cFund\u201d names are prefixed to the account names. Operations:Assets:Bank:... Endowment:Assets:Bank:... Operations:Liabilities:CreditCard:... Endowment:Liabilities:CreditCard:... Operations:Income:Pledges:2014 Operations:Expenses:Salaries:... Operations:Expenses:BuildingImprovement:... Endowment:Income:Investments:... Endowment:Expenses:BuildingImprovement:... \u2026 It is required that any transaction be balanced in every fund that it uses. For example, our Endowment fund often helps pay for things that are beyond the reach of current donations income. 2014-07-25 * \"Bill\u2019s Audio\" \"Sound system upgrade\" Endowment:Assets:Bank1:Checking 800.00 USD Operations:Assets:Bank1:Checking 200.00 USD Endowment:Expenses:BuildingImprovement:Sound -800.00 USD Operations:Expenses:BuildingImprovement:Sound -200.00 USD This represents a single check to Bill\u2019s Audio paid from assets of both the Endowment and Operations funds that are kept in the single external assets account Assets:Bank1:Checking. Note 1: An empty fund name could be allowed and the following \u201c:\u201d omitted, and in fact could be the default for people who don\u2019t want to use these features. (i.e., nothing changes if you don\u2019t use these features.) The Fund with the empty string for its name is, of course, distinct from all other Funds. Note 2: balance and pad directives are not very useful with accounts that participate in more than one Fund. Their use would require knowing the allocation of the account between the different funds and account statements from external holders (banks, e.g.) will not have this information. It might be useful to allow something like 2014-07-31 balance *:Assets:Bank1:Checking 579.39 USD as a check that things were right, but automatically correcting it with pad entries seems impossible. A balance sheet report can be run on any Fund or any combination of Funds and it will balance. You can keep track of what is owned for each different purpose easily. Transfers between funds are booked as expenses and decreases in the assets of one fund and income and increases in assets of the other. The income and expense accounts used for transfers may be generic ( Operations:Income:Transfer ) or you can use accounts set up for a particular kind of income or expense ( Endowment:Expense:BuildingImprovement:Sound ) would be fine as one leg of a transfer transaction. The account name syntax here is just one way it might work and relies on Beancount\u2019s use of five specifically-named top-level accounts. Anything to the left of the first of those could be treated as a fund name, or a different separator could be used between the fund part and the account name part. Similarly, I\u2019ve only shown single-level fund names but they might be hierarchical as well. I\u2019m not sure of the value of that, but observe that if transactions balance at the leaf-level funds they will also balance for funds higher in the hierarchy and there might be some mileage there. For John W.\u2019s Huq\u00faqu'll\u00e1h example one might set up a Fund whose liabilities were \u201cmoral obligations\u201d rather than legal ones (that seems to be the objection to simply tracking the tithes in an ordinary liability account). As income comes in (say, direct deposited in a real bank checking account), book 19% of it to the \u201cmoral obligation\u201d fund\u2019s checking account with a matching liability. When needful expenses are made, take back 19% from the \u201cmoral obligation\u201d fund\u2019s checking account and reduce the liability. No virtual postings or transactions -- everything must balance. This would work well if for example we were to have a HisRetirement fund and a HerRetirement fund -- useful to have separate for estate planning purposes -- but more commonly we want to know about our combined retirement which could be defined to be a virtual fund OurRetirement equal to the sum of HisRetirement and HerRetirement . Note that this only matters when creating reports: there is no need to do anything except normal, double-entry booking with balanced transactions in each real fund. When I say the \u201csum\u201d of two funds I mean a combination of taking the union of the contained account names, after stripping off the fund names, then summing the balances of the common accounts and keeping the balances of the others. (Balance Sheet) For reporting, one wants the capability for balance by fund and balance summed over a set of funds. I also use a report that shows a subset of funds, one per column, with corresponding account names lined up horizontally and a column at the right that is the \u201csum\u201d. When all funds are included in this latter report you get a complete picture of what you own and owe and what is set aside for different purposes, or restricted in different ways. Here\u2019s a small sample of a balance sheet for a subset of the church Funds. The terminology is a little different: what Beancount calls Equity is here Net Assets. And because using large numbers of Funds in PowerChurchPlus is not so easy, the NetAssets are actually categorized for different purposes -- this is where I think the ideas we\u2019re exploring here can really shine: if Funds are really easy to create and combine for reporting then some of the mechanisms that PCP has for divvying up assets within a fund become unnecessary. (Income Statement) For the Income and Expense report, usually I only care about expenses in one Fund at a time, but adding two funds together makes perfect sense if that\u2019s what you need to do. For me, this approach to fund accounting is appealing because it relies on and preserves the fundamental principles of double-entry bookkeeping: when transactions sum to 0 the balance sheet equation is always true. Out of this we automatically get the ability to combine any set of funds (we don\u2019t have to do anything special when entering transactions or organizing the deep structure of the accounts) and have it make at least arithmetical sense, and we don\u2019t rely on any \u201cmagic\u201d associated with renaming or tagging. I don\u2019t see how this can be so easily or neatly achieved by pushing the idea of the \u201cfunds\u201d down into the account hierarchy: funds belong above the five root accounts (Assets, Liabilities, Equity, Income and Expenses), not below them. Ideas for Implementation \uf0c1 Some random ideas for now. This needs a bit more work. If multiple redundant postings are required, the generation of these can be automated using a plugin . For instance, if a technique similar to mirror accounting is used in order to \u201csend the same dollars to multiple accounts\u201d, at least the user should not have to do this manually, which would be both tedious and prone to errors. A procedure to rename accounts upon parsing could be used, in order to merge multiple files into one. (Allowing the user to install such a mapping is an idea I\u2019ve had for a while but never implemented, though it could be implemented by a plugin filter.) We can rely on the fact that the transactions of subaccounts may be joined and summed in a parent account (despite the fact that reporting is lagging behind in that matter at the moment. It will be implemented eventually). Building off the earlier remark about doing something similar to the tag stack for Funds. What if the current tag architecture were extended to allow tags to have a value, #fund=Operations, or #fund=Endowment . Call them value-tags. You would also need to allow postings to have tags. Semantics of value-tags would be that they could only occur once for any posting, that a tag explicitly on a posting overrides the value-tag inherited from the transaction, and that an explicit tag on a transaction overrides a value from the tag-stack, and that only the last value-tag (with a particular key) in the tag-stack is applied to a transaction. This makes Funds a little less first-class than the earlier proposal to stick them in front of account names, but gets around the minor parsing difficulty previously mentioned. It suggests that opening of accounts within funds is not necessary where the previous syntax suggests that it is. The strict balancing rule for each fund in a transaction can still be implemented as a plugin. And reporting for a fund (or sum of funds) looks like: select transactions with any posting matching the desired fund (or funds) collect (and sum if necessary in the obvious way) postings associated with the fund (or funds) being reported on (A) collect (and sum in the obvious way) postings from the selected transactions not associated with the desired fund (B) Format a report with (A) as the information for the desired fund or funds and (B) as OTHER. OTHER is needed to make sure that the report balances, but could be omitted by choice. From an implementation perspective this seems more orthogonal to the current status quo, requiring even less change to existing code. It adds a new feature -- value tags and that can then be used by plugins and new reports to do what we want for fund accounting. Examples \uf0c1 (Carl) Here is an example of how I might try to handle things associated with my paycheck, which involves deferred compensation (403(b) -- extremely similar to a 401(k)) and a Flexible Spending Account (somewhat similar to an HSA which has been discussed previously on the ledger-cli group). Without Funds \uf0c1 (Carl) First, without Funds (this passes a bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions in the absence of Funds. One problem is that it distributes Gross Income directly into the 403b and FSA accounts even though it recognizes that the health insurance contribution is a reduction in salary which the 403b and FSA ought to be as well. So tracking contributions to both of those is made more difficult as well as tracking taxable income. By thinking hard we could fix this -- we would come up with Income and Expense type accounts to represent the contributions, but they would end up looking kind of silly (in my opinion) because they are entirely internal to the accounts system. See the next example for how it would look using Funds. If you stick out your tongue, rub your tummy and stand on your head you will see that the Funds-based solution is equivalent to what we would have come up with in the paragraph above in terms of the complexity of its transactions -- just as many lines are required. The advantage is primarily a mental one -- it is much easier to see what to do to be both consistent and useful. option \u201ctitle\u201d \u201cPaystub - no funds\u201d 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Assets:Deferred:R-403b 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open Assets:FSA ; This way of setting up an FSA looks pretty good. It recognizes the ; rule that the designated amount for the year is immediately available ; (in the Asset account), and that we are obliged to make contributions ; to fund it over the course of the year (the Liability account). 2014-01-01 open Liabilities:FSA 2014-01-01 ! \"Set up FSA for 2014\" Assets:FSA 2000 USD Liabilities:FSA -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Assets:Deferred:R-403b 600 USD Liabilities:FSA 75 USD Expenses:SalReduction:HealthInsurance 90 USD Income:EmplContrib:Emp1:Retirement -600 USD Assets:Deferred:R-403b 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" Expenses:Medical 25 USD Assets:FSA 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Assets:FSA Using Funds \uf0c1 (Carl) And now using Funds (uses proposed features and hence can\u2019t be checked by bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions using Funds. I can straightforwardly arrange things so that contributions to the FSA and 403b are recognized as salary reductions for income tax purposes. And I can easily see how much I have contributed to the 403b and how much my employer has contributed. See the previous example for how it would look without using Funds and which is not as accurate. What this does NOT do is track taxes ultimately owed on the 403b money. I think that is a matter of one's decision about whether to do cash-basis or accrual-basis accounting. If cash basis those taxes are not a current liability and cannot be reported as such. If accrual basis, they are a current liability and need to be recorded as such when the income is booked. For cash-basis books, we'd want the ability to report the state of affairs as if taxes were owed, but that is a matter for reporting rather than booking. We need to make sure we have enough identifiable information to automate creating those reports. I believe that taking a Fund-based accounting perspective easily does this. A problem not solved: what if your basis is different for Federal and State purposes, or even for Federal and multiple different states. Yikes! I've used the convention that the Fund name precedes the root account name. Note that with appropriate standing on one's head along with pivoting rules you can put the Fund name anywhere. Putting it first emphasizes that it identifies a set of accounts that must balance, and it makes it easy for the txn processor to guarantee this property. Accounts without a fund name in front belong to the Fund whose name is the empty string. option \"title\" \"Paystub - no Funds\" 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Expenses:SalReduction:FSA 2014-07-15 open Expenses:SalReduction:R-403b 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open FSA:Assets ; FSA fund accounts 2014-01-01 open FSA:Income:Contributions 2014-01-01 open FSA:Expenses:Medical 2014-01-01 open FSA:Expenses:ReimburseMedical 2014-01-01 open FSA:Liabilities 2014-07-15 open Retirement403b:Assets:CREF ; Retirement fund accounts 2014-07-15 open Retirement403b:Income:EmployeeContrib 2014-07-15 open Retirement403b:Income:EmployerContrib 2014-07-15 open Retirement403b:Income:EarningsGainsAndLosses ; This implements the same idea as above for the FSA, of balancing ; Assets and Liabilities at the opening, but now does it using a ; separate Fund. 2014-01-01 ! \"Set up FSA for 2014\" FSA:Assets 2000 USD FSA:Liabilities -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Expenses:SalReduction:R-403b 600 USD Retirement403b:Income:EmployeeContrib -600 USD Retirement403b:Assets:CREF 600 USD Expenses:SalReduction:FSA 75 USD FSA:Income:Contributions -75 USD FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" FSA:Expenses:Medical 25 USD FSA:Assets 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets -25 USD FSA:Expenses:ReimburseMedical Transfer Accounts Proposal \uf0c1 By Carl Hauser One problem that I\u2019ve experienced using the Fund approach is that it\u2019s a bit too easy to make mistakes when transferring money between funds, such as in the very last transaction above. Formalizing the idea of Transfer accounts can help with this. The most common mistake is to end up with something that moves assets in both accounts in the same direction -- both up or both down as in this mistaken version of the transaction in question: 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets 25 USD FSA:Expenses:ReimburseMedical This balances but isn\u2019t what we intended. Suppose we add the idea of Transfer accounts. They live at the same place in the hierarchy as Income and Expenses and like those are non-balance-sheet accounts. But they really come into play only for transactions that involve multiple funds. There is an additional rule for transactions containing Transfer accounts: the sum of the transfers must also be zero (additional in the sense that the rule about transactions balancing within each fund is still there). So to use this we set things up a little differently: 2014-07-15 open FSA:Transfer:Incoming:Contribution 2014-07-15 open FSA:Transfer:Outgoing:ReimburseMedical 2014-07-15 open Transfer:Outgoing:FSAContribution 2014-07-15 open Transfer:Incoming:ReimburseMedical The incorrect transaction is now flagged because sum of the transfers is -50 USD , not zero. 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Transfer:Incoming:ReimburseMedical FSA:Assets 25 USD FSA:Transfer:Outgoing:ReimburseMedical The paycheck transaction using transfer accounts for the FSA and the retirement account amounts might look like this (after appropriate open s of course): 2014-07-15 ! \"Emp1 Paystub - using transfer accounts\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Transfer:Outgoing:Retirement403b:SalReduction 600 USD Retirement403b:Transfer:Incoming:EmployeeContrib Retirement403b:Assets:CREF 600 USD Transfer:Outgoing:FSA:SalReduction 75 USD FSA:Transfer:Incoming:Contributions FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF Some might think that this is too complicated. Without changing the Transfer accounts idea or rule, you can simplify booking to just a single account per fund, Fund:Transfer , losing some ability for precision in reporting but without losing the ability to check correctness of transfer transactions. Account Aliases \uf0c1 Simon Michael mentions that this is related to HLedger account aliases : \u201cI think this is related to the situation where you want to view entities' finances both separately and merged. Account aliases can be another way to approximate this, as in http://hledger.org/how-to-use-account-aliases .\u201d If you find yourself culturally challenged by our modern lifestyle, perhaps you can imagine the case of roommates, although I don\u2019t like the reductionist view this association brings to my mind. \u21a9","title":"Fund Accounting with Beancount"},{"location":"fund_accounting_with_beancount.html#fund-accounting-with-beancount","text":"Martin Blais , Carl Hauser, August 2014 http://furius.ca/beancount/doc/proposal-funds A discussion about how to carry out fund accounting within Beancount, various approaches, solutions and possible extensions.","title":"Fund Accounting with Beancount"},{"location":"fund_accounting_with_beancount.html#motivation","text":"Multiple users are attempting to solve the problem of fund accounting using command-line accounting systems, partially because this type of accounting occurs in the context of non-profit organizations that have small budgets and would prefer to use free software, and partially because the flexibility and customization required appear to be a good fit for command-line bookkeeping systems.","title":"Motivation"},{"location":"fund_accounting_with_beancount.html#what-is-fund-accounting","text":"For example, see this thread : \u201cAnother religious duty I compute is effectively tithing (we call it Huq\u00faqu'll\u00e1h, and it's computed differently, but that's another story). In order to compute the tithe owed, I accrue 19% of every deposit to a virtual account, and then subtract from that account 19% of every needful expenditure. The total remaining at the end of the year is what I owe in tithe. This tithing account is not a real account, as it exists in no financial institution; but it is real enough as a personal duty. By using virtual account, I can track this \"side-band\" Liability, and then pay it off from an assets account when the time comes. If I report with --real I will simply see how much I've paid to this Liability; and if I report without --real I see how much Huq\u00faqu'll\u00e1h is presently owed.\u201d \u2014 John Wiegley Here\u2019s another description, as a comment from another user: [...] basically the idea that you split your financial life into separate pots called \"funds\". Each fund has its own chart of accounts (to a certain extent) and each fund obeys Assets+Liabilities+Equities == 0. This is often needed in non-profits where money given for specific purposes has to be accounted separately. The one area of non-separation is that actual asset accounts (e.g. bank accounts) and actual liability accounts (credit cards) may hold or owe money on behalf of multiple funds, so you can't use entirely separate ledger files. At our church we use a program called PowerChurchPlus for this and it works really well. My wife is now treasurer for a community music organization and facing the same problem but on such a small scale that the cost of a commercial program isn't warranted. I've seen what was posted in the ledger-cli list about non-profit accounting using C++ ledger and it just looks like it requires way too much discipline to use the tags correctly and consistently. The fund idea is much easier to explain and use (and the balance account invariant naturally enforced). So I was looking at the Beancount code to see whether I could adapt it to support fund accounting. I think the answer is \"yes and relatively easily so\". Furthermore, I think this idea has uses even for individuals: a couple of scenarios present themselves. First, I live in a community property state. My wife and I are each likely to inherit property which will be ours individually, but we also have the community property. These can each be treated as a separate fund and we will be able to report on them separately but also together for understanding our overall financial situation. Similarly, it potentially makes sense to account for retirement money with a separate fund for each person. \u2014 Carl Hauser From the PowerChurchPlus 11.5 Manual (PowerChurch, Inc. 2013): \u201cIn accounting, the term fund has a very specific meaning. An Accounting Fund is a self-balancing Chart of Accounts. [...] In accounting we need to think of a fund as a division, or sub-group of your church. Each Accounting Fund has its own assets, liabilities, equity, income, transfer and expense accounts. So when would you use an additional fund? If you have an area of your church that needs to produce its own reporting, you would need to use an additional fund. For example, if your church operates a preschool or play school, you might want to set up an additional fund to keep their finances separate from the church's. Depending on your needs, you might want to setup a separate fund for the men's or women's group. You might even setup a new fund to keep track of all fixed assets separately from the daily operating accounts.\u201d This is an interesting and apparently common problem. We will describe use cases in the rest of this section.","title":"What is Fund Accounting?"},{"location":"fund_accounting_with_beancount.html#joint-account-management","text":"I have personally used this \u201cfund accounting\u201d idea to manage a joint account that I had with my ex-wife, where we would both hold individual accounts\u2014we were both working professionals\u2014 and chip in to the joint account as needed. This section describes how I did this 1 . The accounting for the joint account was held in a separate file. Two sub-accounts were created to hold each of our \u201cportions\u201d: 2010-01-01 open Assets:BofA:Joint 2010-01-01 open Assets:BofA:Joint:Martin 2010-01-01 open Assets:BofA:Joint:Judie Transfers to the joint account were directly booked into one of the two sub-account: 2012-09-07 * \"Online Xfer Transfer from CK 345\" Assets:BofA:Joint:Judie 1000.00 USD Income:Contributions:Judie When we would incur expenses, we would reduce the asset account with two legs, one for each subaccount. We often booked them 2:1 to account for difference in income, or I just booked many of the transactions to myself (the fact that it was precisely accounted for does not imply that we weren\u2019t being generous to each other in that way): 2013-04-27 * \"Urban Vets for Grumpy\" Expenses:Medical:Cat 100.00 USD Assets:BofA:Joint:Martin -50 USD Assets:BofA:Joint:Judie -50 USD 2013-05-30 * \"Takahachi\" \"Dinner\" Expenses:Food:Restaurant 65.80 USD Assets:BofA:Joint:Judie -25.00 USD Assets:BofA:Joint:Martin It was convenient to elide one of the two amounts, as we weren\u2019t being very precise about this.","title":"Joint Account Management"},{"location":"fund_accounting_with_beancount.html#handling-multiple-funds","text":"(Contributed from Carl Hauser) Here\u2019s the model used in the PowerChurchPlus system that is mentioned above (replacing the account numbers it uses with Beancount-style names). \u201cFund\u201d names are prefixed to the account names. Operations:Assets:Bank:... Endowment:Assets:Bank:... Operations:Liabilities:CreditCard:... Endowment:Liabilities:CreditCard:... Operations:Income:Pledges:2014 Operations:Expenses:Salaries:... Operations:Expenses:BuildingImprovement:... Endowment:Income:Investments:... Endowment:Expenses:BuildingImprovement:... \u2026 It is required that any transaction be balanced in every fund that it uses. For example, our Endowment fund often helps pay for things that are beyond the reach of current donations income. 2014-07-25 * \"Bill\u2019s Audio\" \"Sound system upgrade\" Endowment:Assets:Bank1:Checking 800.00 USD Operations:Assets:Bank1:Checking 200.00 USD Endowment:Expenses:BuildingImprovement:Sound -800.00 USD Operations:Expenses:BuildingImprovement:Sound -200.00 USD This represents a single check to Bill\u2019s Audio paid from assets of both the Endowment and Operations funds that are kept in the single external assets account Assets:Bank1:Checking. Note 1: An empty fund name could be allowed and the following \u201c:\u201d omitted, and in fact could be the default for people who don\u2019t want to use these features. (i.e., nothing changes if you don\u2019t use these features.) The Fund with the empty string for its name is, of course, distinct from all other Funds. Note 2: balance and pad directives are not very useful with accounts that participate in more than one Fund. Their use would require knowing the allocation of the account between the different funds and account statements from external holders (banks, e.g.) will not have this information. It might be useful to allow something like 2014-07-31 balance *:Assets:Bank1:Checking 579.39 USD as a check that things were right, but automatically correcting it with pad entries seems impossible. A balance sheet report can be run on any Fund or any combination of Funds and it will balance. You can keep track of what is owned for each different purpose easily. Transfers between funds are booked as expenses and decreases in the assets of one fund and income and increases in assets of the other. The income and expense accounts used for transfers may be generic ( Operations:Income:Transfer ) or you can use accounts set up for a particular kind of income or expense ( Endowment:Expense:BuildingImprovement:Sound ) would be fine as one leg of a transfer transaction. The account name syntax here is just one way it might work and relies on Beancount\u2019s use of five specifically-named top-level accounts. Anything to the left of the first of those could be treated as a fund name, or a different separator could be used between the fund part and the account name part. Similarly, I\u2019ve only shown single-level fund names but they might be hierarchical as well. I\u2019m not sure of the value of that, but observe that if transactions balance at the leaf-level funds they will also balance for funds higher in the hierarchy and there might be some mileage there. For John W.\u2019s Huq\u00faqu'll\u00e1h example one might set up a Fund whose liabilities were \u201cmoral obligations\u201d rather than legal ones (that seems to be the objection to simply tracking the tithes in an ordinary liability account). As income comes in (say, direct deposited in a real bank checking account), book 19% of it to the \u201cmoral obligation\u201d fund\u2019s checking account with a matching liability. When needful expenses are made, take back 19% from the \u201cmoral obligation\u201d fund\u2019s checking account and reduce the liability. No virtual postings or transactions -- everything must balance. This would work well if for example we were to have a HisRetirement fund and a HerRetirement fund -- useful to have separate for estate planning purposes -- but more commonly we want to know about our combined retirement which could be defined to be a virtual fund OurRetirement equal to the sum of HisRetirement and HerRetirement . Note that this only matters when creating reports: there is no need to do anything except normal, double-entry booking with balanced transactions in each real fund. When I say the \u201csum\u201d of two funds I mean a combination of taking the union of the contained account names, after stripping off the fund names, then summing the balances of the common accounts and keeping the balances of the others. (Balance Sheet) For reporting, one wants the capability for balance by fund and balance summed over a set of funds. I also use a report that shows a subset of funds, one per column, with corresponding account names lined up horizontally and a column at the right that is the \u201csum\u201d. When all funds are included in this latter report you get a complete picture of what you own and owe and what is set aside for different purposes, or restricted in different ways. Here\u2019s a small sample of a balance sheet for a subset of the church Funds. The terminology is a little different: what Beancount calls Equity is here Net Assets. And because using large numbers of Funds in PowerChurchPlus is not so easy, the NetAssets are actually categorized for different purposes -- this is where I think the ideas we\u2019re exploring here can really shine: if Funds are really easy to create and combine for reporting then some of the mechanisms that PCP has for divvying up assets within a fund become unnecessary. (Income Statement) For the Income and Expense report, usually I only care about expenses in one Fund at a time, but adding two funds together makes perfect sense if that\u2019s what you need to do. For me, this approach to fund accounting is appealing because it relies on and preserves the fundamental principles of double-entry bookkeeping: when transactions sum to 0 the balance sheet equation is always true. Out of this we automatically get the ability to combine any set of funds (we don\u2019t have to do anything special when entering transactions or organizing the deep structure of the accounts) and have it make at least arithmetical sense, and we don\u2019t rely on any \u201cmagic\u201d associated with renaming or tagging. I don\u2019t see how this can be so easily or neatly achieved by pushing the idea of the \u201cfunds\u201d down into the account hierarchy: funds belong above the five root accounts (Assets, Liabilities, Equity, Income and Expenses), not below them.","title":"Handling Multiple Funds"},{"location":"fund_accounting_with_beancount.html#ideas-for-implementation","text":"Some random ideas for now. This needs a bit more work. If multiple redundant postings are required, the generation of these can be automated using a plugin . For instance, if a technique similar to mirror accounting is used in order to \u201csend the same dollars to multiple accounts\u201d, at least the user should not have to do this manually, which would be both tedious and prone to errors. A procedure to rename accounts upon parsing could be used, in order to merge multiple files into one. (Allowing the user to install such a mapping is an idea I\u2019ve had for a while but never implemented, though it could be implemented by a plugin filter.) We can rely on the fact that the transactions of subaccounts may be joined and summed in a parent account (despite the fact that reporting is lagging behind in that matter at the moment. It will be implemented eventually). Building off the earlier remark about doing something similar to the tag stack for Funds. What if the current tag architecture were extended to allow tags to have a value, #fund=Operations, or #fund=Endowment . Call them value-tags. You would also need to allow postings to have tags. Semantics of value-tags would be that they could only occur once for any posting, that a tag explicitly on a posting overrides the value-tag inherited from the transaction, and that an explicit tag on a transaction overrides a value from the tag-stack, and that only the last value-tag (with a particular key) in the tag-stack is applied to a transaction. This makes Funds a little less first-class than the earlier proposal to stick them in front of account names, but gets around the minor parsing difficulty previously mentioned. It suggests that opening of accounts within funds is not necessary where the previous syntax suggests that it is. The strict balancing rule for each fund in a transaction can still be implemented as a plugin. And reporting for a fund (or sum of funds) looks like: select transactions with any posting matching the desired fund (or funds) collect (and sum if necessary in the obvious way) postings associated with the fund (or funds) being reported on (A) collect (and sum in the obvious way) postings from the selected transactions not associated with the desired fund (B) Format a report with (A) as the information for the desired fund or funds and (B) as OTHER. OTHER is needed to make sure that the report balances, but could be omitted by choice. From an implementation perspective this seems more orthogonal to the current status quo, requiring even less change to existing code. It adds a new feature -- value tags and that can then be used by plugins and new reports to do what we want for fund accounting.","title":"Ideas for Implementation"},{"location":"fund_accounting_with_beancount.html#examples","text":"(Carl) Here is an example of how I might try to handle things associated with my paycheck, which involves deferred compensation (403(b) -- extremely similar to a 401(k)) and a Flexible Spending Account (somewhat similar to an HSA which has been discussed previously on the ledger-cli group).","title":"Examples"},{"location":"fund_accounting_with_beancount.html#without-funds","text":"(Carl) First, without Funds (this passes a bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions in the absence of Funds. One problem is that it distributes Gross Income directly into the 403b and FSA accounts even though it recognizes that the health insurance contribution is a reduction in salary which the 403b and FSA ought to be as well. So tracking contributions to both of those is made more difficult as well as tracking taxable income. By thinking hard we could fix this -- we would come up with Income and Expense type accounts to represent the contributions, but they would end up looking kind of silly (in my opinion) because they are entirely internal to the accounts system. See the next example for how it would look using Funds. If you stick out your tongue, rub your tummy and stand on your head you will see that the Funds-based solution is equivalent to what we would have come up with in the paragraph above in terms of the complexity of its transactions -- just as many lines are required. The advantage is primarily a mental one -- it is much easier to see what to do to be both consistent and useful. option \u201ctitle\u201d \u201cPaystub - no funds\u201d 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Assets:Deferred:R-403b 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open Assets:FSA ; This way of setting up an FSA looks pretty good. It recognizes the ; rule that the designated amount for the year is immediately available ; (in the Asset account), and that we are obliged to make contributions ; to fund it over the course of the year (the Liability account). 2014-01-01 open Liabilities:FSA 2014-01-01 ! \"Set up FSA for 2014\" Assets:FSA 2000 USD Liabilities:FSA -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Assets:Deferred:R-403b 600 USD Liabilities:FSA 75 USD Expenses:SalReduction:HealthInsurance 90 USD Income:EmplContrib:Emp1:Retirement -600 USD Assets:Deferred:R-403b 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" Expenses:Medical 25 USD Assets:FSA 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Assets:FSA","title":"Without Funds"},{"location":"fund_accounting_with_beancount.html#using-funds","text":"(Carl) And now using Funds (uses proposed features and hence can\u2019t be checked by bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions using Funds. I can straightforwardly arrange things so that contributions to the FSA and 403b are recognized as salary reductions for income tax purposes. And I can easily see how much I have contributed to the 403b and how much my employer has contributed. See the previous example for how it would look without using Funds and which is not as accurate. What this does NOT do is track taxes ultimately owed on the 403b money. I think that is a matter of one's decision about whether to do cash-basis or accrual-basis accounting. If cash basis those taxes are not a current liability and cannot be reported as such. If accrual basis, they are a current liability and need to be recorded as such when the income is booked. For cash-basis books, we'd want the ability to report the state of affairs as if taxes were owed, but that is a matter for reporting rather than booking. We need to make sure we have enough identifiable information to automate creating those reports. I believe that taking a Fund-based accounting perspective easily does this. A problem not solved: what if your basis is different for Federal and State purposes, or even for Federal and multiple different states. Yikes! I've used the convention that the Fund name precedes the root account name. Note that with appropriate standing on one's head along with pivoting rules you can put the Fund name anywhere. Putting it first emphasizes that it identifies a set of accounts that must balance, and it makes it easy for the txn processor to guarantee this property. Accounts without a fund name in front belong to the Fund whose name is the empty string. option \"title\" \"Paystub - no Funds\" 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Expenses:SalReduction:FSA 2014-07-15 open Expenses:SalReduction:R-403b 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open FSA:Assets ; FSA fund accounts 2014-01-01 open FSA:Income:Contributions 2014-01-01 open FSA:Expenses:Medical 2014-01-01 open FSA:Expenses:ReimburseMedical 2014-01-01 open FSA:Liabilities 2014-07-15 open Retirement403b:Assets:CREF ; Retirement fund accounts 2014-07-15 open Retirement403b:Income:EmployeeContrib 2014-07-15 open Retirement403b:Income:EmployerContrib 2014-07-15 open Retirement403b:Income:EarningsGainsAndLosses ; This implements the same idea as above for the FSA, of balancing ; Assets and Liabilities at the opening, but now does it using a ; separate Fund. 2014-01-01 ! \"Set up FSA for 2014\" FSA:Assets 2000 USD FSA:Liabilities -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Expenses:SalReduction:R-403b 600 USD Retirement403b:Income:EmployeeContrib -600 USD Retirement403b:Assets:CREF 600 USD Expenses:SalReduction:FSA 75 USD FSA:Income:Contributions -75 USD FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" FSA:Expenses:Medical 25 USD FSA:Assets 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets -25 USD FSA:Expenses:ReimburseMedical","title":"Using Funds"},{"location":"fund_accounting_with_beancount.html#transfer-accounts-proposal","text":"By Carl Hauser One problem that I\u2019ve experienced using the Fund approach is that it\u2019s a bit too easy to make mistakes when transferring money between funds, such as in the very last transaction above. Formalizing the idea of Transfer accounts can help with this. The most common mistake is to end up with something that moves assets in both accounts in the same direction -- both up or both down as in this mistaken version of the transaction in question: 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets 25 USD FSA:Expenses:ReimburseMedical This balances but isn\u2019t what we intended. Suppose we add the idea of Transfer accounts. They live at the same place in the hierarchy as Income and Expenses and like those are non-balance-sheet accounts. But they really come into play only for transactions that involve multiple funds. There is an additional rule for transactions containing Transfer accounts: the sum of the transfers must also be zero (additional in the sense that the rule about transactions balancing within each fund is still there). So to use this we set things up a little differently: 2014-07-15 open FSA:Transfer:Incoming:Contribution 2014-07-15 open FSA:Transfer:Outgoing:ReimburseMedical 2014-07-15 open Transfer:Outgoing:FSAContribution 2014-07-15 open Transfer:Incoming:ReimburseMedical The incorrect transaction is now flagged because sum of the transfers is -50 USD , not zero. 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Transfer:Incoming:ReimburseMedical FSA:Assets 25 USD FSA:Transfer:Outgoing:ReimburseMedical The paycheck transaction using transfer accounts for the FSA and the retirement account amounts might look like this (after appropriate open s of course): 2014-07-15 ! \"Emp1 Paystub - using transfer accounts\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Transfer:Outgoing:Retirement403b:SalReduction 600 USD Retirement403b:Transfer:Incoming:EmployeeContrib Retirement403b:Assets:CREF 600 USD Transfer:Outgoing:FSA:SalReduction 75 USD FSA:Transfer:Incoming:Contributions FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF Some might think that this is too complicated. Without changing the Transfer accounts idea or rule, you can simplify booking to just a single account per fund, Fund:Transfer , losing some ability for precision in reporting but without losing the ability to check correctness of transfer transactions.","title":"Transfer Accounts Proposal"},{"location":"fund_accounting_with_beancount.html#account-aliases","text":"Simon Michael mentions that this is related to HLedger account aliases : \u201cI think this is related to the situation where you want to view entities' finances both separately and merged. Account aliases can be another way to approximate this, as in http://hledger.org/how-to-use-account-aliases .\u201d If you find yourself culturally challenged by our modern lifestyle, perhaps you can imagine the case of roommates, although I don\u2019t like the reductionist view this association brings to my mind. \u21a9","title":"Account Aliases"},{"location":"getting_started_with_beancount.html","text":"Getting Started with Beancount \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/getting-started Introduction \uf0c1 This document is a gentle guide to creating your first Beancount file, initializing it with some options, some guidelines for how to organize your file, and instructions for declaring accounts and making sure their initial balance does not raise errors. It also contains some material on configuring the Emacs text editor, if you use that. You will probably want to have read some of the User\u2019s Manual first in order to familiarize yourself with the syntax and kinds of available directives, or move on to the Cookbook if you\u2019ve already setup a file or know how to do that. If you\u2019re familiar with Ledger, you may want to read up on the differences between Ledger and Beancount first. Editor Support \uf0c1 Beancount ledgers are simple text files. You can use any text editor to compose your input file. However, a good text editor which understands enough of the Beancount syntax to offer focused facilities like syntax highlighting, autocompletion, and automatic indentation highly has the potential to greatly increase productivity in compiling and maintaining your ledger. Emacs \uf0c1 Support for editing Beancount ledger files in Emacs was traditionally distributed with Beancount. It now lives as its own project in this Github repository . Vim \uf0c1 Support for editing Beancount ledger files in Vim has been implemented by Nathan Grigg and is available in this Github repository . Sublime \uf0c1 Support for editing with Sublime has been contributed by Martin Andreas Andersen and is available in this github repository or as a Sublime package here . VSCode \uf0c1 There are a number of plugins for working with Beancount text files including VSCode-Beancount by Lencerf. Creating your First Input File \uf0c1 To get started, let\u2019s create a minimal input file with two accounts and a single transaction. Enter or copy the following input to a text file: 2014-01-01 open Assets:Checking 2014-01-01 open Equity:Opening-Balances 2014-01-02 * \"Deposit\" Assets:Checking 100.00 USD Equity:Opening-Balances Brief Syntax Overview \uf0c1 A few notes and an ultra brief overview of the Beancount syntax: Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Each component of an account name must begin with a capital letter or number. Description strings must be quoted, like this: \"AMEX PMNT\" . Dates are only parsed in ISO8601 format, that is, YYYY-MM-DD. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. For a complete description of the syntax, visit the User\u2019s Manual . Validating your File \uf0c1 The purpose of Beancount is to produce reports from your input file (either on the console or serve via its web interface). However, there is a tool that you can use to simply load its contents and make some validation checks on it, to ensure that your input does not contain errors. Beancount can be quite strict; this is a tool that you use while you\u2019re entering your data to ensure that your input file is legal. The tool is called \u201cbean-check\u201d and you invoke it like this: bean-check /path/to/your/file.beancount Try it now on the file you just created from the previous section. It should exit with no output. If there are errors, they will be printed on the console. The errors are printed out in a format that Emacs recognizes by default, so you can use Emacs\u2019 next-error and previous-error built-in functions to move the cursor to the location of the problem. Viewing the Web Interface \uf0c1 A convenient way to view reports is to bring up the \u201cbean-web\u201d tool on your input file. Try it: bean-web /path/to/your/file.beancount You can then point a web browser to http://localhost:8080 and click your way around the various reports generated by Beancount. You can then modify the input file and reload the web page your browser is pointing to\u2014bean-web will automatically reload the file contents. At this point, you should probably read some of the Language Syntax document. How to Organize your File \uf0c1 In this section we provide general guidelines for how to organize your file. This assumes you\u2019ve read the Language Syntax document. Preamble to your Input File \uf0c1 I recommend that you begin with just a single file 1 . My file has a header that tells Emacs what mode to open the file with, followed by some common options: ;; -*- mode: beancount; coding: utf-8; fill-column: 400; -*- option \"title\" \"My Personal Ledger\" option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" The title option is used in reports. The list of \u201coperating currencies\u201d identify those commodities which you use most commonly as \u201ccurrencies\u201d and which warrant rendering in their own dedicated columns in reports (this declaration has no other effect on the behavior of any of the calculations). Sections & Declaring Accounts \uf0c1 I like to organize my input file in sections that correspond to each real-world account. Each section defines all the accounts related to this real-world account by using an Open directive. For example, this is a checking account: 2007-02-01 open Assets:US:BofA:Savings USD 2007-02-01 open Income:US:BofA:Savings:Interest USD I like to declare the currency constraints as much as possible, to avoid mistakes. Also, note how I declare an income account specific to this account. This helps break down income in reporting for taxes, as you will likely receive a tax document in relation to that specific account\u2019s income (in the US this would be a 1099-INT form produced by your bank). Here\u2019s what the opening accounts might look like for an investment account: 2012-03-01 open Assets:US:Etrade:Main:Cash USD 2012-03-01 open Assets:US:Etrade:Main:ITOT ITOT 2012-03-01 open Assets:US:Etrade:Main:IXUS IXUS 2012-03-01 open Assets:US:Etrade:Main:IEFA IEFA 2012-03-01 open Income:US:Etrade:Main:Interest USD 2012-03-01 open Income:US:Etrade:Main:PnL USD 2012-03-01 open Income:US:Etrade:Main:Dividend USD 2012-03-01 open Income:US:Etrade:Main:DividendNoTax USD The point is that all these accounts are related somehow. The various sections of the cookbook will describe the set of accounts suggested to create for each section. Not all sections have to be that way. For example, I have the following sections: Eternal accounts. I have a section at the top dedicated to contain special and \u201ceternal\u201d accounts, such as payables and receivables. Daybook. I have a \u201cdaybook\u201d section at the bottom that contains all cash expenses, in chronological order. Expense accounts. All my expenses accounts (categories) are defined in their own section. Employers. For each employer I\u2019ve defined a section where I put the entries for their direct deposits, and track vacations, stock vesting and other job-related transactions. Taxes. I have a section for taxes, organized by taxation year. You can organize it any way you like, because Beancount doesn\u2019t care about the ordering of declarations. Closing Accounts \uf0c1 If a real-world account has closed, or is never going to have any more transactions posted to it, you can declare it \u201cclosed\u201d at a particular date by using a Close directive: ; Moving to another bank. 2013-06-13 close Assets:US:BofA:Savings This tells Beancount not to show the account in reports that don\u2019t include any date where it was active. It also avoids errors by triggering an error if you do try to post to it at a later date. De-duping \uf0c1 One problem that will occur frequently is that once you have some sort of code or process set up to automatically extract postings from downloaded files, you will end up importing postings which provide two separate sides of the same transaction. An example is the payment of a credit card balance via a transfer from a checking account. If you download the transactions for your checking account, you will extract something like this: 2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" Assets:CA:BofA:Checking -923.24 USD The credit card download will yield you this: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Many times, transactions from these accounts need to be booked to an expense account, but in this case, these are two separate legs of the same transaction: a transfer. When you import one of these, you normally look for the other side and merge them together: ;2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Assets:CA:BofA:Checking -923.24 USD I often leave one of the description lines in comments\u2014just my choice, Beancount ignores it. Also note that I had to choose one of the two dates. I just choose the one I prefer, as long as it does not break any balance assertion. In the case that you would forget to merge those two imported transactions, worry not! That\u2019s what balance assertions are for. Regularly place a balance assertion in either of these accounts, e.g., every time you import, and you will get a nice error if you end up entering the transaction twice. This is pretty common and after a while it becomes second nature to interpret that compiler error and fix it in seconds. Finally, when I know I import just one side of these, I select the other account manually and I mark the posting I know will be imported later with a flag, which tells me I haven\u2019t de-duped this transaction yet: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD ! Assets:CA:BofA:Checking Later on, when I import the checking account\u2019s transactions and go fishing for the other side of this payment, I will find this and get a good feeling that the world is operating as it should. (If you\u2019re interested in more of a discussion around de-duplicating and merging transactions, see this feature proposal . Also, you might be interested in the \u201ceffective_date\u201d plugin external contribution, which splits transactions in two.) Which Side? \uf0c1 So if you organize your account in sections the way I suggest above, which section of the file should you leave such \u201cmerged\u201d transactions in, that is, transactions that involve two separate accounts? Well, it\u2019s your call. For example, in the case of a transfer between two accounts organized such that they have their own dedicated sections, it would be nice to be able to leave both transactions there so that when you edit your input file you see them in either section, but unfortunately, the transaction must occur in only one place in your document. You have to choose one. Personally I\u2019m a little careless about being consistent which of the section I choose to leave the transaction in; sometimes I choose one section of my input file, or that of the other account, for the same pair of accounts. It hasn\u2019t been a problem, as I use Emacs and i-search liberally which makes it easy to dig around my gigantic input file. If you want to keep your input more tidy and organized, you could come up with a rule for yourself, e.g. \u201ccredit card payments are always left in the paying account\u2019s section, not in the credit card account\u2019s section\u201d, or perhaps you could leave the transaction in both sections and comment one out 2 . Padding \uf0c1 If you\u2019re just starting out\u2014and you probably are if you\u2019re reading this\u2014you will have no historical data. This means that the balances of your Assets and Liabilities accounts in Beancount will all be zero. But the first thing you should want to do after defining some accounts is establish a balance sheet and bring those amounts to their actual current value. Let\u2019s take your checking account as an example, say you opened it a while back. You don\u2019t remember exactly when, so let\u2019s use an approximate date: 2000-05-28 open Assets:CA:BofA:Checking USD The next thing you do is look up your current balance and put a balance assertion for the corresponding amount: 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Running Beancount on just this will correctly produce an error because Beancount assumes an implicit balance assertion of \u201cempty\u201d at the time you open an account. You will have to \u201cpad\u201d your account to today\u2019s balance by inserting a balance adjustment at some point in time between the opening and the balance, against some equity account, which is an arbitrary place to book \u201cwhere you received the initial balance from.\u201d For this purpose, this is usually the \u201c Equity:Opening-Balances \u201d account. So let\u2019s include this padding transaction and recap what we have so far: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD From here onwards, you would start adding entries reflecting everything that happened after 7/1. However, what if you wanted to go back in time? It is perfectly reasonable that once you\u2019ve got your chart-of-accounts set up you might want to fill in the missing history until at least the beginning of this year. Let\u2019s assume you had a single transaction in June 2014, and let\u2019s add it: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now the balance assertion fails! You would need to adjust the initialization entry to fix this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1956.35 USD Assets:CA:BofA:Checking 1956.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now this works. So basically, every single time you insert an entry in the past, you would have to adjust the balance. Isn\u2019t this annoying? Well, yes. Fortunately, we can provide some help: you can use a Pad directive to replace and automatically synthesize the balance adjustment to match the next balance check, like this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 pad Assets:CA:BofA:Checking Equity:Opening-Balances 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Note that this is only needed for balance sheet accounts (Assets and Liabilities) because we don\u2019t care about the initial balances of the Income and Expenses accounts, we only care about their transitional value (the changes they post during a period). For example, it makes no sense to bring up the Expenses:Restaurant account to the sum total value of all the costs of the meals you consumed since you were born. So you will probably want to get started with Open & Pad directives for each Assets and Liabilities accounts. What\u2019s Next? \uf0c1 At this point you will probably move onwards to the Cookbook , or read the User\u2019s Manual if you haven\u2019t already done that. It is tempting to want to break down a large file into many smaller ones, but especially at first, the convenience of having everything in a single place is great. \u21a9 Some people have suggested that Beancount automatically detect duplicated transactions based on a heuristic and automatically ignore (remove) one of the two, but this has not been tried out yet. In particular, this would lend itself well to organizing transactions not just per section, but in separate files, i.e., all files would contain all the transactions for the accounts they represent. If you\u2019re interested in adding this feature, you could easily implement this as a plugin, without disrupting the rest of the system. \u21a9","title":"Getting Started with Beancount"},{"location":"getting_started_with_beancount.html#getting-started-with-beancount","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/getting-started","title":"Getting Started with Beancount"},{"location":"getting_started_with_beancount.html#introduction","text":"This document is a gentle guide to creating your first Beancount file, initializing it with some options, some guidelines for how to organize your file, and instructions for declaring accounts and making sure their initial balance does not raise errors. It also contains some material on configuring the Emacs text editor, if you use that. You will probably want to have read some of the User\u2019s Manual first in order to familiarize yourself with the syntax and kinds of available directives, or move on to the Cookbook if you\u2019ve already setup a file or know how to do that. If you\u2019re familiar with Ledger, you may want to read up on the differences between Ledger and Beancount first.","title":"Introduction"},{"location":"getting_started_with_beancount.html#editor-support","text":"Beancount ledgers are simple text files. You can use any text editor to compose your input file. However, a good text editor which understands enough of the Beancount syntax to offer focused facilities like syntax highlighting, autocompletion, and automatic indentation highly has the potential to greatly increase productivity in compiling and maintaining your ledger.","title":"Editor Support"},{"location":"getting_started_with_beancount.html#emacs","text":"Support for editing Beancount ledger files in Emacs was traditionally distributed with Beancount. It now lives as its own project in this Github repository .","title":"Emacs"},{"location":"getting_started_with_beancount.html#vim","text":"Support for editing Beancount ledger files in Vim has been implemented by Nathan Grigg and is available in this Github repository .","title":"Vim"},{"location":"getting_started_with_beancount.html#sublime","text":"Support for editing with Sublime has been contributed by Martin Andreas Andersen and is available in this github repository or as a Sublime package here .","title":"Sublime"},{"location":"getting_started_with_beancount.html#vscode","text":"There are a number of plugins for working with Beancount text files including VSCode-Beancount by Lencerf.","title":"VSCode"},{"location":"getting_started_with_beancount.html#creating-your-first-input-file","text":"To get started, let\u2019s create a minimal input file with two accounts and a single transaction. Enter or copy the following input to a text file: 2014-01-01 open Assets:Checking 2014-01-01 open Equity:Opening-Balances 2014-01-02 * \"Deposit\" Assets:Checking 100.00 USD Equity:Opening-Balances","title":"Creating your First Input File"},{"location":"getting_started_with_beancount.html#brief-syntax-overview","text":"A few notes and an ultra brief overview of the Beancount syntax: Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Each component of an account name must begin with a capital letter or number. Description strings must be quoted, like this: \"AMEX PMNT\" . Dates are only parsed in ISO8601 format, that is, YYYY-MM-DD. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. For a complete description of the syntax, visit the User\u2019s Manual .","title":"Brief Syntax Overview"},{"location":"getting_started_with_beancount.html#validating-your-file","text":"The purpose of Beancount is to produce reports from your input file (either on the console or serve via its web interface). However, there is a tool that you can use to simply load its contents and make some validation checks on it, to ensure that your input does not contain errors. Beancount can be quite strict; this is a tool that you use while you\u2019re entering your data to ensure that your input file is legal. The tool is called \u201cbean-check\u201d and you invoke it like this: bean-check /path/to/your/file.beancount Try it now on the file you just created from the previous section. It should exit with no output. If there are errors, they will be printed on the console. The errors are printed out in a format that Emacs recognizes by default, so you can use Emacs\u2019 next-error and previous-error built-in functions to move the cursor to the location of the problem.","title":"Validating your File"},{"location":"getting_started_with_beancount.html#viewing-the-web-interface","text":"A convenient way to view reports is to bring up the \u201cbean-web\u201d tool on your input file. Try it: bean-web /path/to/your/file.beancount You can then point a web browser to http://localhost:8080 and click your way around the various reports generated by Beancount. You can then modify the input file and reload the web page your browser is pointing to\u2014bean-web will automatically reload the file contents. At this point, you should probably read some of the Language Syntax document.","title":"Viewing the Web Interface"},{"location":"getting_started_with_beancount.html#how-to-organize-your-file","text":"In this section we provide general guidelines for how to organize your file. This assumes you\u2019ve read the Language Syntax document.","title":"How to Organize your File"},{"location":"getting_started_with_beancount.html#preamble-to-your-input-file","text":"I recommend that you begin with just a single file 1 . My file has a header that tells Emacs what mode to open the file with, followed by some common options: ;; -*- mode: beancount; coding: utf-8; fill-column: 400; -*- option \"title\" \"My Personal Ledger\" option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" The title option is used in reports. The list of \u201coperating currencies\u201d identify those commodities which you use most commonly as \u201ccurrencies\u201d and which warrant rendering in their own dedicated columns in reports (this declaration has no other effect on the behavior of any of the calculations).","title":"Preamble to your Input File"},{"location":"getting_started_with_beancount.html#sections-declaring-accounts","text":"I like to organize my input file in sections that correspond to each real-world account. Each section defines all the accounts related to this real-world account by using an Open directive. For example, this is a checking account: 2007-02-01 open Assets:US:BofA:Savings USD 2007-02-01 open Income:US:BofA:Savings:Interest USD I like to declare the currency constraints as much as possible, to avoid mistakes. Also, note how I declare an income account specific to this account. This helps break down income in reporting for taxes, as you will likely receive a tax document in relation to that specific account\u2019s income (in the US this would be a 1099-INT form produced by your bank). Here\u2019s what the opening accounts might look like for an investment account: 2012-03-01 open Assets:US:Etrade:Main:Cash USD 2012-03-01 open Assets:US:Etrade:Main:ITOT ITOT 2012-03-01 open Assets:US:Etrade:Main:IXUS IXUS 2012-03-01 open Assets:US:Etrade:Main:IEFA IEFA 2012-03-01 open Income:US:Etrade:Main:Interest USD 2012-03-01 open Income:US:Etrade:Main:PnL USD 2012-03-01 open Income:US:Etrade:Main:Dividend USD 2012-03-01 open Income:US:Etrade:Main:DividendNoTax USD The point is that all these accounts are related somehow. The various sections of the cookbook will describe the set of accounts suggested to create for each section. Not all sections have to be that way. For example, I have the following sections: Eternal accounts. I have a section at the top dedicated to contain special and \u201ceternal\u201d accounts, such as payables and receivables. Daybook. I have a \u201cdaybook\u201d section at the bottom that contains all cash expenses, in chronological order. Expense accounts. All my expenses accounts (categories) are defined in their own section. Employers. For each employer I\u2019ve defined a section where I put the entries for their direct deposits, and track vacations, stock vesting and other job-related transactions. Taxes. I have a section for taxes, organized by taxation year. You can organize it any way you like, because Beancount doesn\u2019t care about the ordering of declarations.","title":"Sections & Declaring Accounts"},{"location":"getting_started_with_beancount.html#closing-accounts","text":"If a real-world account has closed, or is never going to have any more transactions posted to it, you can declare it \u201cclosed\u201d at a particular date by using a Close directive: ; Moving to another bank. 2013-06-13 close Assets:US:BofA:Savings This tells Beancount not to show the account in reports that don\u2019t include any date where it was active. It also avoids errors by triggering an error if you do try to post to it at a later date.","title":"Closing Accounts"},{"location":"getting_started_with_beancount.html#de-duping","text":"One problem that will occur frequently is that once you have some sort of code or process set up to automatically extract postings from downloaded files, you will end up importing postings which provide two separate sides of the same transaction. An example is the payment of a credit card balance via a transfer from a checking account. If you download the transactions for your checking account, you will extract something like this: 2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" Assets:CA:BofA:Checking -923.24 USD The credit card download will yield you this: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Many times, transactions from these accounts need to be booked to an expense account, but in this case, these are two separate legs of the same transaction: a transfer. When you import one of these, you normally look for the other side and merge them together: ;2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Assets:CA:BofA:Checking -923.24 USD I often leave one of the description lines in comments\u2014just my choice, Beancount ignores it. Also note that I had to choose one of the two dates. I just choose the one I prefer, as long as it does not break any balance assertion. In the case that you would forget to merge those two imported transactions, worry not! That\u2019s what balance assertions are for. Regularly place a balance assertion in either of these accounts, e.g., every time you import, and you will get a nice error if you end up entering the transaction twice. This is pretty common and after a while it becomes second nature to interpret that compiler error and fix it in seconds. Finally, when I know I import just one side of these, I select the other account manually and I mark the posting I know will be imported later with a flag, which tells me I haven\u2019t de-duped this transaction yet: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD ! Assets:CA:BofA:Checking Later on, when I import the checking account\u2019s transactions and go fishing for the other side of this payment, I will find this and get a good feeling that the world is operating as it should. (If you\u2019re interested in more of a discussion around de-duplicating and merging transactions, see this feature proposal . Also, you might be interested in the \u201ceffective_date\u201d plugin external contribution, which splits transactions in two.)","title":"De-duping"},{"location":"getting_started_with_beancount.html#which-side","text":"So if you organize your account in sections the way I suggest above, which section of the file should you leave such \u201cmerged\u201d transactions in, that is, transactions that involve two separate accounts? Well, it\u2019s your call. For example, in the case of a transfer between two accounts organized such that they have their own dedicated sections, it would be nice to be able to leave both transactions there so that when you edit your input file you see them in either section, but unfortunately, the transaction must occur in only one place in your document. You have to choose one. Personally I\u2019m a little careless about being consistent which of the section I choose to leave the transaction in; sometimes I choose one section of my input file, or that of the other account, for the same pair of accounts. It hasn\u2019t been a problem, as I use Emacs and i-search liberally which makes it easy to dig around my gigantic input file. If you want to keep your input more tidy and organized, you could come up with a rule for yourself, e.g. \u201ccredit card payments are always left in the paying account\u2019s section, not in the credit card account\u2019s section\u201d, or perhaps you could leave the transaction in both sections and comment one out 2 .","title":"Which Side?"},{"location":"getting_started_with_beancount.html#padding","text":"If you\u2019re just starting out\u2014and you probably are if you\u2019re reading this\u2014you will have no historical data. This means that the balances of your Assets and Liabilities accounts in Beancount will all be zero. But the first thing you should want to do after defining some accounts is establish a balance sheet and bring those amounts to their actual current value. Let\u2019s take your checking account as an example, say you opened it a while back. You don\u2019t remember exactly when, so let\u2019s use an approximate date: 2000-05-28 open Assets:CA:BofA:Checking USD The next thing you do is look up your current balance and put a balance assertion for the corresponding amount: 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Running Beancount on just this will correctly produce an error because Beancount assumes an implicit balance assertion of \u201cempty\u201d at the time you open an account. You will have to \u201cpad\u201d your account to today\u2019s balance by inserting a balance adjustment at some point in time between the opening and the balance, against some equity account, which is an arbitrary place to book \u201cwhere you received the initial balance from.\u201d For this purpose, this is usually the \u201c Equity:Opening-Balances \u201d account. So let\u2019s include this padding transaction and recap what we have so far: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD From here onwards, you would start adding entries reflecting everything that happened after 7/1. However, what if you wanted to go back in time? It is perfectly reasonable that once you\u2019ve got your chart-of-accounts set up you might want to fill in the missing history until at least the beginning of this year. Let\u2019s assume you had a single transaction in June 2014, and let\u2019s add it: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now the balance assertion fails! You would need to adjust the initialization entry to fix this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1956.35 USD Assets:CA:BofA:Checking 1956.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now this works. So basically, every single time you insert an entry in the past, you would have to adjust the balance. Isn\u2019t this annoying? Well, yes. Fortunately, we can provide some help: you can use a Pad directive to replace and automatically synthesize the balance adjustment to match the next balance check, like this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 pad Assets:CA:BofA:Checking Equity:Opening-Balances 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Note that this is only needed for balance sheet accounts (Assets and Liabilities) because we don\u2019t care about the initial balances of the Income and Expenses accounts, we only care about their transitional value (the changes they post during a period). For example, it makes no sense to bring up the Expenses:Restaurant account to the sum total value of all the costs of the meals you consumed since you were born. So you will probably want to get started with Open & Pad directives for each Assets and Liabilities accounts.","title":"Padding"},{"location":"getting_started_with_beancount.html#whats-next","text":"At this point you will probably move onwards to the Cookbook , or read the User\u2019s Manual if you haven\u2019t already done that. It is tempting to want to break down a large file into many smaller ones, but especially at first, the convenience of having everything in a single place is great. \u21a9 Some people have suggested that Beancount automatically detect duplicated transactions based on a heuristic and automatically ignore (remove) one of the two, but this has not been tried out yet. In particular, this would lend itself well to organizing transactions not just per section, but in separate files, i.e., all files would contain all the transactions for the accounts they represent. If you\u2019re interested in adding this feature, you could easily implement this as a plugin, without disrupting the rest of the system. \u21a9","title":"What\u2019s Next?"},{"location":"health_care_expenses.html","text":"Health Care Expenses \uf0c1 Martin Blais , July 2014 This is incomplete, work-in-progress, not released yet. Accounting for your health care expenses is a little different than regular expenses because of the various maximums imposed by your insurance plan. For the purpose of this section, we will assume a context of privatized health care system as is present in the USA, but the same principles may apply to other countries (if anything, it will be simpler). Accounting With No Insurance Plan - The Naive Way \uf0c1 So what\u2019s so different about health care expenses? You might argue that they should be treated the same as other expenses. Say, we could define a few categories like these: 1973-04-27 open Expenses:Health:Medical 1973-04-27 open Expenses:Health:Dental 1973-04-27 open Expenses:Health:Vision 1973-04-27 open Expenses:Health:Drugs 1973-04-27 open Expenses:Health:Acupuncture 1973-04-27 open Expenses:Health:Massage-Therapy 1973-04-27 open Expenses:Health:Physical-Therapy And simply book outflows of moneys to them when you spend on one of these categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Physical-Therapy 25 USD In fact, this would be work just fine if you paid for all of your health care costs out-of-pocket. If that is your situation, this is what you should do. It\u2019s straightforward. The problem is that in practice, the great majority of people don\u2019t pay for health care costs themselves. The great majority of your health-related costs are paid for by your insurance. This does not mean that there are no costs: your insurance usually only pays for a portion of the actual expenses, depending on the service. In particular, depending on your insurance plan, in any calendar year you pay for 100% of your health care costs up to a fixed amount (usually a few hundred dollars). This is called the deductible amount. Heretofore, you pay a percentage of the service (the copayment amounts ) , and after a higher limit amount (the out-of-pocket maximum amount), you don\u2019t pay anything anymore; plans with such limits guarantee that you will never pay more than this amount. If you booked the amounts that you pay for your deductible, the scale of the expenses that would get reflected on your income statement would depend mostly on which type of service you happened to use first after January 1st. It would not accurately reflect the cost of each service that you use. It would not be that useful. Counting the Cash Payments - The Incorrect Way \uf0c1 So you might argue that you should instead book the same outflows of money to categories that reflect their true nature: 1973-04-27 open Expenses:Health:Medical:Deductible 1973-04-27 open Expenses:Health:Medical:Copayments 1973-04-27 open Expenses:Health:Vision:Deductible 1973-04-27 open Expenses:Health:Vision:Copayments 1973-04-27 open Expenses:Health:Dental:Deductible 1973-04-27 open Expenses:Health:Dental:Copayments Then booking the payments to those categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Copayments 25 USD But this is largely unsatisfying, for a few reasons: while this tracks how much of was paid in total in deductible and copayments, it does not reflect anything about how much of a particular health provider services was used. More importantly, it is incorrect, because deductible and copayment limits apply to each calendar year for dates when the service was provided, not when it was paid for. In other words, if you received the service on December 28th, 2013 but paid for it the year following on January 8th, 2014, the copayment accrual is going towards the wrong year. In order to do this correctly, you must accrue the deductible and copayment amounts on the date of service , which is always provided on the documents your insurance company provides (the Explanation of Benefits , more later about this). The date of service is the date you actually were seen by the provider. Another solution would be to define accounts for each year, similarly to tax accounts (see chapter on Taxes): 2012-01-01 open Expenses:Health:Medical:Y2012:Deductible 2012-01-01 open Expenses:Health:Medical:Y2012:Copayments 2013-01-01 open Expenses:Health:Medical:Y2013:Deductible 2013-01-01 open Expenses:Health:Medical:Y2013:Copayments 2014-01-01 open Expenses:Health:Medical:Y2014:Deductible 2014-01-91 open Expenses:Health:Medical:Y2014:Copayments 2014-01-01 open Expenses:Health:Dental:Y2012:Deductible 2014-01-91 open Expenses:Health:Dental:Y2012:Copayments \u2026 Then when you book your expense, you could use the account corresponding to the year the service was received: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Y2013:Copayments 25 USD This is not a nice solution however, due to the proliferation of accounts that need to get created for each year, and it still does not tell us how much of each service we\u2019ve consumed. We clearly need a better solution. How Health Care Insurance Payments Work \uf0c1 Let\u2019s first review how insurance payments work, as it will be needed to have a basic understanding of claims payments to correctly account for these costs. For each service, your insurance covers a portion of it and you normally pay the difference. The way that this works is that your doctor sends a bill to the insurance company and the insurance company either (a) responds to them directly with payment for the covered part of the service, or (b) the insurance company sends you a check and you are meant to sign it over to your doctor (usually after some annoying reminders for you to do so). Sometimes, but not always, your insurance company sends you a report of the doctor\u2019s claim. This document is called an explanation of benefits or \u201cEOB\u201d, and it details the portion of the service that the insurance paid for, the portion that you\u2019re responsible to pay for, and other amounts. Service providers that have no direct relationship with your insurance company will need this detail. This is a useful document for the purpose of accounting: make sure to keep a copy for yourself 1 . Here is a sample from an EOB, with the important areas highlighted: The example above exhibits the following amounts: Claims/Billed. How much the doctor asked the insurance for the service. Patient Savings. How much the service was reduced as a result of pre-established negotiations with the provider (for in-network providers). Applied to Deductible. How much of this service you need to cover yourself as part of this calendar year\u2019s deductible amount. You need to pay this. Copayments. The portion of the service not covered by the insurance. You have to pay this. Claims Payments. How much the insurance actually sent a check to the provider for. The terminology used on the EOB provided by your insurance company may vary slightly, but you should be able to find corresponding amounts easily. The doctor\u2019s billing dept. then sends you a bill for the uncovered portion 2 and you issue them a payment, by credit card, check or otherwise. Consider this a payable when the bill or EOB arrives at your door. All in all, there are four dates relevant to a claim: Date of Service. That is the date you visited the doctor\u2019s office, the date you received the service. This is the date relevant for accounting for deductibles and copayments. Date of Issue. The date that the claim was processed by the insurance. You may ignore this. Billing Date. The date the provider receive payments for its claim and issue you a bill for the remaining portion. We don\u2019t care much about this either. Date of Payment. The date you made the payment. This will automatically appear on your credit card or checking account statement, or if you paid cash, you need to enter this manually (as you do all cash transactions you care about). The most important date when you refer to any claim is the date of service . If you scan and file your EOBs to documents, it is wise to rename them to include this date in the filename. Provider Networks \uf0c1 In the USA, each medical professional (or \u201cservice provider\u201d) decides whether to maintain an established arrangement with each insurance company, depending on their rates of payment and on how much of a pain they are about paying up bills. When they do, they are considered an \u201cin-network provider\u201d by the insurance company. Otherwise they are considered an \u201cout-of-network provider\u201d and the proportion of the services that the insurance covers is much smaller. That\u2019s the only difference. The list of providers that an insurance has in their network\u2014and how difficult they are about refusing to pay bills\u2014is usually a major consideration in the selection of an insurance plan, for someone who has a choice. But because most employers compete on compensation benefits by paying for their employees\u2019 health care insurance costs, as well as the fact that they are usually able to negotiate better rates from insurance companies than an individual can because they represent a large pool of customers (the employees), the great majority of people with jobs end up choosing their employer\u2019s plan and then try to go to in-network providers. This is not always possible, however: your family doctor may decide to stop accepting your insurance as in-network during the course of the year, and you might prefer to maintain an established relationship with your doctor rather than switch, so you end up having to pay a bit more. The bottom line is that in a typical year, you usually use some services of both in-network and out-of-network professionals and you have to account for both of these costs. Accruing on Service Date - The Correct Way \uf0c1 Ideally, we would like to obtain balances for the following amounts: The amount of money that each service cost, regardless of who paid The amount of deductible and copayments that are used in each calendar year What we will do, is enter two entries: An entry for each EOB, at the date of service, for which we obtain a payable. An entry for each payments, that comes out of an Asset or Liability account. In-Network Providers \uf0c1 For in-network providers a typical entry looks like this: 2013-04-01 * \"DR RAPPOPORT\" \"Office visit\" ^anthem-claim-8765937424 Expenses:Health:Medical:Claims 225.00 USD Expenses:Health:Medical:PatientSavings -151.53 USD Liabilities:US:Accounts-Payable -10.00 USD ; Copay Expenses:Health:Medical:ClaimsPayment -63.47 USD Once you\u2019ve figured out which numbers to pull out of of these EOBs, it becomes simpler to enter others, because they are very regular and all look the same. Note that an in-network EOB will include a \u201cPatient Savings\u201d number. This is meant to reflect how much lower you\u2019re paying due to your insurance plan selection. (In practice, this is a bit of smoke and mirrors, because all the claims made by doctors are inflated to reflect the amount they actually get paid for, but I think the insurance likes to show these to you anyway.) I like to immediately reflect the missing portion to an \u201caccounts payable\u201d liability, which tells me I have this registered as an upcoming payment: 1973-04-27 open Liabilities:US:Accounts-Payable You can then eliminate this payable as the payment entry comes in through your credit card import: 2014-01-08 * \"Rappoport Medical Office\" ^anthem-claim-8765937424 Liabilities:US:BofA:Credit-Card -10.00 USD Liabilities:US:Accounts-Payable 10.00 USD Note how I \u201clink\u201d both of the transactions to each other with \u201c ^anthem-claim-8765937424 \u201d so they can easily be found and verified later on. For in-network doctors, because the payments from the insurance are generally predictable, the doctor\u2019s office will sometimes require the copayment on the same day you visit. Just book this as a cash entry against the same liability account. If you have a doctor that you visit regularly, you may create dedicated Liabilities accounts for them: 2009-09-17 open Liabilities:US:Accounts-Payable:DrRappoport Out-of-Network Providers \uf0c1 For out-of-network providers, the EOBs are slightly different: What we noticed is that there is an \u201cother amount\u201d section, which is essentially what will be written off by the provider, and the Patient Savings section is empty. A typical entry corresponding to this EOB would look like this: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Expenses:Health:PhysicalTherapy:Uncovered -155.52 USD Liabilities:US:Accounts-Payable:CityPT -49.34 USD You will typically receive the check for your service provider\u2019s payment (in the example, that is a check for $115.14) and have to send it over to them yourself. You can either deposit the check right away and make the full payment with your credit card, or send the check and just pay the difference (this is what most people do): 2014-01-08 * \"City PhysioTherapy\" ^anthem-claim-17646398 Liabilities:US:BofA:Credit-Card -49.34 USD Liabilities:US:Accounts-Payable:CityPT 49.34 USD One interesting twist is that many out-of-network providers will accept the lower amount that insurance companies pay for out-of-network services and write off the excess amount, or charge you nominal amount only. For example, the \u201cdeal\u201d with my favorite physical therapy place is that they charge me 25$ per session. Once again, the claim is inflated, of course, and the apparent amount the insurance says you have to pay has to be largely written off. This is how you\u2019d book such an entry\u2019s EOB: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Liabilities:US:Accounts-Payable:CityPT -25.00 USD Expenses:Health:PhysicalTherapy:WriteOffs The provider\u2019s administrator does not charge me on every visit. She accrues the amounts and whenever decides to process her accounting, she makes a charge for the total, which clears the account to zero. This might look like this: 2014-02-26 * \"Payment for 3 Physical Therapy treatments\" Liabilities:US:BofA:CreditCard -75.00 USD Liabilities:US:Accounts-Payable:CityPT Tracking Deductible and Copayment Limits \uf0c1 As we\u2019ve seen previously, there are limits on how much you end up paying for health care services. These limits are applied by calendar year, so they need to be applied at the date of service. For this reasons, we can attach corresponding entries to the EOB entries, and then filter transactions by year to calculate the balances for that year. I like to keep tabs on these limits and ensure that they are applied properly. According to my insurance plan, the limits are defined like this: Deductible. \u201cThe amount you pay out-of-pocket within a calendar year before your health insurance begins to pay for covered service.\u201d Out-of-pocket maximum. \u201cThe maximum amount of money you could pay out-of-pocket in a calendar year for covered health care services. There are exclusions, e.g. for prescription drugs and some services.\u201d Co-payment. \u201cA fixed fee that you pay out-of-pocket for a service. This fee does not vary according to the actual cost of the service.\u201d It is not straightforward, however, because there are distinct limits for in-network and out-of-network providers, both for deductible amounts and copayment amounts: Amounts paid for in-network deductibles count towards your limit for out-of-network deductibles. Amounts paid for in-network copayments count towards your limit for out-of-network copayments. Amounts paid for deductibles count towards your limit for copayments. I carry this out using the \u201cmirror accounting\u201d technique I describe in another document. The idea is to use an alternative currency to count for these amounts. TODO: complete this Insurance Premiums \uf0c1 Then you have to track how much you spend on your insurance premiums. These are the fixed semi-monthly payments you make\u2014usually directly processed out of your paycheck\u2014for getting the insurance policy itself. As for most people with jobs in the US, my employer offers a few health coverage plans and pays for most of it, but there is always a portion I have to pay myself: 1973-04-27 open Expenses:Health:Insurance A fixed premium is automatically deducted from my paycheck entries, as it appears on my pay stub: 2014-02-08 * \"Acme Corp - Salary\" \u2026 Expenses:Health:Insurance 42.45 USD \u2026 On your pay stub, be careful not to confuse this with the \u201cMedicare tax,\u201d which is a tax used to pay for the bit of socialized medicine costs the US has for older people. This is just a tax and has little to do with your own health care expenses. Drugs \uf0c1 Drugs are accounted for separately. TODO: complete this You do not always receive these through the mail, but insurance companies are now finally coming up with websites where you can download all the claims that were made on your behalf (and account for them), even those which weren\u2019t mailed to you. \u21a9 This is a tremendous annoyance because each doctor has a different billing department, they are typically antiquated and their online payment options are almost always broken (you end up having to call a phone number and convince a grumpy administrator to pay by credit card because they are still used to receiving checks ), and all the different service providers that collaborate together to offer you a health service will bill you separately (with different billing departments as well). For example, if you undergo surgery, over the following 6 months you can expect to get different bills from your surgeon, his assistant, the anesthesiologist, the person who monitored your brain activity while asleep, the lab who did the blood work, the person who read an X-ray, and so on. And the bills may be sent more than 6 months after the operation took place, well into the following year. It\u2019s completely Kafkaesque. You have to really wonder why the insurance company does not always pay the full service to the providers and then bill you , just once, for the deductibles. That would make the process a lot simpler and reduce the exhorbitant cost of administering health care. \u21a9","title":"Health Care Expenses"},{"location":"health_care_expenses.html#health-care-expenses","text":"Martin Blais , July 2014 This is incomplete, work-in-progress, not released yet. Accounting for your health care expenses is a little different than regular expenses because of the various maximums imposed by your insurance plan. For the purpose of this section, we will assume a context of privatized health care system as is present in the USA, but the same principles may apply to other countries (if anything, it will be simpler).","title":"Health Care Expenses"},{"location":"health_care_expenses.html#accounting-with-no-insurance-plan-the-naive-way","text":"So what\u2019s so different about health care expenses? You might argue that they should be treated the same as other expenses. Say, we could define a few categories like these: 1973-04-27 open Expenses:Health:Medical 1973-04-27 open Expenses:Health:Dental 1973-04-27 open Expenses:Health:Vision 1973-04-27 open Expenses:Health:Drugs 1973-04-27 open Expenses:Health:Acupuncture 1973-04-27 open Expenses:Health:Massage-Therapy 1973-04-27 open Expenses:Health:Physical-Therapy And simply book outflows of moneys to them when you spend on one of these categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Physical-Therapy 25 USD In fact, this would be work just fine if you paid for all of your health care costs out-of-pocket. If that is your situation, this is what you should do. It\u2019s straightforward. The problem is that in practice, the great majority of people don\u2019t pay for health care costs themselves. The great majority of your health-related costs are paid for by your insurance. This does not mean that there are no costs: your insurance usually only pays for a portion of the actual expenses, depending on the service. In particular, depending on your insurance plan, in any calendar year you pay for 100% of your health care costs up to a fixed amount (usually a few hundred dollars). This is called the deductible amount. Heretofore, you pay a percentage of the service (the copayment amounts ) , and after a higher limit amount (the out-of-pocket maximum amount), you don\u2019t pay anything anymore; plans with such limits guarantee that you will never pay more than this amount. If you booked the amounts that you pay for your deductible, the scale of the expenses that would get reflected on your income statement would depend mostly on which type of service you happened to use first after January 1st. It would not accurately reflect the cost of each service that you use. It would not be that useful.","title":"Accounting With No Insurance Plan - The Naive Way"},{"location":"health_care_expenses.html#counting-the-cash-payments-the-incorrect-way","text":"So you might argue that you should instead book the same outflows of money to categories that reflect their true nature: 1973-04-27 open Expenses:Health:Medical:Deductible 1973-04-27 open Expenses:Health:Medical:Copayments 1973-04-27 open Expenses:Health:Vision:Deductible 1973-04-27 open Expenses:Health:Vision:Copayments 1973-04-27 open Expenses:Health:Dental:Deductible 1973-04-27 open Expenses:Health:Dental:Copayments Then booking the payments to those categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Copayments 25 USD But this is largely unsatisfying, for a few reasons: while this tracks how much of was paid in total in deductible and copayments, it does not reflect anything about how much of a particular health provider services was used. More importantly, it is incorrect, because deductible and copayment limits apply to each calendar year for dates when the service was provided, not when it was paid for. In other words, if you received the service on December 28th, 2013 but paid for it the year following on January 8th, 2014, the copayment accrual is going towards the wrong year. In order to do this correctly, you must accrue the deductible and copayment amounts on the date of service , which is always provided on the documents your insurance company provides (the Explanation of Benefits , more later about this). The date of service is the date you actually were seen by the provider. Another solution would be to define accounts for each year, similarly to tax accounts (see chapter on Taxes): 2012-01-01 open Expenses:Health:Medical:Y2012:Deductible 2012-01-01 open Expenses:Health:Medical:Y2012:Copayments 2013-01-01 open Expenses:Health:Medical:Y2013:Deductible 2013-01-01 open Expenses:Health:Medical:Y2013:Copayments 2014-01-01 open Expenses:Health:Medical:Y2014:Deductible 2014-01-91 open Expenses:Health:Medical:Y2014:Copayments 2014-01-01 open Expenses:Health:Dental:Y2012:Deductible 2014-01-91 open Expenses:Health:Dental:Y2012:Copayments \u2026 Then when you book your expense, you could use the account corresponding to the year the service was received: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Y2013:Copayments 25 USD This is not a nice solution however, due to the proliferation of accounts that need to get created for each year, and it still does not tell us how much of each service we\u2019ve consumed. We clearly need a better solution.","title":"Counting the Cash Payments - The Incorrect Way"},{"location":"health_care_expenses.html#how-health-care-insurance-payments-work","text":"Let\u2019s first review how insurance payments work, as it will be needed to have a basic understanding of claims payments to correctly account for these costs. For each service, your insurance covers a portion of it and you normally pay the difference. The way that this works is that your doctor sends a bill to the insurance company and the insurance company either (a) responds to them directly with payment for the covered part of the service, or (b) the insurance company sends you a check and you are meant to sign it over to your doctor (usually after some annoying reminders for you to do so). Sometimes, but not always, your insurance company sends you a report of the doctor\u2019s claim. This document is called an explanation of benefits or \u201cEOB\u201d, and it details the portion of the service that the insurance paid for, the portion that you\u2019re responsible to pay for, and other amounts. Service providers that have no direct relationship with your insurance company will need this detail. This is a useful document for the purpose of accounting: make sure to keep a copy for yourself 1 . Here is a sample from an EOB, with the important areas highlighted: The example above exhibits the following amounts: Claims/Billed. How much the doctor asked the insurance for the service. Patient Savings. How much the service was reduced as a result of pre-established negotiations with the provider (for in-network providers). Applied to Deductible. How much of this service you need to cover yourself as part of this calendar year\u2019s deductible amount. You need to pay this. Copayments. The portion of the service not covered by the insurance. You have to pay this. Claims Payments. How much the insurance actually sent a check to the provider for. The terminology used on the EOB provided by your insurance company may vary slightly, but you should be able to find corresponding amounts easily. The doctor\u2019s billing dept. then sends you a bill for the uncovered portion 2 and you issue them a payment, by credit card, check or otherwise. Consider this a payable when the bill or EOB arrives at your door. All in all, there are four dates relevant to a claim: Date of Service. That is the date you visited the doctor\u2019s office, the date you received the service. This is the date relevant for accounting for deductibles and copayments. Date of Issue. The date that the claim was processed by the insurance. You may ignore this. Billing Date. The date the provider receive payments for its claim and issue you a bill for the remaining portion. We don\u2019t care much about this either. Date of Payment. The date you made the payment. This will automatically appear on your credit card or checking account statement, or if you paid cash, you need to enter this manually (as you do all cash transactions you care about). The most important date when you refer to any claim is the date of service . If you scan and file your EOBs to documents, it is wise to rename them to include this date in the filename.","title":"How Health Care Insurance Payments Work"},{"location":"health_care_expenses.html#provider-networks","text":"In the USA, each medical professional (or \u201cservice provider\u201d) decides whether to maintain an established arrangement with each insurance company, depending on their rates of payment and on how much of a pain they are about paying up bills. When they do, they are considered an \u201cin-network provider\u201d by the insurance company. Otherwise they are considered an \u201cout-of-network provider\u201d and the proportion of the services that the insurance covers is much smaller. That\u2019s the only difference. The list of providers that an insurance has in their network\u2014and how difficult they are about refusing to pay bills\u2014is usually a major consideration in the selection of an insurance plan, for someone who has a choice. But because most employers compete on compensation benefits by paying for their employees\u2019 health care insurance costs, as well as the fact that they are usually able to negotiate better rates from insurance companies than an individual can because they represent a large pool of customers (the employees), the great majority of people with jobs end up choosing their employer\u2019s plan and then try to go to in-network providers. This is not always possible, however: your family doctor may decide to stop accepting your insurance as in-network during the course of the year, and you might prefer to maintain an established relationship with your doctor rather than switch, so you end up having to pay a bit more. The bottom line is that in a typical year, you usually use some services of both in-network and out-of-network professionals and you have to account for both of these costs.","title":"Provider Networks"},{"location":"health_care_expenses.html#accruing-on-service-date-the-correct-way","text":"Ideally, we would like to obtain balances for the following amounts: The amount of money that each service cost, regardless of who paid The amount of deductible and copayments that are used in each calendar year What we will do, is enter two entries: An entry for each EOB, at the date of service, for which we obtain a payable. An entry for each payments, that comes out of an Asset or Liability account.","title":"Accruing on Service Date - The Correct Way"},{"location":"health_care_expenses.html#in-network-providers","text":"For in-network providers a typical entry looks like this: 2013-04-01 * \"DR RAPPOPORT\" \"Office visit\" ^anthem-claim-8765937424 Expenses:Health:Medical:Claims 225.00 USD Expenses:Health:Medical:PatientSavings -151.53 USD Liabilities:US:Accounts-Payable -10.00 USD ; Copay Expenses:Health:Medical:ClaimsPayment -63.47 USD Once you\u2019ve figured out which numbers to pull out of of these EOBs, it becomes simpler to enter others, because they are very regular and all look the same. Note that an in-network EOB will include a \u201cPatient Savings\u201d number. This is meant to reflect how much lower you\u2019re paying due to your insurance plan selection. (In practice, this is a bit of smoke and mirrors, because all the claims made by doctors are inflated to reflect the amount they actually get paid for, but I think the insurance likes to show these to you anyway.) I like to immediately reflect the missing portion to an \u201caccounts payable\u201d liability, which tells me I have this registered as an upcoming payment: 1973-04-27 open Liabilities:US:Accounts-Payable You can then eliminate this payable as the payment entry comes in through your credit card import: 2014-01-08 * \"Rappoport Medical Office\" ^anthem-claim-8765937424 Liabilities:US:BofA:Credit-Card -10.00 USD Liabilities:US:Accounts-Payable 10.00 USD Note how I \u201clink\u201d both of the transactions to each other with \u201c ^anthem-claim-8765937424 \u201d so they can easily be found and verified later on. For in-network doctors, because the payments from the insurance are generally predictable, the doctor\u2019s office will sometimes require the copayment on the same day you visit. Just book this as a cash entry against the same liability account. If you have a doctor that you visit regularly, you may create dedicated Liabilities accounts for them: 2009-09-17 open Liabilities:US:Accounts-Payable:DrRappoport","title":"In-Network Providers"},{"location":"health_care_expenses.html#out-of-network-providers","text":"For out-of-network providers, the EOBs are slightly different: What we noticed is that there is an \u201cother amount\u201d section, which is essentially what will be written off by the provider, and the Patient Savings section is empty. A typical entry corresponding to this EOB would look like this: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Expenses:Health:PhysicalTherapy:Uncovered -155.52 USD Liabilities:US:Accounts-Payable:CityPT -49.34 USD You will typically receive the check for your service provider\u2019s payment (in the example, that is a check for $115.14) and have to send it over to them yourself. You can either deposit the check right away and make the full payment with your credit card, or send the check and just pay the difference (this is what most people do): 2014-01-08 * \"City PhysioTherapy\" ^anthem-claim-17646398 Liabilities:US:BofA:Credit-Card -49.34 USD Liabilities:US:Accounts-Payable:CityPT 49.34 USD One interesting twist is that many out-of-network providers will accept the lower amount that insurance companies pay for out-of-network services and write off the excess amount, or charge you nominal amount only. For example, the \u201cdeal\u201d with my favorite physical therapy place is that they charge me 25$ per session. Once again, the claim is inflated, of course, and the apparent amount the insurance says you have to pay has to be largely written off. This is how you\u2019d book such an entry\u2019s EOB: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Liabilities:US:Accounts-Payable:CityPT -25.00 USD Expenses:Health:PhysicalTherapy:WriteOffs The provider\u2019s administrator does not charge me on every visit. She accrues the amounts and whenever decides to process her accounting, she makes a charge for the total, which clears the account to zero. This might look like this: 2014-02-26 * \"Payment for 3 Physical Therapy treatments\" Liabilities:US:BofA:CreditCard -75.00 USD Liabilities:US:Accounts-Payable:CityPT","title":"Out-of-Network Providers"},{"location":"health_care_expenses.html#tracking-deductible-and-copayment-limits","text":"As we\u2019ve seen previously, there are limits on how much you end up paying for health care services. These limits are applied by calendar year, so they need to be applied at the date of service. For this reasons, we can attach corresponding entries to the EOB entries, and then filter transactions by year to calculate the balances for that year. I like to keep tabs on these limits and ensure that they are applied properly. According to my insurance plan, the limits are defined like this: Deductible. \u201cThe amount you pay out-of-pocket within a calendar year before your health insurance begins to pay for covered service.\u201d Out-of-pocket maximum. \u201cThe maximum amount of money you could pay out-of-pocket in a calendar year for covered health care services. There are exclusions, e.g. for prescription drugs and some services.\u201d Co-payment. \u201cA fixed fee that you pay out-of-pocket for a service. This fee does not vary according to the actual cost of the service.\u201d It is not straightforward, however, because there are distinct limits for in-network and out-of-network providers, both for deductible amounts and copayment amounts: Amounts paid for in-network deductibles count towards your limit for out-of-network deductibles. Amounts paid for in-network copayments count towards your limit for out-of-network copayments. Amounts paid for deductibles count towards your limit for copayments. I carry this out using the \u201cmirror accounting\u201d technique I describe in another document. The idea is to use an alternative currency to count for these amounts. TODO: complete this","title":"Tracking Deductible and Copayment Limits"},{"location":"health_care_expenses.html#insurance-premiums","text":"Then you have to track how much you spend on your insurance premiums. These are the fixed semi-monthly payments you make\u2014usually directly processed out of your paycheck\u2014for getting the insurance policy itself. As for most people with jobs in the US, my employer offers a few health coverage plans and pays for most of it, but there is always a portion I have to pay myself: 1973-04-27 open Expenses:Health:Insurance A fixed premium is automatically deducted from my paycheck entries, as it appears on my pay stub: 2014-02-08 * \"Acme Corp - Salary\" \u2026 Expenses:Health:Insurance 42.45 USD \u2026 On your pay stub, be careful not to confuse this with the \u201cMedicare tax,\u201d which is a tax used to pay for the bit of socialized medicine costs the US has for older people. This is just a tax and has little to do with your own health care expenses.","title":"Insurance Premiums"},{"location":"health_care_expenses.html#drugs","text":"Drugs are accounted for separately. TODO: complete this You do not always receive these through the mail, but insurance companies are now finally coming up with websites where you can download all the claims that were made on your behalf (and account for them), even those which weren\u2019t mailed to you. \u21a9 This is a tremendous annoyance because each doctor has a different billing department, they are typically antiquated and their online payment options are almost always broken (you end up having to call a phone number and convince a grumpy administrator to pay by credit card because they are still used to receiving checks ), and all the different service providers that collaborate together to offer you a health service will bill you separately (with different billing departments as well). For example, if you undergo surgery, over the following 6 months you can expect to get different bills from your surgeon, his assistant, the anesthesiologist, the person who monitored your brain activity while asleep, the lab who did the blood work, the person who read an X-ray, and so on. And the bills may be sent more than 6 months after the operation took place, well into the following year. It\u2019s completely Kafkaesque. You have to really wonder why the insurance company does not always pay the full service to the providers and then bill you , just once, for the deductibles. That would make the process a lot simpler and reduce the exhorbitant cost of administering health care. \u21a9","title":"Drugs"},{"location":"how_inventories_work.html","text":"How Inventories Work \uf0c1 Martin Blais , December 2016 http://furius.ca/beancount/doc/booking This document explains how we accumulate commodities and match sales (reductions) against accumulated inventory contents. Introduction \uf0c1 Beyond the ability to track and list the postings made to each of the accounts (an operation that produces a journal of entries), one of the most common and useful operations of Beancount is to sum up the positions of arbitrary sets of postings. These aggregations are at the heart of how Beancount works, and are implemented in an object called \u201cinventory.\u201d This document explains how this aggregation process works. If you\u2019re going to track investments, it\u2019s necessary to understand what follows. Matches & Booking Methods \uf0c1 In order to get the big picture, let\u2019s walk through the various booking features by way of a simple examples. This should expose you to all the main ideas in one go. Simple Postings \u2014 No Cost \uf0c1 Consider a set of simple postings to an account, e.g., 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 221.23 USD \u2026 2016-04-29 * \"ATM Withdrawal\" Assets:Bank:Checking -100.00 USD \u2026 2016-04-29 * \"Debit card payment\" Assets:Bank:Checking -45.67 USD \u2026 The inventory of the Checking account begins empty. After the first transaction, its contents are 221.23 USD. After the second, 121.23 USD. Finally, the third transaction brings this balance to 75.56 USD. This seems very natural; the numbers simply add to. It might be obvious, but note also that the numbers are allowed to change the sign (go negative). Multiple Commodities \uf0c1 An inventory may contain more than one type of commodity. It is equivalent to a mapping from commodity to some number of units. For example, 2016-07-24 * \"Dinner before leaving NYC\" Expenses:Restaurants 34.58 USD \u2026 2016-07-26 * \"Food with friends after landing\" Expenses:Restaurants 62.11 CAD \u2026 After those two transactions, the Restaurants account contains 34.58 USD and 62.11 CAD. Its contents are said to be of mixed commodities. And naturally, postings are applied to just the currencies they affect. For instance, the following transaction 2016-07-27 * \"Brunch\" Expenses:Restaurants 23.91 CAD \u2026 brings the balance of that account to 34.58 USD and 86.02 CAD. The number of units USD hasn\u2019t changed. Note that accounts may contain any number of commodities, and this is also true for commodities held at cost, which we\u2019ll see shortly. While this is made possible, I recommend that you define enough accounts to keep a single commodity in each; this can be enforced with the \u201c onecommodity \u201d plugin. Cost Basis \uf0c1 Things get a little more hairy when we consider the tracking of investments with a cost basis. Beancount allows you to associate a cost basis and an optional label with a particular lot acquired. Consider these two purchases to an investment account: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest 25 HOOL {23.00 USD, \"first-lot\"} \u2026 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest 35 HOOL {27.00 USD} \u2026 So now, the investment account\u2019s inventory contains units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Those two lots were not merged, they are still two distinct positions in the inventory. Inventories merge lots together and adjust the units only if the commodity and all of its cost attributes exactly match. (In practice, it\u2019s pretty rare that two augmentations will have the same cost and date attributes.) Note how Beancount automatically associated the acquisition date to each lot; you can override it if desired, by adding the date similar to the optional label. this is useful for making cost basis adjustments). Postings that add to the content of an inventory are called augmentations . Reductions \uf0c1 But how do we remove commodities from an inventory? You could eat away at an existing lot by selling some of it. You do this by posting a reduction to the account, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {23.00 USD} \u2026 Just to be clear, what makes this posting a reduction is the mere fact that the sign (-) is opposite that of the balance of the account (+25) for that commodity. This posting tells Beancount to find all lots with a cost basis of 23.00 USD and remove 12 units from it. The resulting inventory will be units ccy cost cost-ccy lot-date label 13 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Note how the first posting was reduced by 12 units. We didn\u2019t have to specify all of the lot\u2019s attributes, just the cost. We could have equivalently used the date to specify which lot to reduce: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 Or the label: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {\"first-lot\"} \u2026 Or a combination of these. Any combination of attributes will be matched against the inventory contents to find which lot to reduce. In fact, if the inventory happened to have just a single lot in it, you could reduce it like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Ambiguous Matches \uf0c1 But what happens if multiple lots match the reduction? For example, with the previous inventory containing two lots, if you wrote your sale like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Beancount wouldn\u2019t be able to figure out which lot needs to get reduced. We have an ambiguous match. Partially ambiguous matches are also possible. For example, if you have the following inventory to reduce from: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, None} 30 HOOL {25.00 USD, 2015-04-01, None} 35 HOOL {27.00 USD, 2015-05-01, None} And you attempted to reduce like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 The first two lots are selected as matches. Strict Booking \uf0c1 What does Beancount do with ambiguous matches? By default, it issues an error. More precisely, what happens is that Beancount invokes the booking method and it handles the ambiguous match depending on what it is set. The default booking method is \u201c STRICT \u201d and it just gives up and spits out an error, telling you you need to refine your input in order to disambiguate your inventory reduction. FIFO and LIFO Booking \uf0c1 Other booking methods are available. They can be configured using options, like this: option \"booking_method\" \"FIFO\" The \u201c FIFO \u201d method automatically selects the oldest of the matching lots up to the requested size of the reduction. For example, given our previous inventory: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Attempting to reduce 28 shares like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -28 HOOL {} \u2026 would match both lots, completely reduce the first one to zero units and remove the remaining 3 units from the second lot, to result in the following inventory: units ccy cost cost-ccy lot-date label 32 HOOL {27.00 USD, 2015-05-01, None} The \u201cLIFO\u201d method works similarly, but consumes the youngest (latest) lots first, working its way backward in time to remove the volume. Per-account Booking Method \uf0c1 You don\u2019t have to make all accounts follow the same booking method; the option in the previous section sets the default method for all accounts. In order to override the booking method for a particular account, you can use an optional string option on the account\u2019s Open directive, like this: 2014-01-01 open Assets:Invest \"FIFO\" This allows you to treat different accounts with a different booking resolution. Total Matches \uf0c1 There is an exception to strict booking: if the entire inventory is being reduced by exactly the total number of units, it\u2019s clear that all the matching lots are to be selected and this is considered unambiguous, even under \u201cSTRICT\u201d booking. For example, under \u201cSTRICT\u201d booking, this reduction would empty up the previous inventory without raising an error, because there are 25 + 35 shares matching: 2015-05-15 * \"Sell all my shares\" Assets:Invest -60 HOOL {} \u2026 Average Booking \uf0c1 Retirement accounts created by government incentive programs (such as the 401k plan in the US or the RRSP in Canada) typically consist in pre-tax money. For these types of accounts, brokers usually disregard the calculation of cost basis because the taxation is to be made upon distributing money outside the account. These accounts are often managed to the extent that they are fully invested; therefore, fees are often taken as shares of the investment portfolio, priced on the day the fee is paid out. This makes it awkward to track the cost basis of individual lots. The correct way to deal with this is to treat the number of units and the cost basis of each commodity\u2019s entire set of lots separately. For example, the following two transactions: 2016-07-28 * \"Buy some shares of retirement fund\" Assets:Invest 45.0045 VBMPX {11.11 USD} \u2026 2016-10-12 * \"Buy some shares of retirement fund\" Assets:Invest 54.5951 VBMPX {10.99 USD} \u2026 Should result in a single lot with the total number of units and the averaged cost: units ccy cost cost-ccy lot-date label 99.5996 VBMPX {11.0442 USD, 2016-07-28, None} A fee taken in this account might look like this: 2016-12-30 * \"Quarterly management fee\" Assets:Invest -1.4154 VBMPX {10.59 USD} Expenses:Fees Even with negative units the number and cost get aggregated separately: units ccy cost cost-ccy lot-date label 98.1842 VBMPX {11.0508 USD, 2016-07-28, None} This feature isn\u2019t yet supported in Beancount; it\u2019s fairly tricky to implement, and will be the subject in a minor release in the future. No Booking \uf0c1 However, there is another way to deal with non-taxable accounts in the meantime: you can simply disable the booking. There is a booking method called \u201c NONE \u201d which implements a very liberal strategy which accepts any new lot.. New lots are always appended unconditionally to the inventory. Using this strategy on the transactions from the previous section would result in this inventory: units ccy cost cost-ccy lot-date label 45.0045 VBMPX {11.11 USD, 2016-07-28, None} 54.5951 VBMPX {10.99 USD, 2016-10-12, None} -1.4154 VBMPX {10.59 USD, 2016-12-30, None} Observe how the resulting inventory has a mix of signs; normally this is not allowed, but it is tolerated under this degenerate booking method. Note that under this method, the only meaningful values are the total number of units and the total or average cost amounts. The individual lots aren\u2019t really lots, they only represent the list of all postings made to that account. Note: If you are familiar with Ledger, this is the default and only booking method that it supports. Summary \uf0c1 In summary, here\u2019s what we presented in the walkthrough. Augmentations are never problematic; they always add a new position to an existing inventory. On the other hand, reductions may result in a few outcomes: Single match. Only one position matches the reduction; it is reduced. Total match. The total number of units requested matches the total number of units of the positions matched. These positions are reduced away. No match. None of the positions matches the reducing posting. An error is raised. Ambiguous matches. More than one position in the inventory matches the reducing posting; the booking method is invoked to handle this . There are a few booking methods available to handle the last case: STRICT. An error is raised. FIFO. Units from oldest (earliest) lots are selected until the reduction is complete. LIFO. Units from youngest (latest) lots are selected until the reduction is complete. AVERAGE. After every reduction, all the units of the affected commodity are merged and their new average cost is recalculated. NONE. Booking is disabled; the reducing lots is simply added to the inventory. This results in an inventory with mixed signs and only the total number of units and total cost basis are sensible numbers. Beancount has a default booking method for all accounts, which can be overridden with an option: option \"booking_method\" \"FIFO\" The default value for the booking method is \u201cSTRICT\u201d. I recommend that you leave it that way and override the booking method for specific accounts. The method can be specified for each account by adding a string to its Open directive: 2016-05-01 open Assets:Vanguard:RGAGX \"AVERAGE\" How Prices are Used \uf0c1 The short answer is that prices aren\u2019t used nor affect the booking algorithm at all. However, it is relevant to discuss what they do in this context because users invariably get confused about their interpretation. There are two use cases for prices: making conversions between commodities and tagging a reducing lot with its sale price in order to record it and optionally balance the proceeds. Commodity Conversions \uf0c1 Conversions are used to exchange one currency for another. They look like this: 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 220.00 USD @ 1.3 CAD Income:Payment -286.00 CAD For the purpose of booking it against the Checking account\u2019s inventory, the posting with the price attached to it is treated just the same as if there was no price: the Checking account simply receives a deposit of 220.00 units of USD and will match against positions of commodity \u201cUSD\u201d. The price is used only to verify that the transaction balances and ensure the double-entry accounting rule is respected (220.00 x 1.3 CAD + -286.00 CAD = 0.00). It is otherwise ignored for the purpose of modifying the inventory contents. In a sense, after the postings have been applied to the account inventories, the price is forgotten and the inventory balance retains no memory of the deposit having occurred from a conversion. Price vs. Cost Basis \uf0c1 One might wonder how the price is used if there is a cost basis specification, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest:HOOL -12 HOOL {23.00 USD} @ 24.70 USD Assets:Invest:Cash 296.40 USD Income:Invest:Gains The answer is often surprising to many users: the price is not used by the balancing algorithm if there is a cost basis; the cost basis is the number used to balance the postings. This is a useful property that allows us to compute capital gains automatically. In the previous example, the balance algorithm would sum up -12 x 23.00 + 296.40 = -20.40 USD, which is the capital gain, (24.70 - 23.00) * 12. It would complete the last posting and assign it this value. In general, the way that profits on sales are calculated is by weighing the proceedings, i.e., the cash deposits, against the cost basis of the sold lots, and this is sufficient to establish the gain difference. Also, if an augmenting posting happens to have a price annotation on it, it is also unused. The price is an annotation for your records. It remains attached to the Posting objects and if you want to make use of it somehow, you can always do that by writing some Python code. There are already two plugins which make use of this annotation: beancount.plugins.implicit_prices : This plugin takes the prices attached to the postings and automatically creates and inserts Price directives for each of them, in order to feed the global price database. beancount.plugins.sellgains : This plugin implements an additional balancing check: it uses the prices to compute the expected proceeds and weighs them against all the other postings of the transaction excluding any postings to Income accounts. In our example, it would check that (-12 x 24.70 + 296.40) = 0. This provides yet another means of verifying the correctness of your input. See the Trading with Beancount document for more details on this topic. Trades \uf0c1 The combination of acquiring some asset and selling it back is what we call a \u201ctrade.\u201d In Beancount we consider only assets with a cost basis to be the subject of trades. Since booking reductions against accumulated inventory contents happens during the booking process, this is where trades should be identified and recorded. As of now [Dec 2016], trade recording has not been implemented. Some prototypes for it have been tested previously and I believe it will be very easy to add in the near future. This will be documented here. Watch this space. The way trades will be implemented is by allowing the booking process to insert matching metadata with unique UUIDs on both the augmenting and reducing postings, in the stream of transactions. Functions and reports will be provided that are able to easily extract the pairs of postings for each reducing postings and filter those out in different ways. Ultimately, one should be able to extract a list of all trades to a table, with the acquisition and sale price, as well as other fees. Debugging Booking Issues \uf0c1 If you\u2019re experiencing difficulties in recording your sales due to the matching process, there are tools you can use to view an account\u2019s detailed inventory contents before and after applying a Transaction to it. To do this, you can use the bean-doctor command. You invoke the program providing it with the file and line number close to the Transaction you want to select, like this: bean-doctor context The resulting output will show the list of inventory contents of all affected accounts prior to the transaction being applied, including cost basis, acquisition date, and optional label fully rendered. Note that some failures are typically handled by throwing away an invalid Transaction\u2019s effects (but never quietly). From Emacs or VI, placing the cursor near a transaction and invoking the corresponding command is the easiest way to invoke the command, as it inserts the line number automatically. Appendix \uf0c1 The rest of this document delves into more technical details. You should feel free to ignore this entirely, it\u2019s not necessary reading to understand how Beancount works. Only bother if you\u2019re interested in the details. Data Representation \uf0c1 It is useful to know how positions are represented in an inventory object. A Position is essentially some number of units of a commodity with some optional information about its acquisition: Cost. Its per-unit acquisition cost (the \u201ccost basis\u201d). Date. The date at which the units were acquired. Label. Some user-specified label which can be used to refer to the lot). We often refer to these position objects as \u201clots\u201d or \u201clegs.\u201d Schematically, a position object looks like this: The structure of a Position object. There are two different types of positions, discussed in detail in the sections that follow: Simple positions. These are positions with no cost basis. The \u201ccost\u201d attribute is set to a null value. (\u201c None \u201d in Python.) Positions held at cost. These are positions with an associated cost basis and acquisition details. An Inventory is simply an accumulation of such positions, represented as a list. We sometimes talk of the ante-inventory to refer to the contents of the inventory before a transaction\u2019s postings have been applied to it, and the ex-inventory to the resulting inventory after they have been applied. A Posting is an object which is a superset of a position: in addition to units and cost, it has an associated account and an optional price attributes. If present, the price has the same type as units. It represents one of the legs of a transaction in the input. Postings imply positions, and these positions are added to inventories. We can say that a position is posted to an account. For more details on the internal data structures used in Beancount, please refer to the Design Doc which expands on this topic further. Why Booking is Not Simple \uf0c1 The complexity of the reduction process shows itself when we consider how to keep track of the cost basis of various lots. To demonstrate how this works, let us consider a simple example that we shall reuse in the different sections below: 25 shares are bought on 4/1 at $23/share. 35 shares are bought on 5/1 at $27/share. 30 shares are sold on 5/15; at that time, the price is $26/share. We\u2019ll ignore commissions for now, they don\u2019t introduce any additional complexity. In Beancount, this scenario would be recorded like this: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest:HOOL 25 HOOL {23.00 USD} Assets:Invest:Cash -575.00 USD 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {...} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains Now, the entire question revolves around which of the shares are selected to be sold. I\u2019ve rendered this input as a red ellipsis (\u201c\u2026\u201d). Whatever the user puts in that spot will be used to determine which lot we want to use. Whichever lot(s) we elect to be the ones sold will determine the amount of gains, because that is a function of the cost basis of those shares. This is why this matters. Augmentations vs. Reductions \uf0c1 The most important observation is that there are two distinct kinds of lot specifications which look very similar in the input but which are processed very differently. When we buy, as in the first transaction above, the {...} cost basis syntax provides Beancount with information about a new lot: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD We call this an \u201caugmentation\u201d to the inventory, because it will simply add a new position to it. The cost basis that you provide is attached to this position and preserved through time, in the inventory. In addition, there are a few other pieces of data you can provide for an augmenting lot. Let\u2019s have a look at all the data that can be provided in the cost spec: Cost basis. This consists in per-unit and total cost numbers\u2014which are combined into a single per-unit number\u2014and a currency. Acquisition date. A lot has an acquisition date. By default, the date attached of its parent transaction will be set as its acquisition date automatically. You may override this date by providing one. This comes in handy to handle stock splits or wash sales and preserve the original acquisition date of the replacement shares, as we\u2019ll see later. Label. You can provide a unique label for it, so that you can more easily refer to it later on, when you sell some or all of it. Merge. An indicator (a flag) that the lot should be merged (this will be useful for average cost booking which will be implemented later). For an augmenting postings , these informations must be either provided or inferred automatically. They can be provided in any order: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {2015-04-25, 27.00 USD, \"hooli-123\"} Assets:Invest:Cash -945.00 USD If you omit the date, the date of the Transaction is attached to it. If you omit the cost, the rest of the postings must be filled in such that the cost amount can be inferred from them. Since the label is optional anyway, an unspecified label field is left as a null value. You might wonder why it is allowed to override the date of an augmentation; it is useful when making cost basis adjustments to preserve the original acquisition date of a posting: You remove the posting, and then replace it with its original date and a new cost basis. Now, when we sell those shares, we will refer to the posting as a \u201creducing\u201d posting , a \u201c reduction \u201d. Note that the terms \u201caugmenting\u201d and \u201creducing\u201d are just terminology I\u2019m came up with in the context of designing how Beancount processes inventories; they\u2019re not general accounting terms. It\u2019s a \u201creduction\u201d because we\u2019re removing shares from the inventory that has been accumulated up to the date of its transaction. For example, if we were to sell 30 shares from that lot of 35 shares, the input might look like this: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {27.00 USD} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains While the input looks the same as on the augmenting posting, Beancount handles this quite differently: it looks at the state of the account\u2019s inventory before applying the transaction and finds all the positions that match the lot data you provided. It then uses the details of the matched lots as the cost basis information for the reducing posting. In this example, it would simply match all the positions which have a cost basis of $27.00. This example\u2019s inventory before the sale contains a single lot of 35 shares at $27.00, so there is a single position matching it and that lot is reduced by 30 shares and 5 shares remain. We\u2019ll see later what happens in the case of multiple lots matching the specification. Note that you could have provide other subsets of lot information to match against, like just providing the label, for example: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {\"hooli-123\"} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains This is also a valid way to identify the particular lot you wish to reduce. If you had provided a date here, it would also only be used to match against the inventory contents, to disambiguate between lots acquired at different dates, not to attach the date anywhere. And furthermore, if there was a single lot in the inventory you could have also just provided just an empty cost basis spec like this: \u201c {} \u201d. The Booking Methods section below will delve into the detail of what happens when the matches are ambiguous. In summary: When you\u2019re adding something to an account\u2019s inventory (augmenting), the information you provide is used to create a new lot and is attached to it. When you\u2019re removing from an account\u2019s inventory (reducing), the information you provide is used to filter the inventory contents to select which of the lot(s) to reduce, and information from the selected lots is filled in. Homogeneous and Mixed Inventories \uf0c1 So far in the example and in the vast majority of the examples in the documentation, \u201caugmenting\u201d means adding a positive number of shares. But in Beancount many of the accounts normally have a negative balance, e.g., liabilities accounts. It\u2019s fair to ask if it makes sense to hold a negative balance of commodities held at cost. The answer is yes. These would correspond to \u201cshort\u201d positions. Most people are unlikely to be selling short, but Beancount inventories support it. How we define \u201caugmenting\u201d is in relation to the existing balance of lots of a particular commodity. For example, if an account\u2019s inventory contains the following positions: 25 HOOL {23.00 USD, 2016-04-01} 35 HOOL {27.00 USD, 2016-05-01} Then \u201cadding\u201d means a positive number of shares. On the other hand, if the account contains only short positions, like this: -20 HOOL {23.00 USD, 2016-04-15} -10 HOOL {27.00 USD, 2016-05-15} Then \u201cadding\u201d means a negative number of shares, and \u201creducing\u201d would be carried out by matching a positive number of shares against it. The two inventories portrayed above are homogeneous in units of HOOL, that is, all of the positions have the same sign. With of the most booking methods we will see further, Beancount makes it impossible to create a non-homogeneous, or \u201cmixed,\u201d inventory. But the \u201cNONE\u201d method allows it. A mixed inventory might have the following contents, for example: 25 HOOL {23.00 USD, 2016-04-01} -20 HOOL {23.00 USD, 2016-04-15} As you may intuit, the notion of \u201caugmenting\u201d or \u201creducing\u201d only makes sense for homogeneous inventories. Original Proposal \uf0c1 If you\u2019re interested in the design doc that led to this implementation, you can find the document here . I hope the resulting implementation is simple enough yet general.","title":"How Inventories Work"},{"location":"how_inventories_work.html#how-inventories-work","text":"Martin Blais , December 2016 http://furius.ca/beancount/doc/booking This document explains how we accumulate commodities and match sales (reductions) against accumulated inventory contents.","title":"How Inventories Work"},{"location":"how_inventories_work.html#introduction","text":"Beyond the ability to track and list the postings made to each of the accounts (an operation that produces a journal of entries), one of the most common and useful operations of Beancount is to sum up the positions of arbitrary sets of postings. These aggregations are at the heart of how Beancount works, and are implemented in an object called \u201cinventory.\u201d This document explains how this aggregation process works. If you\u2019re going to track investments, it\u2019s necessary to understand what follows.","title":"Introduction"},{"location":"how_inventories_work.html#matches-booking-methods","text":"In order to get the big picture, let\u2019s walk through the various booking features by way of a simple examples. This should expose you to all the main ideas in one go.","title":"Matches & Booking Methods"},{"location":"how_inventories_work.html#simple-postings-no-cost","text":"Consider a set of simple postings to an account, e.g., 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 221.23 USD \u2026 2016-04-29 * \"ATM Withdrawal\" Assets:Bank:Checking -100.00 USD \u2026 2016-04-29 * \"Debit card payment\" Assets:Bank:Checking -45.67 USD \u2026 The inventory of the Checking account begins empty. After the first transaction, its contents are 221.23 USD. After the second, 121.23 USD. Finally, the third transaction brings this balance to 75.56 USD. This seems very natural; the numbers simply add to. It might be obvious, but note also that the numbers are allowed to change the sign (go negative).","title":"Simple Postings \u2014 No Cost"},{"location":"how_inventories_work.html#multiple-commodities","text":"An inventory may contain more than one type of commodity. It is equivalent to a mapping from commodity to some number of units. For example, 2016-07-24 * \"Dinner before leaving NYC\" Expenses:Restaurants 34.58 USD \u2026 2016-07-26 * \"Food with friends after landing\" Expenses:Restaurants 62.11 CAD \u2026 After those two transactions, the Restaurants account contains 34.58 USD and 62.11 CAD. Its contents are said to be of mixed commodities. And naturally, postings are applied to just the currencies they affect. For instance, the following transaction 2016-07-27 * \"Brunch\" Expenses:Restaurants 23.91 CAD \u2026 brings the balance of that account to 34.58 USD and 86.02 CAD. The number of units USD hasn\u2019t changed. Note that accounts may contain any number of commodities, and this is also true for commodities held at cost, which we\u2019ll see shortly. While this is made possible, I recommend that you define enough accounts to keep a single commodity in each; this can be enforced with the \u201c onecommodity \u201d plugin.","title":"Multiple Commodities"},{"location":"how_inventories_work.html#cost-basis","text":"Things get a little more hairy when we consider the tracking of investments with a cost basis. Beancount allows you to associate a cost basis and an optional label with a particular lot acquired. Consider these two purchases to an investment account: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest 25 HOOL {23.00 USD, \"first-lot\"} \u2026 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest 35 HOOL {27.00 USD} \u2026 So now, the investment account\u2019s inventory contains units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Those two lots were not merged, they are still two distinct positions in the inventory. Inventories merge lots together and adjust the units only if the commodity and all of its cost attributes exactly match. (In practice, it\u2019s pretty rare that two augmentations will have the same cost and date attributes.) Note how Beancount automatically associated the acquisition date to each lot; you can override it if desired, by adding the date similar to the optional label. this is useful for making cost basis adjustments). Postings that add to the content of an inventory are called augmentations .","title":"Cost Basis"},{"location":"how_inventories_work.html#reductions","text":"But how do we remove commodities from an inventory? You could eat away at an existing lot by selling some of it. You do this by posting a reduction to the account, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {23.00 USD} \u2026 Just to be clear, what makes this posting a reduction is the mere fact that the sign (-) is opposite that of the balance of the account (+25) for that commodity. This posting tells Beancount to find all lots with a cost basis of 23.00 USD and remove 12 units from it. The resulting inventory will be units ccy cost cost-ccy lot-date label 13 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Note how the first posting was reduced by 12 units. We didn\u2019t have to specify all of the lot\u2019s attributes, just the cost. We could have equivalently used the date to specify which lot to reduce: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 Or the label: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {\"first-lot\"} \u2026 Or a combination of these. Any combination of attributes will be matched against the inventory contents to find which lot to reduce. In fact, if the inventory happened to have just a single lot in it, you could reduce it like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026","title":"Reductions"},{"location":"how_inventories_work.html#ambiguous-matches","text":"But what happens if multiple lots match the reduction? For example, with the previous inventory containing two lots, if you wrote your sale like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Beancount wouldn\u2019t be able to figure out which lot needs to get reduced. We have an ambiguous match. Partially ambiguous matches are also possible. For example, if you have the following inventory to reduce from: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, None} 30 HOOL {25.00 USD, 2015-04-01, None} 35 HOOL {27.00 USD, 2015-05-01, None} And you attempted to reduce like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 The first two lots are selected as matches.","title":"Ambiguous Matches"},{"location":"how_inventories_work.html#strict-booking","text":"What does Beancount do with ambiguous matches? By default, it issues an error. More precisely, what happens is that Beancount invokes the booking method and it handles the ambiguous match depending on what it is set. The default booking method is \u201c STRICT \u201d and it just gives up and spits out an error, telling you you need to refine your input in order to disambiguate your inventory reduction.","title":"Strict Booking"},{"location":"how_inventories_work.html#fifo-and-lifo-booking","text":"Other booking methods are available. They can be configured using options, like this: option \"booking_method\" \"FIFO\" The \u201c FIFO \u201d method automatically selects the oldest of the matching lots up to the requested size of the reduction. For example, given our previous inventory: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Attempting to reduce 28 shares like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -28 HOOL {} \u2026 would match both lots, completely reduce the first one to zero units and remove the remaining 3 units from the second lot, to result in the following inventory: units ccy cost cost-ccy lot-date label 32 HOOL {27.00 USD, 2015-05-01, None} The \u201cLIFO\u201d method works similarly, but consumes the youngest (latest) lots first, working its way backward in time to remove the volume.","title":"FIFO and LIFO Booking"},{"location":"how_inventories_work.html#per-account-booking-method","text":"You don\u2019t have to make all accounts follow the same booking method; the option in the previous section sets the default method for all accounts. In order to override the booking method for a particular account, you can use an optional string option on the account\u2019s Open directive, like this: 2014-01-01 open Assets:Invest \"FIFO\" This allows you to treat different accounts with a different booking resolution.","title":"Per-account Booking Method"},{"location":"how_inventories_work.html#total-matches","text":"There is an exception to strict booking: if the entire inventory is being reduced by exactly the total number of units, it\u2019s clear that all the matching lots are to be selected and this is considered unambiguous, even under \u201cSTRICT\u201d booking. For example, under \u201cSTRICT\u201d booking, this reduction would empty up the previous inventory without raising an error, because there are 25 + 35 shares matching: 2015-05-15 * \"Sell all my shares\" Assets:Invest -60 HOOL {} \u2026","title":"Total Matches"},{"location":"how_inventories_work.html#average-booking","text":"Retirement accounts created by government incentive programs (such as the 401k plan in the US or the RRSP in Canada) typically consist in pre-tax money. For these types of accounts, brokers usually disregard the calculation of cost basis because the taxation is to be made upon distributing money outside the account. These accounts are often managed to the extent that they are fully invested; therefore, fees are often taken as shares of the investment portfolio, priced on the day the fee is paid out. This makes it awkward to track the cost basis of individual lots. The correct way to deal with this is to treat the number of units and the cost basis of each commodity\u2019s entire set of lots separately. For example, the following two transactions: 2016-07-28 * \"Buy some shares of retirement fund\" Assets:Invest 45.0045 VBMPX {11.11 USD} \u2026 2016-10-12 * \"Buy some shares of retirement fund\" Assets:Invest 54.5951 VBMPX {10.99 USD} \u2026 Should result in a single lot with the total number of units and the averaged cost: units ccy cost cost-ccy lot-date label 99.5996 VBMPX {11.0442 USD, 2016-07-28, None} A fee taken in this account might look like this: 2016-12-30 * \"Quarterly management fee\" Assets:Invest -1.4154 VBMPX {10.59 USD} Expenses:Fees Even with negative units the number and cost get aggregated separately: units ccy cost cost-ccy lot-date label 98.1842 VBMPX {11.0508 USD, 2016-07-28, None} This feature isn\u2019t yet supported in Beancount; it\u2019s fairly tricky to implement, and will be the subject in a minor release in the future.","title":"Average Booking"},{"location":"how_inventories_work.html#no-booking","text":"However, there is another way to deal with non-taxable accounts in the meantime: you can simply disable the booking. There is a booking method called \u201c NONE \u201d which implements a very liberal strategy which accepts any new lot.. New lots are always appended unconditionally to the inventory. Using this strategy on the transactions from the previous section would result in this inventory: units ccy cost cost-ccy lot-date label 45.0045 VBMPX {11.11 USD, 2016-07-28, None} 54.5951 VBMPX {10.99 USD, 2016-10-12, None} -1.4154 VBMPX {10.59 USD, 2016-12-30, None} Observe how the resulting inventory has a mix of signs; normally this is not allowed, but it is tolerated under this degenerate booking method. Note that under this method, the only meaningful values are the total number of units and the total or average cost amounts. The individual lots aren\u2019t really lots, they only represent the list of all postings made to that account. Note: If you are familiar with Ledger, this is the default and only booking method that it supports.","title":"No Booking"},{"location":"how_inventories_work.html#summary","text":"In summary, here\u2019s what we presented in the walkthrough. Augmentations are never problematic; they always add a new position to an existing inventory. On the other hand, reductions may result in a few outcomes: Single match. Only one position matches the reduction; it is reduced. Total match. The total number of units requested matches the total number of units of the positions matched. These positions are reduced away. No match. None of the positions matches the reducing posting. An error is raised. Ambiguous matches. More than one position in the inventory matches the reducing posting; the booking method is invoked to handle this . There are a few booking methods available to handle the last case: STRICT. An error is raised. FIFO. Units from oldest (earliest) lots are selected until the reduction is complete. LIFO. Units from youngest (latest) lots are selected until the reduction is complete. AVERAGE. After every reduction, all the units of the affected commodity are merged and their new average cost is recalculated. NONE. Booking is disabled; the reducing lots is simply added to the inventory. This results in an inventory with mixed signs and only the total number of units and total cost basis are sensible numbers. Beancount has a default booking method for all accounts, which can be overridden with an option: option \"booking_method\" \"FIFO\" The default value for the booking method is \u201cSTRICT\u201d. I recommend that you leave it that way and override the booking method for specific accounts. The method can be specified for each account by adding a string to its Open directive: 2016-05-01 open Assets:Vanguard:RGAGX \"AVERAGE\"","title":"Summary"},{"location":"how_inventories_work.html#how-prices-are-used","text":"The short answer is that prices aren\u2019t used nor affect the booking algorithm at all. However, it is relevant to discuss what they do in this context because users invariably get confused about their interpretation. There are two use cases for prices: making conversions between commodities and tagging a reducing lot with its sale price in order to record it and optionally balance the proceeds.","title":"How Prices are Used"},{"location":"how_inventories_work.html#commodity-conversions","text":"Conversions are used to exchange one currency for another. They look like this: 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 220.00 USD @ 1.3 CAD Income:Payment -286.00 CAD For the purpose of booking it against the Checking account\u2019s inventory, the posting with the price attached to it is treated just the same as if there was no price: the Checking account simply receives a deposit of 220.00 units of USD and will match against positions of commodity \u201cUSD\u201d. The price is used only to verify that the transaction balances and ensure the double-entry accounting rule is respected (220.00 x 1.3 CAD + -286.00 CAD = 0.00). It is otherwise ignored for the purpose of modifying the inventory contents. In a sense, after the postings have been applied to the account inventories, the price is forgotten and the inventory balance retains no memory of the deposit having occurred from a conversion.","title":"Commodity Conversions"},{"location":"how_inventories_work.html#price-vs-cost-basis","text":"One might wonder how the price is used if there is a cost basis specification, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest:HOOL -12 HOOL {23.00 USD} @ 24.70 USD Assets:Invest:Cash 296.40 USD Income:Invest:Gains The answer is often surprising to many users: the price is not used by the balancing algorithm if there is a cost basis; the cost basis is the number used to balance the postings. This is a useful property that allows us to compute capital gains automatically. In the previous example, the balance algorithm would sum up -12 x 23.00 + 296.40 = -20.40 USD, which is the capital gain, (24.70 - 23.00) * 12. It would complete the last posting and assign it this value. In general, the way that profits on sales are calculated is by weighing the proceedings, i.e., the cash deposits, against the cost basis of the sold lots, and this is sufficient to establish the gain difference. Also, if an augmenting posting happens to have a price annotation on it, it is also unused. The price is an annotation for your records. It remains attached to the Posting objects and if you want to make use of it somehow, you can always do that by writing some Python code. There are already two plugins which make use of this annotation: beancount.plugins.implicit_prices : This plugin takes the prices attached to the postings and automatically creates and inserts Price directives for each of them, in order to feed the global price database. beancount.plugins.sellgains : This plugin implements an additional balancing check: it uses the prices to compute the expected proceeds and weighs them against all the other postings of the transaction excluding any postings to Income accounts. In our example, it would check that (-12 x 24.70 + 296.40) = 0. This provides yet another means of verifying the correctness of your input. See the Trading with Beancount document for more details on this topic.","title":"Price vs. Cost Basis"},{"location":"how_inventories_work.html#trades","text":"The combination of acquiring some asset and selling it back is what we call a \u201ctrade.\u201d In Beancount we consider only assets with a cost basis to be the subject of trades. Since booking reductions against accumulated inventory contents happens during the booking process, this is where trades should be identified and recorded. As of now [Dec 2016], trade recording has not been implemented. Some prototypes for it have been tested previously and I believe it will be very easy to add in the near future. This will be documented here. Watch this space. The way trades will be implemented is by allowing the booking process to insert matching metadata with unique UUIDs on both the augmenting and reducing postings, in the stream of transactions. Functions and reports will be provided that are able to easily extract the pairs of postings for each reducing postings and filter those out in different ways. Ultimately, one should be able to extract a list of all trades to a table, with the acquisition and sale price, as well as other fees.","title":"Trades"},{"location":"how_inventories_work.html#debugging-booking-issues","text":"If you\u2019re experiencing difficulties in recording your sales due to the matching process, there are tools you can use to view an account\u2019s detailed inventory contents before and after applying a Transaction to it. To do this, you can use the bean-doctor command. You invoke the program providing it with the file and line number close to the Transaction you want to select, like this: bean-doctor context The resulting output will show the list of inventory contents of all affected accounts prior to the transaction being applied, including cost basis, acquisition date, and optional label fully rendered. Note that some failures are typically handled by throwing away an invalid Transaction\u2019s effects (but never quietly). From Emacs or VI, placing the cursor near a transaction and invoking the corresponding command is the easiest way to invoke the command, as it inserts the line number automatically.","title":"Debugging Booking Issues"},{"location":"how_inventories_work.html#appendix","text":"The rest of this document delves into more technical details. You should feel free to ignore this entirely, it\u2019s not necessary reading to understand how Beancount works. Only bother if you\u2019re interested in the details.","title":"Appendix"},{"location":"how_inventories_work.html#data-representation","text":"It is useful to know how positions are represented in an inventory object. A Position is essentially some number of units of a commodity with some optional information about its acquisition: Cost. Its per-unit acquisition cost (the \u201ccost basis\u201d). Date. The date at which the units were acquired. Label. Some user-specified label which can be used to refer to the lot). We often refer to these position objects as \u201clots\u201d or \u201clegs.\u201d Schematically, a position object looks like this: The structure of a Position object. There are two different types of positions, discussed in detail in the sections that follow: Simple positions. These are positions with no cost basis. The \u201ccost\u201d attribute is set to a null value. (\u201c None \u201d in Python.) Positions held at cost. These are positions with an associated cost basis and acquisition details. An Inventory is simply an accumulation of such positions, represented as a list. We sometimes talk of the ante-inventory to refer to the contents of the inventory before a transaction\u2019s postings have been applied to it, and the ex-inventory to the resulting inventory after they have been applied. A Posting is an object which is a superset of a position: in addition to units and cost, it has an associated account and an optional price attributes. If present, the price has the same type as units. It represents one of the legs of a transaction in the input. Postings imply positions, and these positions are added to inventories. We can say that a position is posted to an account. For more details on the internal data structures used in Beancount, please refer to the Design Doc which expands on this topic further.","title":"Data Representation"},{"location":"how_inventories_work.html#why-booking-is-not-simple","text":"The complexity of the reduction process shows itself when we consider how to keep track of the cost basis of various lots. To demonstrate how this works, let us consider a simple example that we shall reuse in the different sections below: 25 shares are bought on 4/1 at $23/share. 35 shares are bought on 5/1 at $27/share. 30 shares are sold on 5/15; at that time, the price is $26/share. We\u2019ll ignore commissions for now, they don\u2019t introduce any additional complexity. In Beancount, this scenario would be recorded like this: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest:HOOL 25 HOOL {23.00 USD} Assets:Invest:Cash -575.00 USD 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {...} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains Now, the entire question revolves around which of the shares are selected to be sold. I\u2019ve rendered this input as a red ellipsis (\u201c\u2026\u201d). Whatever the user puts in that spot will be used to determine which lot we want to use. Whichever lot(s) we elect to be the ones sold will determine the amount of gains, because that is a function of the cost basis of those shares. This is why this matters.","title":"Why Booking is Not Simple"},{"location":"how_inventories_work.html#augmentations-vs-reductions","text":"The most important observation is that there are two distinct kinds of lot specifications which look very similar in the input but which are processed very differently. When we buy, as in the first transaction above, the {...} cost basis syntax provides Beancount with information about a new lot: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD We call this an \u201caugmentation\u201d to the inventory, because it will simply add a new position to it. The cost basis that you provide is attached to this position and preserved through time, in the inventory. In addition, there are a few other pieces of data you can provide for an augmenting lot. Let\u2019s have a look at all the data that can be provided in the cost spec: Cost basis. This consists in per-unit and total cost numbers\u2014which are combined into a single per-unit number\u2014and a currency. Acquisition date. A lot has an acquisition date. By default, the date attached of its parent transaction will be set as its acquisition date automatically. You may override this date by providing one. This comes in handy to handle stock splits or wash sales and preserve the original acquisition date of the replacement shares, as we\u2019ll see later. Label. You can provide a unique label for it, so that you can more easily refer to it later on, when you sell some or all of it. Merge. An indicator (a flag) that the lot should be merged (this will be useful for average cost booking which will be implemented later). For an augmenting postings , these informations must be either provided or inferred automatically. They can be provided in any order: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {2015-04-25, 27.00 USD, \"hooli-123\"} Assets:Invest:Cash -945.00 USD If you omit the date, the date of the Transaction is attached to it. If you omit the cost, the rest of the postings must be filled in such that the cost amount can be inferred from them. Since the label is optional anyway, an unspecified label field is left as a null value. You might wonder why it is allowed to override the date of an augmentation; it is useful when making cost basis adjustments to preserve the original acquisition date of a posting: You remove the posting, and then replace it with its original date and a new cost basis. Now, when we sell those shares, we will refer to the posting as a \u201creducing\u201d posting , a \u201c reduction \u201d. Note that the terms \u201caugmenting\u201d and \u201creducing\u201d are just terminology I\u2019m came up with in the context of designing how Beancount processes inventories; they\u2019re not general accounting terms. It\u2019s a \u201creduction\u201d because we\u2019re removing shares from the inventory that has been accumulated up to the date of its transaction. For example, if we were to sell 30 shares from that lot of 35 shares, the input might look like this: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {27.00 USD} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains While the input looks the same as on the augmenting posting, Beancount handles this quite differently: it looks at the state of the account\u2019s inventory before applying the transaction and finds all the positions that match the lot data you provided. It then uses the details of the matched lots as the cost basis information for the reducing posting. In this example, it would simply match all the positions which have a cost basis of $27.00. This example\u2019s inventory before the sale contains a single lot of 35 shares at $27.00, so there is a single position matching it and that lot is reduced by 30 shares and 5 shares remain. We\u2019ll see later what happens in the case of multiple lots matching the specification. Note that you could have provide other subsets of lot information to match against, like just providing the label, for example: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {\"hooli-123\"} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains This is also a valid way to identify the particular lot you wish to reduce. If you had provided a date here, it would also only be used to match against the inventory contents, to disambiguate between lots acquired at different dates, not to attach the date anywhere. And furthermore, if there was a single lot in the inventory you could have also just provided just an empty cost basis spec like this: \u201c {} \u201d. The Booking Methods section below will delve into the detail of what happens when the matches are ambiguous. In summary: When you\u2019re adding something to an account\u2019s inventory (augmenting), the information you provide is used to create a new lot and is attached to it. When you\u2019re removing from an account\u2019s inventory (reducing), the information you provide is used to filter the inventory contents to select which of the lot(s) to reduce, and information from the selected lots is filled in.","title":"Augmentations vs. Reductions"},{"location":"how_inventories_work.html#homogeneous-and-mixed-inventories","text":"So far in the example and in the vast majority of the examples in the documentation, \u201caugmenting\u201d means adding a positive number of shares. But in Beancount many of the accounts normally have a negative balance, e.g., liabilities accounts. It\u2019s fair to ask if it makes sense to hold a negative balance of commodities held at cost. The answer is yes. These would correspond to \u201cshort\u201d positions. Most people are unlikely to be selling short, but Beancount inventories support it. How we define \u201caugmenting\u201d is in relation to the existing balance of lots of a particular commodity. For example, if an account\u2019s inventory contains the following positions: 25 HOOL {23.00 USD, 2016-04-01} 35 HOOL {27.00 USD, 2016-05-01} Then \u201cadding\u201d means a positive number of shares. On the other hand, if the account contains only short positions, like this: -20 HOOL {23.00 USD, 2016-04-15} -10 HOOL {27.00 USD, 2016-05-15} Then \u201cadding\u201d means a negative number of shares, and \u201creducing\u201d would be carried out by matching a positive number of shares against it. The two inventories portrayed above are homogeneous in units of HOOL, that is, all of the positions have the same sign. With of the most booking methods we will see further, Beancount makes it impossible to create a non-homogeneous, or \u201cmixed,\u201d inventory. But the \u201cNONE\u201d method allows it. A mixed inventory might have the following contents, for example: 25 HOOL {23.00 USD, 2016-04-01} -20 HOOL {23.00 USD, 2016-04-15} As you may intuit, the notion of \u201caugmenting\u201d or \u201creducing\u201d only makes sense for homogeneous inventories.","title":"Homogeneous and Mixed Inventories"},{"location":"how_inventories_work.html#original-proposal","text":"If you\u2019re interested in the design doc that led to this implementation, you can find the document here . I hope the resulting implementation is simple enough yet general.","title":"Original Proposal"},{"location":"how_we_share_expenses.html","text":"How We Share Expenses \uf0c1 This document explains how I share expenses with my wife. This is a bit involved and I\u2019ve developed a good working system, but it\u2019s not so simple for most people to do that, so I figured I would take the time to describe it to help others with designing similar processes for themselves. Context \uf0c1 We\u2019re both working professionals and have decided to share all expenses roughly 70%/30%. This is what we shoot for, this is not a hard rule, but we track it as if it were rigid, and accept it as a good approximation. We have two types of shared expenses: Shared. Common expenses between us, e.g., rent, a dinner out with friends, etc. Kyle. Expenses for our child, e.g., daycare, diapers, nanny, baby food. These get handled very differently, because we book our child's expenses as if it were a separate project on its own. (If you prefer pictures, there\u2019s a diagram at the end of this document that provides an overview of the system.) Shared Expenses \uf0c1 For our shared expenses, I maintain an account for her on my personal ledger file. This is simple and I\u2019m not too interested in maintaining a separate ledger for the totality of our common shared expenses. It\u2019s sufficient for me to just keep track of her balance on that one account. You can imagine that account like a credit card (I\u2019m the credit provider) that she pays off with transfers and also by making her own shared expenses. I just declare an Assets:US:Share:Carolyn account on my personal ledger ( blais.beancount ). My Shared Expenses \uf0c1 Whenever I incur an expense that is for both of us, I book it normally but I will tag it with #carolyn: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery This gets automatically converted to: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery 19.60 USD Assets:US:Share:Carolyn 13.06 USD share: TRUE This is done by a custom plugin I built that splits the expenses according to some rules that we have between us (see also this plugin by Akkukis ). In this example, 40% of 32.66 (13.06) gets rerouted to her account. Note that this is an asset account for me, because she owes this. Her Shared Expenses \uf0c1 We also have to keep track of the money she spends on her own for shared expenses. Since she\u2019s not a Beancount user, I\u2019ve set up a Google Sheets doc in which she can add rows to a particular sheet. This sheet has fields: Date, Description, Account, Amount. I try to keep it simple. Then, I built an extract_sheets.py script that can pull down this data automatically and it writes it to a dedicated file for this, overwriting the entire contents each time. The contents of this ledger ( carolyn.beancount ) look like this: pushtag #carolyn ... 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" Expenses:Home:Furniture 199.99 USD Assets:US:Share:Carolyn -199.99 USD ... poptag #carolyn All of those transactions are tagged as #carolyn from the pushtag/poptag directive. They get translated by the plugin to reduce the amount by the portion I\u2019m supposed to pay: 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" #carolyn Expenses:Home:Furniture 119.99 USD Assets:US:Share:Carolyn -119.99 USD share: TRUE These postings typically reduce her asset account by that much, and thus remove the portion she paid for me on these shared expenses. I generate the file with a single command like this: extract_sheets.py --tag='carolyn' --sheet='Shared Expenses' '' 'Assets:US:Share:Carolyn' > carolyn.beancount In my personal ledger, I include this file to merge those expenses with my own directives flow. I also define a query to generate a statement for her account. In blais.beancount : include \"carolyn.beancount\" 2020-01-01 query \"carolyn\" \" select date, description, position, balance from open on 2017-01-01 where account ~ 'Assets:US:Share:Carolyn' \" Reviewing & Statement \uf0c1 In order to produce a statement for her to review (and spot the occasional mistake in data entry), I simply produce a journal of that account to a CSV file and upload that to another sheet in the same Google Sheets doc she inputs her expenses: bean-query -f csv -o carolyn.csv --numberify $L run carolyn upload-to-sheets -v --docid=\"\" carolyn.csv:\"Shared Account (Read-Only)\" This makes it easy for her to eyeball all the amounts posted to her balance with me and point out errors if they occur (some always appear). Moreover, all the information is in one place\u2014it\u2019s important to keep it simple. Finally, we can use the collaborative features of Sheets to communicate, e.g. comments, highlighting text, etc. Note that this system has the benefit of correctly accruing my expenses, by reducing my portion on categories for the stuff I pay and by including her portion on categories for the stuff she pays for. Reconciling our Shared Expenses \uf0c1 Finally, in order to reconcile this account, my wife (or I, but usually she\u2019s behind) just makes a bank transfer to my account, which I book to reduce her running account balance: 2019-01-30 * \"HERBANK EXT TRNSFR; DIRECTDEP\" Assets:US:MyBank:Checking 3000 USD Assets:US:Share:Carolyn Typically she'll do this every month or two. She'll be fiddling on her laptop and ask casually \"Hey, what's my balance I can do a transfer now?\" It\u2019s all fine to relax about the particulars since the system is keeping track of everything precisely, so she can send some approximate amount, it doesn't matter, it'll post to her account. Child Expenses \uf0c1 I designed a very different system to track our child\u2019s expenses. For Kyle, I\u2019m definitely interested in tracking the total cash flows and expenses related to him, regardless of who paid for them. It\u2019s interesting to be able to ask (our ledger) a question like: \u201cHow much did his schooling cost?\u201d, for example, or \u201cHow much did we pay in diapers, in total?\u201d. Furthermore, we tend to pay for different things for Kyle, e.g. I deal with the daycare expenses (I\u2019m the accounting nerd after all, so this shouldn\u2019t be surprising), and his mother tends to buy all the clothing and prepare his food. To have a global picture of all costs related to him, we need to account for these things correctly. One interesting detail is that it would be difficult to do this with the previously described method, because I\u2019d have to have a mirror of all expense accounts I\u2019d use for him. This would make my personal ledger really ugly. For example, I want to book diapers to Expenses:Pharmacy , but I also have my own personal pharmacy expenses. So in theory, to do that I\u2019d like to have separate accounts for him and me, e.g., Expenses:Kyle:Pharmacy and Expenses:Pharmacy . This would have to be done for all the accounts we use for him. I don't do that. My Child Expenses on my Personal Ledger \uf0c1 Instead of doing that, what I want is for my personal ledger to book all the expenses I make for him to a single category: Expenses:Kyle , and to track all the detail in a shared ledger. But I still have to book all the expenses to some category, and these appear on my personal ledger, there\u2019s no option (I won\u2019t maintain a separate credit card to pay for his expenses, that would be overkill, so I have to find a way). I accomplish this by booking the expenses to my own expenses account, as usual, but tagging the transaction with #kyle: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Pharmacy And I have a different plugin that automatically makes the conversion of those transactions to: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Kyle 49.99 USD diverted_account: \"Expenses:Pharmacy\" So from my personal side, all those expenses get booked to my \u201cKyle project\u201d account. This is accomplished by the divert_expenses plugin, with this configuration: plugin \"beancount.plugins.divert_expenses\" \"{ 'tag': 'kyle', 'account': 'Expenses:Kyle' }\" The \u201cdiverted_account\u201d metadata is used to keep track of the original account, and this is used later by another script that generates a ledger file decided to my expenses for him (more below). My Child Expenses in Kyle\u2019s own Ledger \uf0c1 Now, because we\u2019re considering Kyle\u2019s expenses a project of his own, I have to maintain a set of ledgers for him. I automatically pull the transactions I described in the previous section from my personal ledger and automatically convert them to a file dedicated to his dad (me). This is done by calling the extract_tagged script: extract_tagged.py blais.beancount '#kyle' 'Income:Dad' --translate \"Expenses:Kyle:Mom=Income:Mom\" > dad.beancount The matching transaction from the previous section would look like this in it: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Income:Dad -49.99 USD Expenses:Pharmacy 49.99 USD As you can see, the script was able to reconstruct the original account name in Kyle\u2019s ledger by using the metadata saved by the divert_expenses plugin in the previous section. It also books the source of the payment to a single account showing it came from his father ( Income:Dad ). There\u2019s no need for me to keep track of which payment method I used on Kyle\u2019s side (e.g. by credit card), that\u2019s not important to him. This ledger contains the sum total of all expenses I\u2019ve made for him to date. The file gets entirely overwritten each time I run this (this is a purely generated output, no hand editing is ever done here, if I change anything I change it in my personal ledger file). Her Child Expenses \uf0c1 In order to keep track of her expenses for Kyle, we use the same method (and programs) as we use for our shared accounts in order to pull a set of \u201cCarolyn\u2019s expenses for Kyle\u201d from another sheet in the same Google Sheets doc: extract_sheets.py --sheet='Kyle Expenses (Regular)' '' 'Income:Mom' > mom.beancount This pulls in transactions that look like this: 2018-09-23 * \"SPROUT SAN FRANCISCO\" \"Clothing for Kyle 9-12 months\" Expenses:Clothing 118.30 USD Income:Mom -118.30 USD The expenses accounts are pulled from the sheet\u2014sometimes I have to go fix that by hand a bit, as they may not track precisely those from our ledger\u2014and the income shows that the contribution was made by his mother. Putting it All Together \uf0c1 Finally, we need to put together all these files. I created a top-level kyle.beancount file that simple declares all of his account categories and includes his mom and dad files. We have three files: dad.beancount mom.beancount kyle.beancount -> includes transactions from dad.beancount and mom.beancount I can then run bean-web or bean-query on kyle.beancount. There are two things which are interesting to see on that ledger: The distribution of Kyle\u2019s expenses, in other words, what\u2019s it costing us to raise a child (regardless of who pays). The difference between our contributions. In order to reconcile (2), we basically compare the balances of the Income:Mom and Income:Dad accounts. This can be done \u201cby hand\u201d, visually (using a calculator), but since Beancount\u2019s API make it so easy to pull any number from a ledger, I wrote a simple script which does that, computes the total amount of Income contributions, breaks it down to the expected numbers based on our chosen breakdown, compares it to each parent\u2019s actual contribution, and simply prints out the difference: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42906.58 Mom expected contribution: 28403.72 Mom actual contribution: 28102.72 Mom OWES Dad: 301.00 After reconciling, the final number should be zero. Reconciling the Child Ledger \uf0c1 In order to account for the difference and make the contributions to Kyle\u2019s upbringing in line with our mutual arrangement of 60%/40%, in the previous section, my wife would need to transfer $301 to me. Of course, we don\u2019t actually transfer anything in practice, I just add a virtual transfer to book the difference to her shared account on my ledger. To do this, all I have to do is insert a transaction in blais.beancount that looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Assets:US:Share:Carolyn 301.00 USD Expenses:Kyle:Mom When this gets pulled into dad.beancount (as explained previously), it looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Income:Mom -301.00 USD Income:Dad 301.00 USD After that, going back to the Kyle ledger, pulling in all the transactions again (this is done by using a Makefile) would show an updated balance of 0: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42605.58 Mom expected contribution: 28403.72 Mom actual contribution: 28403.72 Mom OWES Dad: 0.00 Summary of the System \uf0c1 Here\u2019s a diagram that puts in perspective the entire system together: I (\u201cDad\u201d) use Beancount via Emacs, exclusively. Carolyn (\u201cMom\u201d) only interacts with a single Google Sheets doc with three sheets in it. I pull in Carolyn\u2019s shared expenses from a sheet that she fills in to a ledger which gets included in my personal ledger. I also pull in her expenses for Kyle in a similar document, and from my personal ledger I generate my own expenses for Kyle. Both of these documents are merged in a top-level ledger dedicated to Kyle\u2019s expenses. I\u2019m not going to claim it\u2019s simple and that it\u2019s always up-to-date. I tend to update all of these things once/quarter and fix a few input errors each time we review it. I automate much of it via Makefiles, but frankly I don\u2019t update it frequently enough to just remember where all the moving pieces are, so every time, there\u2019s a bit of scrambling and reading my makefiles and figuring out what\u2019s what (in fact, I was motivated to write this to cement my understanding solidly for the future). Amazingly enough, it hasn\u2019t broken at all and I find all the pieces every time and make it work. And we have enough loose change between the two of us that it\u2019s never that urgent to reconcile every week. My wife tends to pay more shared expenses and I tend to pay more Kyle\u2019s expenses, so I often end up doing a transfer from one to the other to even things out and it\u2019s relatively close\u2014the shortfall in Kyle expenses makes up for my shortfall on the shared expenses. Conclusion \uf0c1 I hope this was useful to some of you trying to solve problems using the double-entry bookkeeping method. Please write questions and comments on the Beancount mailing-list, or as comments on this doc.","title":"How We Share Expenses"},{"location":"how_we_share_expenses.html#how-we-share-expenses","text":"This document explains how I share expenses with my wife. This is a bit involved and I\u2019ve developed a good working system, but it\u2019s not so simple for most people to do that, so I figured I would take the time to describe it to help others with designing similar processes for themselves.","title":"How We Share Expenses"},{"location":"how_we_share_expenses.html#context","text":"We\u2019re both working professionals and have decided to share all expenses roughly 70%/30%. This is what we shoot for, this is not a hard rule, but we track it as if it were rigid, and accept it as a good approximation. We have two types of shared expenses: Shared. Common expenses between us, e.g., rent, a dinner out with friends, etc. Kyle. Expenses for our child, e.g., daycare, diapers, nanny, baby food. These get handled very differently, because we book our child's expenses as if it were a separate project on its own. (If you prefer pictures, there\u2019s a diagram at the end of this document that provides an overview of the system.)","title":"Context"},{"location":"how_we_share_expenses.html#shared-expenses","text":"For our shared expenses, I maintain an account for her on my personal ledger file. This is simple and I\u2019m not too interested in maintaining a separate ledger for the totality of our common shared expenses. It\u2019s sufficient for me to just keep track of her balance on that one account. You can imagine that account like a credit card (I\u2019m the credit provider) that she pays off with transfers and also by making her own shared expenses. I just declare an Assets:US:Share:Carolyn account on my personal ledger ( blais.beancount ).","title":"Shared Expenses"},{"location":"how_we_share_expenses.html#my-shared-expenses","text":"Whenever I incur an expense that is for both of us, I book it normally but I will tag it with #carolyn: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery This gets automatically converted to: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery 19.60 USD Assets:US:Share:Carolyn 13.06 USD share: TRUE This is done by a custom plugin I built that splits the expenses according to some rules that we have between us (see also this plugin by Akkukis ). In this example, 40% of 32.66 (13.06) gets rerouted to her account. Note that this is an asset account for me, because she owes this.","title":"My Shared Expenses"},{"location":"how_we_share_expenses.html#her-shared-expenses","text":"We also have to keep track of the money she spends on her own for shared expenses. Since she\u2019s not a Beancount user, I\u2019ve set up a Google Sheets doc in which she can add rows to a particular sheet. This sheet has fields: Date, Description, Account, Amount. I try to keep it simple. Then, I built an extract_sheets.py script that can pull down this data automatically and it writes it to a dedicated file for this, overwriting the entire contents each time. The contents of this ledger ( carolyn.beancount ) look like this: pushtag #carolyn ... 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" Expenses:Home:Furniture 199.99 USD Assets:US:Share:Carolyn -199.99 USD ... poptag #carolyn All of those transactions are tagged as #carolyn from the pushtag/poptag directive. They get translated by the plugin to reduce the amount by the portion I\u2019m supposed to pay: 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" #carolyn Expenses:Home:Furniture 119.99 USD Assets:US:Share:Carolyn -119.99 USD share: TRUE These postings typically reduce her asset account by that much, and thus remove the portion she paid for me on these shared expenses. I generate the file with a single command like this: extract_sheets.py --tag='carolyn' --sheet='Shared Expenses' '' 'Assets:US:Share:Carolyn' > carolyn.beancount In my personal ledger, I include this file to merge those expenses with my own directives flow. I also define a query to generate a statement for her account. In blais.beancount : include \"carolyn.beancount\" 2020-01-01 query \"carolyn\" \" select date, description, position, balance from open on 2017-01-01 where account ~ 'Assets:US:Share:Carolyn' \"","title":"Her Shared Expenses"},{"location":"how_we_share_expenses.html#reviewing-statement","text":"In order to produce a statement for her to review (and spot the occasional mistake in data entry), I simply produce a journal of that account to a CSV file and upload that to another sheet in the same Google Sheets doc she inputs her expenses: bean-query -f csv -o carolyn.csv --numberify $L run carolyn upload-to-sheets -v --docid=\"\" carolyn.csv:\"Shared Account (Read-Only)\" This makes it easy for her to eyeball all the amounts posted to her balance with me and point out errors if they occur (some always appear). Moreover, all the information is in one place\u2014it\u2019s important to keep it simple. Finally, we can use the collaborative features of Sheets to communicate, e.g. comments, highlighting text, etc. Note that this system has the benefit of correctly accruing my expenses, by reducing my portion on categories for the stuff I pay and by including her portion on categories for the stuff she pays for.","title":"Reviewing & Statement"},{"location":"how_we_share_expenses.html#reconciling-our-shared-expenses","text":"Finally, in order to reconcile this account, my wife (or I, but usually she\u2019s behind) just makes a bank transfer to my account, which I book to reduce her running account balance: 2019-01-30 * \"HERBANK EXT TRNSFR; DIRECTDEP\" Assets:US:MyBank:Checking 3000 USD Assets:US:Share:Carolyn Typically she'll do this every month or two. She'll be fiddling on her laptop and ask casually \"Hey, what's my balance I can do a transfer now?\" It\u2019s all fine to relax about the particulars since the system is keeping track of everything precisely, so she can send some approximate amount, it doesn't matter, it'll post to her account.","title":"Reconciling our Shared Expenses"},{"location":"how_we_share_expenses.html#child-expenses","text":"I designed a very different system to track our child\u2019s expenses. For Kyle, I\u2019m definitely interested in tracking the total cash flows and expenses related to him, regardless of who paid for them. It\u2019s interesting to be able to ask (our ledger) a question like: \u201cHow much did his schooling cost?\u201d, for example, or \u201cHow much did we pay in diapers, in total?\u201d. Furthermore, we tend to pay for different things for Kyle, e.g. I deal with the daycare expenses (I\u2019m the accounting nerd after all, so this shouldn\u2019t be surprising), and his mother tends to buy all the clothing and prepare his food. To have a global picture of all costs related to him, we need to account for these things correctly. One interesting detail is that it would be difficult to do this with the previously described method, because I\u2019d have to have a mirror of all expense accounts I\u2019d use for him. This would make my personal ledger really ugly. For example, I want to book diapers to Expenses:Pharmacy , but I also have my own personal pharmacy expenses. So in theory, to do that I\u2019d like to have separate accounts for him and me, e.g., Expenses:Kyle:Pharmacy and Expenses:Pharmacy . This would have to be done for all the accounts we use for him. I don't do that.","title":"Child Expenses"},{"location":"how_we_share_expenses.html#my-child-expenses-on-my-personal-ledger","text":"Instead of doing that, what I want is for my personal ledger to book all the expenses I make for him to a single category: Expenses:Kyle , and to track all the detail in a shared ledger. But I still have to book all the expenses to some category, and these appear on my personal ledger, there\u2019s no option (I won\u2019t maintain a separate credit card to pay for his expenses, that would be overkill, so I have to find a way). I accomplish this by booking the expenses to my own expenses account, as usual, but tagging the transaction with #kyle: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Pharmacy And I have a different plugin that automatically makes the conversion of those transactions to: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Kyle 49.99 USD diverted_account: \"Expenses:Pharmacy\" So from my personal side, all those expenses get booked to my \u201cKyle project\u201d account. This is accomplished by the divert_expenses plugin, with this configuration: plugin \"beancount.plugins.divert_expenses\" \"{ 'tag': 'kyle', 'account': 'Expenses:Kyle' }\" The \u201cdiverted_account\u201d metadata is used to keep track of the original account, and this is used later by another script that generates a ledger file decided to my expenses for him (more below).","title":"My Child Expenses on my Personal Ledger"},{"location":"how_we_share_expenses.html#my-child-expenses-in-kyles-own-ledger","text":"Now, because we\u2019re considering Kyle\u2019s expenses a project of his own, I have to maintain a set of ledgers for him. I automatically pull the transactions I described in the previous section from my personal ledger and automatically convert them to a file dedicated to his dad (me). This is done by calling the extract_tagged script: extract_tagged.py blais.beancount '#kyle' 'Income:Dad' --translate \"Expenses:Kyle:Mom=Income:Mom\" > dad.beancount The matching transaction from the previous section would look like this in it: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Income:Dad -49.99 USD Expenses:Pharmacy 49.99 USD As you can see, the script was able to reconstruct the original account name in Kyle\u2019s ledger by using the metadata saved by the divert_expenses plugin in the previous section. It also books the source of the payment to a single account showing it came from his father ( Income:Dad ). There\u2019s no need for me to keep track of which payment method I used on Kyle\u2019s side (e.g. by credit card), that\u2019s not important to him. This ledger contains the sum total of all expenses I\u2019ve made for him to date. The file gets entirely overwritten each time I run this (this is a purely generated output, no hand editing is ever done here, if I change anything I change it in my personal ledger file).","title":"My Child Expenses in Kyle\u2019s own Ledger"},{"location":"how_we_share_expenses.html#her-child-expenses","text":"In order to keep track of her expenses for Kyle, we use the same method (and programs) as we use for our shared accounts in order to pull a set of \u201cCarolyn\u2019s expenses for Kyle\u201d from another sheet in the same Google Sheets doc: extract_sheets.py --sheet='Kyle Expenses (Regular)' '' 'Income:Mom' > mom.beancount This pulls in transactions that look like this: 2018-09-23 * \"SPROUT SAN FRANCISCO\" \"Clothing for Kyle 9-12 months\" Expenses:Clothing 118.30 USD Income:Mom -118.30 USD The expenses accounts are pulled from the sheet\u2014sometimes I have to go fix that by hand a bit, as they may not track precisely those from our ledger\u2014and the income shows that the contribution was made by his mother.","title":"Her Child Expenses"},{"location":"how_we_share_expenses.html#putting-it-all-together","text":"Finally, we need to put together all these files. I created a top-level kyle.beancount file that simple declares all of his account categories and includes his mom and dad files. We have three files: dad.beancount mom.beancount kyle.beancount -> includes transactions from dad.beancount and mom.beancount I can then run bean-web or bean-query on kyle.beancount. There are two things which are interesting to see on that ledger: The distribution of Kyle\u2019s expenses, in other words, what\u2019s it costing us to raise a child (regardless of who pays). The difference between our contributions. In order to reconcile (2), we basically compare the balances of the Income:Mom and Income:Dad accounts. This can be done \u201cby hand\u201d, visually (using a calculator), but since Beancount\u2019s API make it so easy to pull any number from a ledger, I wrote a simple script which does that, computes the total amount of Income contributions, breaks it down to the expected numbers based on our chosen breakdown, compares it to each parent\u2019s actual contribution, and simply prints out the difference: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42906.58 Mom expected contribution: 28403.72 Mom actual contribution: 28102.72 Mom OWES Dad: 301.00 After reconciling, the final number should be zero.","title":"Putting it All Together"},{"location":"how_we_share_expenses.html#reconciling-the-child-ledger","text":"In order to account for the difference and make the contributions to Kyle\u2019s upbringing in line with our mutual arrangement of 60%/40%, in the previous section, my wife would need to transfer $301 to me. Of course, we don\u2019t actually transfer anything in practice, I just add a virtual transfer to book the difference to her shared account on my ledger. To do this, all I have to do is insert a transaction in blais.beancount that looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Assets:US:Share:Carolyn 301.00 USD Expenses:Kyle:Mom When this gets pulled into dad.beancount (as explained previously), it looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Income:Mom -301.00 USD Income:Dad 301.00 USD After that, going back to the Kyle ledger, pulling in all the transactions again (this is done by using a Makefile) would show an updated balance of 0: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42605.58 Mom expected contribution: 28403.72 Mom actual contribution: 28403.72 Mom OWES Dad: 0.00","title":"Reconciling the Child Ledger"},{"location":"how_we_share_expenses.html#summary-of-the-system","text":"Here\u2019s a diagram that puts in perspective the entire system together: I (\u201cDad\u201d) use Beancount via Emacs, exclusively. Carolyn (\u201cMom\u201d) only interacts with a single Google Sheets doc with three sheets in it. I pull in Carolyn\u2019s shared expenses from a sheet that she fills in to a ledger which gets included in my personal ledger. I also pull in her expenses for Kyle in a similar document, and from my personal ledger I generate my own expenses for Kyle. Both of these documents are merged in a top-level ledger dedicated to Kyle\u2019s expenses. I\u2019m not going to claim it\u2019s simple and that it\u2019s always up-to-date. I tend to update all of these things once/quarter and fix a few input errors each time we review it. I automate much of it via Makefiles, but frankly I don\u2019t update it frequently enough to just remember where all the moving pieces are, so every time, there\u2019s a bit of scrambling and reading my makefiles and figuring out what\u2019s what (in fact, I was motivated to write this to cement my understanding solidly for the future). Amazingly enough, it hasn\u2019t broken at all and I find all the pieces every time and make it work. And we have enough loose change between the two of us that it\u2019s never that urgent to reconcile every week. My wife tends to pay more shared expenses and I tend to pay more Kyle\u2019s expenses, so I often end up doing a transfer from one to the other to even things out and it\u2019s relatively close\u2014the shortfall in Kyle expenses makes up for my shortfall on the shared expenses.","title":"Summary of the System"},{"location":"how_we_share_expenses.html#conclusion","text":"I hope this was useful to some of you trying to solve problems using the double-entry bookkeeping method. Please write questions and comments on the Beancount mailing-list, or as comments on this doc.","title":"Conclusion"},{"location":"importing_external_data.html","text":"Importing External Data in Beancount \uf0c1 Martin Blais , March 2016 http://furius.ca/beancount/doc/ingest This document is about Beancount v2; Beancount v3 is in development and uses a completely different build and installation system. For instructions on importing v3, see this document (Beangulp). Introduction The Importing Process Automating Network Downloads Typical Downloads Extracting Data from PDF Files Tools Invocation Configuration Configuring from an Input File Writing an Importer Regression Testing your Importers Generating Test Input Making Incremental Improvements Running the Tests Caching Data In-Memory Caching On-Disk Caching Organizing your Files Example Importers Cleaning Up Automatic Categorization Cleaning up Payees Future Work Related Discussion Threads Historical Note Introduction \uf0c1 This is the user\u2019s manual for the library and tools in Beancount which can help you automate the importing of external transaction data into your Beancount input file and manage the documents you download from your financial institutions\u2019 websites. The Importing Process \uf0c1 People often wonder how we do this, so let me describe candidly and in more detail what we\u2019re talking about doing here. The essence of the task at hand is to transcribe the transactions that occur in a person\u2019s entire set of accounts to a single text file: the Beancount input file. Having the entire set of transactions ingested in a single system is what we need to do in order to generate comprehensive reports about one\u2019s wealth and expenses. Some people call this \u201creconciling\u201d. We could transcribe all the transactions manually from paper statements by typing them in. However nowadays most financial institutions have a website where you can download a statement of historical transactions in a number of data formats which you can parse to output Beancount syntax for them. Importing transactions from these documents involves: Manually reviewing the transactions for correctness or even fraud; Merging new transactions with previous transactions imported from another account. For example, a payment from a bank account to pay off one\u2019s credit card will typically be imported from both the bank AND the credit card account. You must manually merge the corresponding transactions together 1 . Assigning the right category to an expense transaction Organizing your file by moving the resulting directives to the right place in your file. Verifying balances either visually or inserting a Balance directive which asserts what the final account balance should be after the new transactions are inserted. If my importers work without bugs, this is a process that takes me 30-60 minutes to update the majority of my active accounts. Less active accounts are updated every quarter or when I feel like it. I tend to do this on Saturday morning maybe twice per month, or sometimes weekly. If you maintain a well-organized input file with lots of assertions, mismatches are easily found, it\u2019s a pleasant and easy process, and after you\u2019re done generating an updated balance sheet is rewarding (I typically re-export to a Google Finance portfolio). Automating Network Downloads \uf0c1 The downloading of files is not something I automate, and Beancount provides no tools to connect to the network and fetch your files. There is simply too great a variety of protocols out there to make a meaningful contribution to this problem 2 . Given the nature of today's secure websites and the castles of JavaScript used to implement them, it would be a nightmare to implement. Web scraping is probably too much to be a worthwhile, viable solution. I manually log into the various websites with my usernames & passwords and click the right buttons to generate the downloaded files I need. These files are recognized automatically by the importers and extracting transactions and filing the documents in a well-organized directory hierarchy is automated using the tools described in this document. While I\u2019m not scripting the fetching, I think it\u2019s possible to do so on some sites. That work is left for you to implement where you think it\u2019s worth the time. Typical Downloads \uf0c1 Here\u2019s a description of the typical kinds of files involved; this describes my use case and what I\u2019ve managed to do. This should give you a qualitative sense of what\u2019s involved. Credit cards and banks provide fairly good quality historical statement downloads in OFX or CSV file formats but I need to categorize the other side of those transactions manually and merge some of the transactions together. Investment accounts provide me with great quality of processable statements and the extraction of purchase transactions is fully automated, but I need to manually edit sales transactions in order to associate the correct cost basis. Some institutions for specialized products (e.g., P2P lending) provide only PDF files and those are translated manually. Payroll stubs and vesting events are usually provided only as PDFs and I don't bother trying to extract data automatically; I transcribe those manually, keeping the input very regular and with postings in the same order as they appear on the statements. This makes it easier. Cash transactions : I have to enter those by hand. I only book non-food expenses as individual transactions directly, and for food maybe once every six months I'll count my wallet balance and insert a summarizing transaction for each month to debit away the cash account towards food to make it balance. If you do this, you end up with surprisingly little transactions to type manually, maybe just a few each week (it depends on lifestyle choices, for me this works). When I\u2019m on the go, I just note those on my phone in Google Keep and eventually transcribe them after they accumulate. Extracting Data from PDF Files \uf0c1 I've made some headway toward converting data from PDF files, which is a common need, but it's incomplete; it turns out that fully automating table extraction from PDF isn't easy in the general case. I have some code that is close to working and will release it when the time is right. Otherwise, the best FOSS solution I\u2019ve found for this is a tool called TabulaPDF but you still need to manually identify where the tables of data are located on the page; you may be able to automate some fetching using its sister project tabula-java . Nevertheless, I usually have good success with my importers grepping around PDF statements converted to ugly text in order to identify what institution they are for and extracting the date of issuance of the document. Finally, there are a number of different tools used to extract text from PDF documents, such as PDFMiner , LibreOffice , the xpdf library, the poppler library 3 and more... but none of them works consistently on all input documents; you will likely end up installing many and relying on different ones for different input files. For this reason, I\u2019m not requiring a dependency on PDF conversion tools from within Beancount. You should test what works on your specific documents and invoke those tools from your importer implementations. Tools \uf0c1 There are three Beancount tools provided to orchestrate the three stages of importing: bean-identify : Given a messy list of downloaded files (e.g. in ~/Downloads), automatically identify which of your configured importers is able to handle them and print them out. This is to be used for debugging and figuring out if your configuration is properly associating a suitable importer for each of the files you downloaded; bean-extract : Extracting transactions and statement date from each file, if at all possible. This produces some Beancount input text to be moved to your input file; bean-file : Filing away the downloaded files to a directory hierarchy which mirrors the chart of accounts, for preservation, e.g. in a personal git repo. The filenames are cleaned, the files are moved and an appropriate statement date is prepended to each of them so that Beancount may produce corresponding Document directives. Invocation \uf0c1 All tools accept the same input parameters: bean- For example, bean-extract blais.config ~/Downloads The filing tool accepts an extra option that lets the user decide where to move the files, e.g., bean-file -o ~/accounting/documents blais.config ~/Downloads Its default behavior is to move the files to the same directory as that of the configuration file. Configuration \uf0c1 The tools introduced previously orchestrate the processes, but they don\u2019t do all that much of the concrete work of groking the individual downloads themselves. They call methods on importer objects. You must provide a list of such importers; this list is the configuration for the importing process (without it, those tools don\u2019t do anything useful). For each file found, each of the importers is called to assert whether it can or cannot handle that file. If it deems that it can, methods can be called to produce a list of transactions, extract a date, or produce a cleaned up filename for the downloaded file. The configuration should be a Python3 module in which you instantiate the importers and assign the list to the module-level \u201c CONFIG \u201d variable, like this: #!/usr/bin/env python3 from myimporters.bank import acmebank from myimporters.bank import chase \u2026 CONFIG = [ acmebank.Importer(), chase.Importer(), \u2026 ] Of course, since you\u2019re crafting a Python script, you can insert whatever other code in there you like. All that matters is that this \u201c CONFIG \u201d variable refers to a list of objects which comply with the importer protocol (described in the next section). Their order does not matter. In particular, it\u2019s a good idea to write your importers as generically as possible and to parameterize them with the particular account names you use in your input file. This helps keep your code independent of the particular accounts and forces you to define logical accounts, and I\u2019ve found that this helps with clarity. Or not\u2026 At the end of the day, these importer codes live in some of your own personal place, not with Beancount. If you so desire, you can keep them as messy and unshareable as you like. Configuring from an Input File \uf0c1 An interesting idea that I haven\u2019t tested yet is to use one\u2019s Beancount input file to infer the configuration of importers. If you want to try this out and hack something, you can load your input file from the import configuration Python config, by using the API\u2019s beancount.loader.load_file() function. Writing an Importer \uf0c1 Each of the importers must comply with a particular protocol and implement at least some of its methods. The full detail of this protocol is best found in the source code itself: importer.py . The tools above will take care of finding the downloads and invoking the appropriate methods on your importer objects. Here\u2019s a brief summary of the methods you need to, or may want to, implement: name(): This method provides a unique id for each importer instance. It\u2019s convenient to be able to refer to your importers with a unique name; it gets printed out by the identification process, for instance. identify(): This method just returns true if this importer can handle the given file. You must implement this method, and all the tools invoke it to figure out the list of (file, importer) pairs. extract(): This is called to attempt to extract some Beancount directives from the file contents. It must create the directives by instantiating the objects defined in beancount.core.data and return them. file_account(): This method returns the root account associated with this importer. This is where the downloaded file will be moved by the filing script file_date(): If a date can be extracted from the statement\u2019s contents, return it here. This is useful for dated PDF statements\u2026 it\u2019s often possible using regular expressions to grep out the date from a PDF converted to text. This allows the filing script to prepend a relevant date instead of using the date when the file was downloaded (the default). file_name(): It\u2019s most convenient not to bother renaming downloaded files. Oftentimes, the files generated from your bank all have a unique name and they end up getting renamed by your browser when you download multiple ones and the names collide. This function is used for the importer to provide a \u201cnice\u201d name to file the download under. So basically, you create some module somewhere on your PYTHONPATH\u2014anywhere you like, somewhere private\u2014and you implement a class, something like this: from beancount.ingest import importer class Importer(importer.ImporterProtocol): def identify(self, file): \u2026 # Override other methods\u2026 Typically I create my importer module files in directories dedicated to each importer, so that I can place example input files all in that directory for regression testing. Regression Testing your Importers \uf0c1 I've found over time that regression testing is key to maintaining your importer code working. Importers are often written against file formats with no official spec and unexpected surprises routinely occur. For example, I have XML files with some unescaped \"&\" characters, which require a custom fix just for that bank 4 . I\u2019ve also witnessed a discount brokerage switching its dates format between MM/DD/YY and DD/MM/YY; that importer now needs to be able to handle both types. So you make the necessary adjustment, and eventually you find out that something else breaks; this isn\u2019t great. And the timing is particularly annoying: usually things break when you\u2019re trying to update your ledger: you have other things to do. The easiest, laziest and most relevant way to test those importers is to use some real data files and compare what your importer extracts from them to expected outputs. For the importers to be at least somewhat reliable, you really need to be able to reproduce the extractions on a number of real inputs. And since the inputs are so unpredictable and poorly defined, it\u2019s not practical to write exhaustive tests on what they could be. In practice, I have to make at least some fix to some of my importers every couple of months, and with this process, it only sinks about a half-hour of my time: I add the new downloaded file which causes breakage to the importer directory, I fix the code by running it there locally as a test. And I also run the tests over all the previously downloaded test inputs in that directory (old and new) to ensure my importer is still working as intended on the older files. There is some support for automating this process in beancount.ingest.regression . What we want is some routine that will list the importer\u2019s package directory, identify the input files which are to be used for testing, and generate a suite of unit tests which compares the output produced by importer methods to the contents of \u201cexpected files\u201d placed next to the test file. For example, given a package with an implementation of an importer and two sample input files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample2.csv You can place this code in the Python module (the __init__.py file): from beancount.ingest import regression \u2026 def test(): importer = Importer(...) yield from regression.compare_sample_files(importer) If your importer overrides the extract() and file_date() methods, this will generate four unit tests which get run automatically by pytest : A test which calls extract() on sample1.csv , prints the extracted entries to a string, and compares this string with the contents of sample1.csv.extract A test which calls file_date() on sample1.csv and compares the date with the one found in the sample1.csv.file_date file. A test like (1) but on sample2.csv A test like (2) but on sample2.csv Generating Test Input \uf0c1 At first, the files containing the expected outputs do not exist. When an expected output file is absent like this, the regression tests automatically generate those files from the extracted output. This would result in the following list of files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample1.csv.extract /home/joe/importers/acmebank/sample1.csv.file_date /home/joe/importers/acmebank/sample2.csv /home/joe/importers/acmebank/sample2.csv.extract /home/joe/importers/acmebank/sample2.csv.file_date You should inspect the contents of the expected output files to visually assert that they represent the contents of the downloaded files. If you run the tests again with those files present, the expected output files will be used as inputs to the tests. If the contents differ in the future, the test will fail and an error will be generated. (You can test this out now if you want, by manually editing and inserting some unexpected data in one of those files.) When you edit your source code, you can always re-run the tests to make sure it still works on those older files. When a newly downloaded file fails, you repeat the process above: You make a copy of it in that directory, fix the importer, run it, check the expected files. That\u2019s it 5 . Making Incremental Improvements \uf0c1 Sometimes I make improvements to the importers that result in more or better output being generated even in the older files, so that all the old tests will now fail. A good way to deal with this is to keep all of these files under source control, locally delete all the expected files, run the tests to regenerate new ones, and then diff against the most recent commit to check that the changes are as expected. Caching Data \uf0c1 Some of the data conversions for binary files can be costly and slow. This is usually the case for converting PDF files to text 6 . This is particularly painful, since in the process of ingesting our downloaded data we\u2019re typically going to run the tools multiple times\u2014at least twice if everything works without flaw: once to extract, twice to file\u2014and usually many more times if there are problems. For this reason, we want to cache these conversions, so that a painful 40 second PDF-to-text conversion doesn\u2019t have to be run twice, for example. Beancount aims to provide two levels of caching for conversions on downloaded files: An in-memory caching of conversions so that multiple importers requesting the same conversion runs them only once, and An on-disk caching of conversions so that multiple invocations of the tools get reused. In-Memory Caching \uf0c1 In-memory caching works like this: Your methods receive a wrapper object for a given file and invoke the wrapper\u2019s convert() method, providing a converter callable/function. class MyImporter(ImporterProtocol): ... def extract(self, file): text = file.convert(slow_convert_pdf_to_text) match = re.search(..., text) This conversion is automatically memoized: if two importers or two different methods use the same converter on the file, the conversion is only run once. This is a simple way of handling redundant conversions in-memory. Make sure to always call those through the .convert() method and share the converter functions to take advantage of this. On-Disk Caching \uf0c1 At the moment. Beancount only implements (1). On-disk caching will be implemented later. Track this ticket for status updates. Organizing your Files \uf0c1 The tools described in this document are pretty flexible in terms of letting you specify Import configuration : The Python file which provides the list of importer objects as a configuration; Importers implementation : The Python modules which implement the individual importers and their regression testing files; Downloads directory : Which directory the downloaded files are to be found in; Filing directory : Which directory the downloaded files are intended to be filed to. You can specify these from any location you want. Despite this, some people are often asking how to organize their files, so I provide a template example under beancount/examples/ingest/office , and I describe this here. I recommend that you create a Git or Mercurial 7 source-controlled repository following this structure: office \u251c\u2500\u2500 documents \u2502 \u251c\u2500\u2500 Assets \u2502 \u251c\u2500\u2500 Liabilities \u2502 \u251c\u2500\u2500 Income \u2502 \u2514\u2500\u2500 Expenses \u251c\u2500\u2500 importers \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u2514\u2500\u2500 \u2026 \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u251c\u2500\u2500 sample-download-1.csv \u2502 \u251c\u2500\u2500 sample-download-1.extract \u2502 \u251c\u2500\u2500 sample-download-1.file_date \u2502 \u2514\u2500\u2500 sample-download-1.file_name \u251c\u2500\u2500 personal.beancount \u2514\u2500\u2500 personal.import The root \u201coffice\u201d directory is your repository. It contains your ledger file (\u201c personal.beancount \u201d), your importer configuration (\u201c personal.import \u201d), your custom importers source code (\u201c importers/ \u201d) and your history of documents (\u201c documents/ \u201d), which should be well-organized by bean-file. You always run the commands from this root directory. An advantage of storing your documents in the same repository as your importers source code is that you can just symlink your regression tests to some files under the documents/ directory. You can check your configuration by running identify: bean-identify example.import ~/Downloads If it works, you can extract transactions from your downloaded files at once: bean-extract -e example.beancount example.import ~/Downloads > tmp.beancount You then open tmp.beancount and move its contents to your personal.beancount file. Once you\u2019re finished, you can stash away the downloaded files for posterity like this: bean-file example.import ~/Downloads -o documents If my importers work, I usually don\u2019t even bother opening those files. You can use the --dry-run option to test moving destinations before doing so. To run the regression tests of the custom importers, use the following command: pytest -v importers Personally, I have a Makefile in my root directory with these targets to make my life easier. Note that you will have to install \u201cpytest\u201d, which is a test runner; it is often packaged as \u201cpython3-pytest\u201d or \u201cpytest\u201d. Example Importers \uf0c1 Beyond the documentation above, I cooked up an example importer for a made-up CSV file format for a made-up investment account. See this directory . There\u2019s also an example of an importer which uses an external tool (PDFMiner2) to convert a PDF file to text to identify it and to extract the statement date from it. See this directory . Beancount also comes with some very basic generic importers. See this directory . There is a simple OFX importer that has worked for me for a long time. Though it\u2019s pretty simple, I\u2019ve used it for years, it\u2019s good enough to pull info out of most credit card accounts. There are also a couple of mixin classes you can mix into your importer implementation to make it more convenient; these are relics from the LedgerHub project\u2014you don\u2019t really need to use them\u2014which can help in the transition to it. Eventually I plan to build and provide a generic CSV file parser in this framework, as well as a parser for QIF files which should allow one to transition from Quicken to Beancount. (I need example inputs to do this; if you\u2019re comfortable sharing your file I could use it to build this, as I don\u2019t have any real input, I don\u2019t use Quicken.) It would also be nice to build a converter from GnuCash at some point; this would go here as well. Cleaning Up \uf0c1 Automatic Categorization \uf0c1 A frequently asked question and common idea from first-time users is \u201cHow do I automatically assign a category to transactions I\u2019ve imported which have only one side?\u201d For example, importing transactions from a credit card account usually provides only one posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD For which you must manually insert an Expenses posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD Expenses:Food:Grocery People often have the impression that it is time-consuming to do this. My standard answer is that while it would be fun to have, if you have a text editor with account name completion configured properly, it\u2019s a breeze to do this manually and you don\u2019t really need it. You wouldn\u2019t save much time by automating this away. And personally I like to go over each of the transactions to check what they are and sometimes add comments (e.g., who I had dinner with, what that Amazon charge was for, etc.) and that\u2019s when I categorize. It\u2019s something that could eventually be solved by letting the user provide some simple rules, or by using the history of past transactions to feed into a simple learning classifier. Beancount does not currently provide a mechanism to automatically categorize transactions. You can build this into your importer code. I want to provide a hook for the user to register a completion function that could run across all the importers where you could hook that code in. Cleaning up Payees \uf0c1 The payees that one can find in the downloads are usually ugly names: They are sometimes the legal names of the business, which often does not reflect the street name of the place you went, for various reasons. For example, I recently ate at a restaurant called the \u201cLucky Bee\u201d in New York, and the memo from the OFX file was \u201cKING BEE\u201d. The names are sometimes abbreviated or contain some crud. In the previous example, the actual memo was \u201cKING BEE NEW YO\u201d, where \u201cNEW YO\u201d is a truncated location string. The amount of ugliness is inconsistent between data sources. It would be nice to be able to normalize the payee names by translating them at import time. I think you can do most of it using some simple rules mapping regular expressions to names provided by the user. There\u2019s really no good automated way to obtain the \u201cclean name\u201d of the payee. Beancount does not provide a hook for letting you do this this yet. It will eventually. You could also build a plugin to rename those accounts when loading your ledger. I\u2019ll build that too\u2014it\u2019s easy and would result in much nicer output. Future Work \uf0c1 A list of things I\u2019d really want to add, beyond fortifying what\u2019s already there: A generic, configurable CSV importer which you can instantiate. I plan to play with this a bit and build a sniffer that could automatically figure out the role of each column. A hook to allow you to register a callback for post-processing transactions that works across all importers. Related Discussion Threads \uf0c1 Getting started; assigning accounts to bank .csv data Status of LedgerHub\u2026 how can I get started? Rekon wants your CSV files Historical Note \uf0c1 There once was a first implementation of the process described in this document. The project was called LedgerHub and has been decommissioned in February 2016, rewritten and the resulting code integrated in Beancount itself, into this beancount.ingest library. The original project was intended to include the implementation of various importers to share them with other people, but this sharing was not very successful, and so the rewrite includes only the scaffolding for building your own importers and invoking them, and only a very limited number of example importer implementations. Documents about LedgerHub are preserved, and can help you understand the origins and design choices for Beancount\u2019s importer support. They can be found here: Original design Original instructions & final status (the old version of this doc) An analysis of the reasons why it the project was terminated (post-mortem) There are essentially three conceptual modes of entering such transactions: (1) a user crafts a single transaction manually, (2) another where a user inputs the two sides as a single transaction to transfer accounts, and (3) the two separate transactions get merged into a single one automatically. These are dual modes of each other. The twist in this story is that the same transaction often posts at different dates in each of its accounts. Beancount currently [March 2016] does not support multiple dates for a single transaction\u2019s postings, but a discussion is ongoing to implement support for these input modes. See this document for more details. \u21a9 The closest to universal downloader you will find in the free software world is ofxclient for OFX files, and in the commercial world, Yodlee provides a service that connects to many financial institutions. \u21a9 The \u2018pdftotext\u2019 utility in poppler provides the useful \u2018-layout\u2019 flag which outputs a text file without mangling tables, which can be helpful in the normal case of \u2018transaction-per-row\u2019 \u21a9 After sending them a few detailed emails about this and getting no response nor seeing any change in the downloaded files, I have given up on them fixing the issue. \u21a9 As you can see, this process is partly why I don\u2019t share my importers code. It requires the storage of way too much personal data in order to keep them working. \u21a9 I don\u2019t really understand why, since opening them up for viewing is almost instant, but nearly all the tools to convert them to other formats are vastly slower. \u21a9 I personally much prefer Mercurial for the clarity of its commands and output and its extensibility, but an advantage of Git\u2019s storage model is that moving files within it comes for free (no extra copy is stored). Moving files in a Mercurial repository costs you a bit in storage space. And if you rename accounts or change how you organize your files you will end up potentially copying many large files. \u21a9","title":"Importing External Data"},{"location":"importing_external_data.html#importing-external-data-in-beancount","text":"Martin Blais , March 2016 http://furius.ca/beancount/doc/ingest This document is about Beancount v2; Beancount v3 is in development and uses a completely different build and installation system. For instructions on importing v3, see this document (Beangulp). Introduction The Importing Process Automating Network Downloads Typical Downloads Extracting Data from PDF Files Tools Invocation Configuration Configuring from an Input File Writing an Importer Regression Testing your Importers Generating Test Input Making Incremental Improvements Running the Tests Caching Data In-Memory Caching On-Disk Caching Organizing your Files Example Importers Cleaning Up Automatic Categorization Cleaning up Payees Future Work Related Discussion Threads Historical Note","title":"Importing External Data in Beancount"},{"location":"importing_external_data.html#introduction","text":"This is the user\u2019s manual for the library and tools in Beancount which can help you automate the importing of external transaction data into your Beancount input file and manage the documents you download from your financial institutions\u2019 websites.","title":"Introduction"},{"location":"importing_external_data.html#the-importing-process","text":"People often wonder how we do this, so let me describe candidly and in more detail what we\u2019re talking about doing here. The essence of the task at hand is to transcribe the transactions that occur in a person\u2019s entire set of accounts to a single text file: the Beancount input file. Having the entire set of transactions ingested in a single system is what we need to do in order to generate comprehensive reports about one\u2019s wealth and expenses. Some people call this \u201creconciling\u201d. We could transcribe all the transactions manually from paper statements by typing them in. However nowadays most financial institutions have a website where you can download a statement of historical transactions in a number of data formats which you can parse to output Beancount syntax for them. Importing transactions from these documents involves: Manually reviewing the transactions for correctness or even fraud; Merging new transactions with previous transactions imported from another account. For example, a payment from a bank account to pay off one\u2019s credit card will typically be imported from both the bank AND the credit card account. You must manually merge the corresponding transactions together 1 . Assigning the right category to an expense transaction Organizing your file by moving the resulting directives to the right place in your file. Verifying balances either visually or inserting a Balance directive which asserts what the final account balance should be after the new transactions are inserted. If my importers work without bugs, this is a process that takes me 30-60 minutes to update the majority of my active accounts. Less active accounts are updated every quarter or when I feel like it. I tend to do this on Saturday morning maybe twice per month, or sometimes weekly. If you maintain a well-organized input file with lots of assertions, mismatches are easily found, it\u2019s a pleasant and easy process, and after you\u2019re done generating an updated balance sheet is rewarding (I typically re-export to a Google Finance portfolio).","title":"The Importing Process"},{"location":"importing_external_data.html#automating-network-downloads","text":"The downloading of files is not something I automate, and Beancount provides no tools to connect to the network and fetch your files. There is simply too great a variety of protocols out there to make a meaningful contribution to this problem 2 . Given the nature of today's secure websites and the castles of JavaScript used to implement them, it would be a nightmare to implement. Web scraping is probably too much to be a worthwhile, viable solution. I manually log into the various websites with my usernames & passwords and click the right buttons to generate the downloaded files I need. These files are recognized automatically by the importers and extracting transactions and filing the documents in a well-organized directory hierarchy is automated using the tools described in this document. While I\u2019m not scripting the fetching, I think it\u2019s possible to do so on some sites. That work is left for you to implement where you think it\u2019s worth the time.","title":"Automating Network Downloads"},{"location":"importing_external_data.html#typical-downloads","text":"Here\u2019s a description of the typical kinds of files involved; this describes my use case and what I\u2019ve managed to do. This should give you a qualitative sense of what\u2019s involved. Credit cards and banks provide fairly good quality historical statement downloads in OFX or CSV file formats but I need to categorize the other side of those transactions manually and merge some of the transactions together. Investment accounts provide me with great quality of processable statements and the extraction of purchase transactions is fully automated, but I need to manually edit sales transactions in order to associate the correct cost basis. Some institutions for specialized products (e.g., P2P lending) provide only PDF files and those are translated manually. Payroll stubs and vesting events are usually provided only as PDFs and I don't bother trying to extract data automatically; I transcribe those manually, keeping the input very regular and with postings in the same order as they appear on the statements. This makes it easier. Cash transactions : I have to enter those by hand. I only book non-food expenses as individual transactions directly, and for food maybe once every six months I'll count my wallet balance and insert a summarizing transaction for each month to debit away the cash account towards food to make it balance. If you do this, you end up with surprisingly little transactions to type manually, maybe just a few each week (it depends on lifestyle choices, for me this works). When I\u2019m on the go, I just note those on my phone in Google Keep and eventually transcribe them after they accumulate.","title":"Typical Downloads"},{"location":"importing_external_data.html#extracting-data-from-pdf-files","text":"I've made some headway toward converting data from PDF files, which is a common need, but it's incomplete; it turns out that fully automating table extraction from PDF isn't easy in the general case. I have some code that is close to working and will release it when the time is right. Otherwise, the best FOSS solution I\u2019ve found for this is a tool called TabulaPDF but you still need to manually identify where the tables of data are located on the page; you may be able to automate some fetching using its sister project tabula-java . Nevertheless, I usually have good success with my importers grepping around PDF statements converted to ugly text in order to identify what institution they are for and extracting the date of issuance of the document. Finally, there are a number of different tools used to extract text from PDF documents, such as PDFMiner , LibreOffice , the xpdf library, the poppler library 3 and more... but none of them works consistently on all input documents; you will likely end up installing many and relying on different ones for different input files. For this reason, I\u2019m not requiring a dependency on PDF conversion tools from within Beancount. You should test what works on your specific documents and invoke those tools from your importer implementations.","title":"Extracting Data from PDF Files"},{"location":"importing_external_data.html#tools","text":"There are three Beancount tools provided to orchestrate the three stages of importing: bean-identify : Given a messy list of downloaded files (e.g. in ~/Downloads), automatically identify which of your configured importers is able to handle them and print them out. This is to be used for debugging and figuring out if your configuration is properly associating a suitable importer for each of the files you downloaded; bean-extract : Extracting transactions and statement date from each file, if at all possible. This produces some Beancount input text to be moved to your input file; bean-file : Filing away the downloaded files to a directory hierarchy which mirrors the chart of accounts, for preservation, e.g. in a personal git repo. The filenames are cleaned, the files are moved and an appropriate statement date is prepended to each of them so that Beancount may produce corresponding Document directives.","title":"Tools"},{"location":"importing_external_data.html#invocation","text":"All tools accept the same input parameters: bean- For example, bean-extract blais.config ~/Downloads The filing tool accepts an extra option that lets the user decide where to move the files, e.g., bean-file -o ~/accounting/documents blais.config ~/Downloads Its default behavior is to move the files to the same directory as that of the configuration file.","title":"Invocation"},{"location":"importing_external_data.html#configuration","text":"The tools introduced previously orchestrate the processes, but they don\u2019t do all that much of the concrete work of groking the individual downloads themselves. They call methods on importer objects. You must provide a list of such importers; this list is the configuration for the importing process (without it, those tools don\u2019t do anything useful). For each file found, each of the importers is called to assert whether it can or cannot handle that file. If it deems that it can, methods can be called to produce a list of transactions, extract a date, or produce a cleaned up filename for the downloaded file. The configuration should be a Python3 module in which you instantiate the importers and assign the list to the module-level \u201c CONFIG \u201d variable, like this: #!/usr/bin/env python3 from myimporters.bank import acmebank from myimporters.bank import chase \u2026 CONFIG = [ acmebank.Importer(), chase.Importer(), \u2026 ] Of course, since you\u2019re crafting a Python script, you can insert whatever other code in there you like. All that matters is that this \u201c CONFIG \u201d variable refers to a list of objects which comply with the importer protocol (described in the next section). Their order does not matter. In particular, it\u2019s a good idea to write your importers as generically as possible and to parameterize them with the particular account names you use in your input file. This helps keep your code independent of the particular accounts and forces you to define logical accounts, and I\u2019ve found that this helps with clarity. Or not\u2026 At the end of the day, these importer codes live in some of your own personal place, not with Beancount. If you so desire, you can keep them as messy and unshareable as you like.","title":"Configuration"},{"location":"importing_external_data.html#configuring-from-an-input-file","text":"An interesting idea that I haven\u2019t tested yet is to use one\u2019s Beancount input file to infer the configuration of importers. If you want to try this out and hack something, you can load your input file from the import configuration Python config, by using the API\u2019s beancount.loader.load_file() function.","title":"Configuring from an Input File"},{"location":"importing_external_data.html#writing-an-importer","text":"Each of the importers must comply with a particular protocol and implement at least some of its methods. The full detail of this protocol is best found in the source code itself: importer.py . The tools above will take care of finding the downloads and invoking the appropriate methods on your importer objects. Here\u2019s a brief summary of the methods you need to, or may want to, implement: name(): This method provides a unique id for each importer instance. It\u2019s convenient to be able to refer to your importers with a unique name; it gets printed out by the identification process, for instance. identify(): This method just returns true if this importer can handle the given file. You must implement this method, and all the tools invoke it to figure out the list of (file, importer) pairs. extract(): This is called to attempt to extract some Beancount directives from the file contents. It must create the directives by instantiating the objects defined in beancount.core.data and return them. file_account(): This method returns the root account associated with this importer. This is where the downloaded file will be moved by the filing script file_date(): If a date can be extracted from the statement\u2019s contents, return it here. This is useful for dated PDF statements\u2026 it\u2019s often possible using regular expressions to grep out the date from a PDF converted to text. This allows the filing script to prepend a relevant date instead of using the date when the file was downloaded (the default). file_name(): It\u2019s most convenient not to bother renaming downloaded files. Oftentimes, the files generated from your bank all have a unique name and they end up getting renamed by your browser when you download multiple ones and the names collide. This function is used for the importer to provide a \u201cnice\u201d name to file the download under. So basically, you create some module somewhere on your PYTHONPATH\u2014anywhere you like, somewhere private\u2014and you implement a class, something like this: from beancount.ingest import importer class Importer(importer.ImporterProtocol): def identify(self, file): \u2026 # Override other methods\u2026 Typically I create my importer module files in directories dedicated to each importer, so that I can place example input files all in that directory for regression testing.","title":"Writing an Importer"},{"location":"importing_external_data.html#regression-testing-your-importers","text":"I've found over time that regression testing is key to maintaining your importer code working. Importers are often written against file formats with no official spec and unexpected surprises routinely occur. For example, I have XML files with some unescaped \"&\" characters, which require a custom fix just for that bank 4 . I\u2019ve also witnessed a discount brokerage switching its dates format between MM/DD/YY and DD/MM/YY; that importer now needs to be able to handle both types. So you make the necessary adjustment, and eventually you find out that something else breaks; this isn\u2019t great. And the timing is particularly annoying: usually things break when you\u2019re trying to update your ledger: you have other things to do. The easiest, laziest and most relevant way to test those importers is to use some real data files and compare what your importer extracts from them to expected outputs. For the importers to be at least somewhat reliable, you really need to be able to reproduce the extractions on a number of real inputs. And since the inputs are so unpredictable and poorly defined, it\u2019s not practical to write exhaustive tests on what they could be. In practice, I have to make at least some fix to some of my importers every couple of months, and with this process, it only sinks about a half-hour of my time: I add the new downloaded file which causes breakage to the importer directory, I fix the code by running it there locally as a test. And I also run the tests over all the previously downloaded test inputs in that directory (old and new) to ensure my importer is still working as intended on the older files. There is some support for automating this process in beancount.ingest.regression . What we want is some routine that will list the importer\u2019s package directory, identify the input files which are to be used for testing, and generate a suite of unit tests which compares the output produced by importer methods to the contents of \u201cexpected files\u201d placed next to the test file. For example, given a package with an implementation of an importer and two sample input files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample2.csv You can place this code in the Python module (the __init__.py file): from beancount.ingest import regression \u2026 def test(): importer = Importer(...) yield from regression.compare_sample_files(importer) If your importer overrides the extract() and file_date() methods, this will generate four unit tests which get run automatically by pytest : A test which calls extract() on sample1.csv , prints the extracted entries to a string, and compares this string with the contents of sample1.csv.extract A test which calls file_date() on sample1.csv and compares the date with the one found in the sample1.csv.file_date file. A test like (1) but on sample2.csv A test like (2) but on sample2.csv","title":"Regression Testing your Importers"},{"location":"importing_external_data.html#generating-test-input","text":"At first, the files containing the expected outputs do not exist. When an expected output file is absent like this, the regression tests automatically generate those files from the extracted output. This would result in the following list of files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample1.csv.extract /home/joe/importers/acmebank/sample1.csv.file_date /home/joe/importers/acmebank/sample2.csv /home/joe/importers/acmebank/sample2.csv.extract /home/joe/importers/acmebank/sample2.csv.file_date You should inspect the contents of the expected output files to visually assert that they represent the contents of the downloaded files. If you run the tests again with those files present, the expected output files will be used as inputs to the tests. If the contents differ in the future, the test will fail and an error will be generated. (You can test this out now if you want, by manually editing and inserting some unexpected data in one of those files.) When you edit your source code, you can always re-run the tests to make sure it still works on those older files. When a newly downloaded file fails, you repeat the process above: You make a copy of it in that directory, fix the importer, run it, check the expected files. That\u2019s it 5 .","title":"Generating Test Input"},{"location":"importing_external_data.html#making-incremental-improvements","text":"Sometimes I make improvements to the importers that result in more or better output being generated even in the older files, so that all the old tests will now fail. A good way to deal with this is to keep all of these files under source control, locally delete all the expected files, run the tests to regenerate new ones, and then diff against the most recent commit to check that the changes are as expected.","title":"Making Incremental Improvements"},{"location":"importing_external_data.html#caching-data","text":"Some of the data conversions for binary files can be costly and slow. This is usually the case for converting PDF files to text 6 . This is particularly painful, since in the process of ingesting our downloaded data we\u2019re typically going to run the tools multiple times\u2014at least twice if everything works without flaw: once to extract, twice to file\u2014and usually many more times if there are problems. For this reason, we want to cache these conversions, so that a painful 40 second PDF-to-text conversion doesn\u2019t have to be run twice, for example. Beancount aims to provide two levels of caching for conversions on downloaded files: An in-memory caching of conversions so that multiple importers requesting the same conversion runs them only once, and An on-disk caching of conversions so that multiple invocations of the tools get reused.","title":"Caching Data"},{"location":"importing_external_data.html#in-memory-caching","text":"In-memory caching works like this: Your methods receive a wrapper object for a given file and invoke the wrapper\u2019s convert() method, providing a converter callable/function. class MyImporter(ImporterProtocol): ... def extract(self, file): text = file.convert(slow_convert_pdf_to_text) match = re.search(..., text) This conversion is automatically memoized: if two importers or two different methods use the same converter on the file, the conversion is only run once. This is a simple way of handling redundant conversions in-memory. Make sure to always call those through the .convert() method and share the converter functions to take advantage of this.","title":"In-Memory Caching"},{"location":"importing_external_data.html#on-disk-caching","text":"At the moment. Beancount only implements (1). On-disk caching will be implemented later. Track this ticket for status updates.","title":"On-Disk Caching"},{"location":"importing_external_data.html#organizing-your-files","text":"The tools described in this document are pretty flexible in terms of letting you specify Import configuration : The Python file which provides the list of importer objects as a configuration; Importers implementation : The Python modules which implement the individual importers and their regression testing files; Downloads directory : Which directory the downloaded files are to be found in; Filing directory : Which directory the downloaded files are intended to be filed to. You can specify these from any location you want. Despite this, some people are often asking how to organize their files, so I provide a template example under beancount/examples/ingest/office , and I describe this here. I recommend that you create a Git or Mercurial 7 source-controlled repository following this structure: office \u251c\u2500\u2500 documents \u2502 \u251c\u2500\u2500 Assets \u2502 \u251c\u2500\u2500 Liabilities \u2502 \u251c\u2500\u2500 Income \u2502 \u2514\u2500\u2500 Expenses \u251c\u2500\u2500 importers \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u2514\u2500\u2500 \u2026 \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u251c\u2500\u2500 sample-download-1.csv \u2502 \u251c\u2500\u2500 sample-download-1.extract \u2502 \u251c\u2500\u2500 sample-download-1.file_date \u2502 \u2514\u2500\u2500 sample-download-1.file_name \u251c\u2500\u2500 personal.beancount \u2514\u2500\u2500 personal.import The root \u201coffice\u201d directory is your repository. It contains your ledger file (\u201c personal.beancount \u201d), your importer configuration (\u201c personal.import \u201d), your custom importers source code (\u201c importers/ \u201d) and your history of documents (\u201c documents/ \u201d), which should be well-organized by bean-file. You always run the commands from this root directory. An advantage of storing your documents in the same repository as your importers source code is that you can just symlink your regression tests to some files under the documents/ directory. You can check your configuration by running identify: bean-identify example.import ~/Downloads If it works, you can extract transactions from your downloaded files at once: bean-extract -e example.beancount example.import ~/Downloads > tmp.beancount You then open tmp.beancount and move its contents to your personal.beancount file. Once you\u2019re finished, you can stash away the downloaded files for posterity like this: bean-file example.import ~/Downloads -o documents If my importers work, I usually don\u2019t even bother opening those files. You can use the --dry-run option to test moving destinations before doing so. To run the regression tests of the custom importers, use the following command: pytest -v importers Personally, I have a Makefile in my root directory with these targets to make my life easier. Note that you will have to install \u201cpytest\u201d, which is a test runner; it is often packaged as \u201cpython3-pytest\u201d or \u201cpytest\u201d.","title":"Organizing your Files"},{"location":"importing_external_data.html#example-importers","text":"Beyond the documentation above, I cooked up an example importer for a made-up CSV file format for a made-up investment account. See this directory . There\u2019s also an example of an importer which uses an external tool (PDFMiner2) to convert a PDF file to text to identify it and to extract the statement date from it. See this directory . Beancount also comes with some very basic generic importers. See this directory . There is a simple OFX importer that has worked for me for a long time. Though it\u2019s pretty simple, I\u2019ve used it for years, it\u2019s good enough to pull info out of most credit card accounts. There are also a couple of mixin classes you can mix into your importer implementation to make it more convenient; these are relics from the LedgerHub project\u2014you don\u2019t really need to use them\u2014which can help in the transition to it. Eventually I plan to build and provide a generic CSV file parser in this framework, as well as a parser for QIF files which should allow one to transition from Quicken to Beancount. (I need example inputs to do this; if you\u2019re comfortable sharing your file I could use it to build this, as I don\u2019t have any real input, I don\u2019t use Quicken.) It would also be nice to build a converter from GnuCash at some point; this would go here as well.","title":"Example Importers"},{"location":"importing_external_data.html#cleaning-up","text":"","title":"Cleaning Up"},{"location":"importing_external_data.html#automatic-categorization","text":"A frequently asked question and common idea from first-time users is \u201cHow do I automatically assign a category to transactions I\u2019ve imported which have only one side?\u201d For example, importing transactions from a credit card account usually provides only one posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD For which you must manually insert an Expenses posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD Expenses:Food:Grocery People often have the impression that it is time-consuming to do this. My standard answer is that while it would be fun to have, if you have a text editor with account name completion configured properly, it\u2019s a breeze to do this manually and you don\u2019t really need it. You wouldn\u2019t save much time by automating this away. And personally I like to go over each of the transactions to check what they are and sometimes add comments (e.g., who I had dinner with, what that Amazon charge was for, etc.) and that\u2019s when I categorize. It\u2019s something that could eventually be solved by letting the user provide some simple rules, or by using the history of past transactions to feed into a simple learning classifier. Beancount does not currently provide a mechanism to automatically categorize transactions. You can build this into your importer code. I want to provide a hook for the user to register a completion function that could run across all the importers where you could hook that code in.","title":"Automatic Categorization"},{"location":"importing_external_data.html#cleaning-up-payees","text":"The payees that one can find in the downloads are usually ugly names: They are sometimes the legal names of the business, which often does not reflect the street name of the place you went, for various reasons. For example, I recently ate at a restaurant called the \u201cLucky Bee\u201d in New York, and the memo from the OFX file was \u201cKING BEE\u201d. The names are sometimes abbreviated or contain some crud. In the previous example, the actual memo was \u201cKING BEE NEW YO\u201d, where \u201cNEW YO\u201d is a truncated location string. The amount of ugliness is inconsistent between data sources. It would be nice to be able to normalize the payee names by translating them at import time. I think you can do most of it using some simple rules mapping regular expressions to names provided by the user. There\u2019s really no good automated way to obtain the \u201cclean name\u201d of the payee. Beancount does not provide a hook for letting you do this this yet. It will eventually. You could also build a plugin to rename those accounts when loading your ledger. I\u2019ll build that too\u2014it\u2019s easy and would result in much nicer output.","title":"Cleaning up Payees"},{"location":"importing_external_data.html#future-work","text":"A list of things I\u2019d really want to add, beyond fortifying what\u2019s already there: A generic, configurable CSV importer which you can instantiate. I plan to play with this a bit and build a sniffer that could automatically figure out the role of each column. A hook to allow you to register a callback for post-processing transactions that works across all importers.","title":"Future Work"},{"location":"importing_external_data.html#related-discussion-threads","text":"Getting started; assigning accounts to bank .csv data Status of LedgerHub\u2026 how can I get started? Rekon wants your CSV files","title":"Related Discussion Threads"},{"location":"importing_external_data.html#historical-note","text":"There once was a first implementation of the process described in this document. The project was called LedgerHub and has been decommissioned in February 2016, rewritten and the resulting code integrated in Beancount itself, into this beancount.ingest library. The original project was intended to include the implementation of various importers to share them with other people, but this sharing was not very successful, and so the rewrite includes only the scaffolding for building your own importers and invoking them, and only a very limited number of example importer implementations. Documents about LedgerHub are preserved, and can help you understand the origins and design choices for Beancount\u2019s importer support. They can be found here: Original design Original instructions & final status (the old version of this doc) An analysis of the reasons why it the project was terminated (post-mortem) There are essentially three conceptual modes of entering such transactions: (1) a user crafts a single transaction manually, (2) another where a user inputs the two sides as a single transaction to transfer accounts, and (3) the two separate transactions get merged into a single one automatically. These are dual modes of each other. The twist in this story is that the same transaction often posts at different dates in each of its accounts. Beancount currently [March 2016] does not support multiple dates for a single transaction\u2019s postings, but a discussion is ongoing to implement support for these input modes. See this document for more details. \u21a9 The closest to universal downloader you will find in the free software world is ofxclient for OFX files, and in the commercial world, Yodlee provides a service that connects to many financial institutions. \u21a9 The \u2018pdftotext\u2019 utility in poppler provides the useful \u2018-layout\u2019 flag which outputs a text file without mangling tables, which can be helpful in the normal case of \u2018transaction-per-row\u2019 \u21a9 After sending them a few detailed emails about this and getting no response nor seeing any change in the downloaded files, I have given up on them fixing the issue. \u21a9 As you can see, this process is partly why I don\u2019t share my importers code. It requires the storage of way too much personal data in order to keep them working. \u21a9 I don\u2019t really understand why, since opening them up for viewing is almost instant, but nearly all the tools to convert them to other formats are vastly slower. \u21a9 I personally much prefer Mercurial for the clarity of its commands and output and its extensibility, but an advantage of Git\u2019s storage model is that moving files within it comes for free (no extra copy is stored). Moving files in a Mercurial repository costs you a bit in storage space. And if you rename accounts or change how you organize your files you will end up potentially copying many large files. \u21a9","title":"Historical Note"},{"location":"installing_beancount.html","text":"Installing Beancount \uf0c1 Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/install Instructions for downloading and installing Beancount on your computer. Releases \uf0c1 Beancount is a mature project: the first version was written in 2008. The current version of Beancount \u2014 branch \"v3\" \u2014 is stable and under continued maintenance and development. There is a mailing-list and a PyPI page. The \"master\" branch is the development branch. (Note: If you used the \"v2\" branch in the past, many of the tools have been removed from branch \"v3\" and moved to their own dedicated github projects under http://github.com/beancount . Some of the tools, e.g. bean-report, bean-web, have been deprecated.) Where to Get It \uf0c1 This is the official location for the source code: https://github.com/beancount/beancount Download it like this, by using Git to make a clone on your machine: git clone https://github.com/beancount/beancount How to Install \uf0c1 Installing Python \uf0c1 Beancount uses Python 3.8 or above, which is a pretty recent version of Python (as of this writing), and a few common library dependencies. I try to minimize dependencies, but you do have to install a few. This is very easy. First, you should have a working Python install. Install the latest stable version >=3.8 using the download from python.org . Make sure you have the development headers and libraries installed as well (e.g., the \u201cPython.h\u201d header file). For example, on a Debian/Ubuntu system you would install the python3-dev package. Beancount supports setuptools since Feb 2016, and you will need to install dependencies. You will want to have the \u201cpip3\u201d tool installed. It\u2019s installed by default along with Python3 by now\u2014test this out by invoking \u201cpython3 -m pip --help\u201d command. Installing Beancount \uf0c1 Installing Beancount using pip \uf0c1 This is the easiest way to install Beancount. You just install Beancount using sudo -H python3 -m pip install beancount This should automatically download and install all the dependencies. Note, however, that this will install the latest version that was pushed to the PyPI repository and not the very latest version available from source. Releases to PyPI are made sporadically but frequently enough not to be too far behind. Consult the CHANGES file if you\u2019d like to find out what is not included since the release date. Installing Beancount using pip from the Repository \uf0c1 You can also use pip to install Beancount from its source code repository directly: sudo -H python3 -m pip install git+https://github.com/beancount/beancount#egg=beancount Installing Beancount from Source \uf0c1 Installing from source offers the advantage of providing you with the very latest version of the stable branch (\u201cmaster\u201d). The master branch should be as stable as the released version most of the time. Get the source code from the official repository: git clone https://github.com/beancount/beancount You might need to install some non-Python library dependencies, such as bison and flex and perhaps a few more (you'll find out when you try to build). It should be obvious what\u2019s missing. If on Ubuntu, use apt get to install those. If installing on Windows, see the Windows section below. Build and Install Beancount from source using pip3 \uf0c1 You can then install all the dependencies and Beancount itself using pip: cd beancount sudo -H python3 -m pip install . Installing for Development \uf0c1 If you want to execute the source in-place for making changes to it, you can use the setuptools \u201cdevelop\u201d command to point to it: cd beancount sudo python3 setup.py develop Warning: This modifies a .pth file in your Python installation to point to the path to your clone. You may or may not want this. I don't do this myself; the way I work with it is the \"old school\" way; I just build it locally and modify my shell's environment to find its libraries. You build it like this: cd beancount python3 setup.py build_ext -i # or \"make build\" and then both the PATH and PYTHONPATH environment variables need to be updated for it like this: export PATH=$PATH:/path/to/beancount/bin export PYTHONPATH=$PYTHONPATH:/path/to/beancount Dependencies for Development \uf0c1 Beancount needs a few more tools for development. If you\u2019re reading this, you\u2019re a developer, so I\u2019ll assume you can figure out how to install packages, skip the detail, and just list what you might need: pytest: for unit tests ruff: for linting GNU flex : This lexer generator is needed if you intend to modify the lexer. It generated C code that chops up the input files into tokens for the parser to consume. GNU bison : This old-school parser generator is needed if you intend to modify the grammar. It generates C code that parses the tokens generated by flex. (I like using old tools like this because they are pretty low on dependencies, just C code. It should work everywhere.)` python-dateutil : to run the beancount.scripts.example example generator script. Installing from Distribution Packages \uf0c1 Various distributions may package Beancount. Here are links to those known to exist: Arch: https://aur.archlinux.org/packages/beancount/ Windows Installation \uf0c1 Native \uf0c1 Installing this package by pip requires compiling some C++ code during the installation procedure which is only possible if an appropriate compiler is available on the computer, otherwise you will receive an error message about missing vsvarsall.bat or cl.exe . To be able to compile C++ code for Python you will need to install the same major version of the C++ compiler as your Python installation was compiled with. By running python in a console and looking for a text similar to [MSC v.1900 64 bit (AMD64)] you can determine which compiler was used for your particular Python distribution. In this example it is v.1900 . Using this number find the required Visual C++ version here . Since different versions seem to be compatible as long as the first two digits are the same you can in theory use any Visual C++ compiler between 1900 and 1999. According to my experience both Python 3.8 and 3.6 was compiled with MSC v.1900 so you can do either of the following to satisfy this requirement: Install the standalone Build Tools for Visual Studio 2017 or Install the standalone Visual C++ Build Tools 2015 or Modify an existing Visual Studio 2017 installation Start the Visual Studio 2017 installer from Add or remove programs Select Individual components Check VC++ 2017 version 15.9 v14.16 latest v141 tools or newer under Compilers, build tools, and runtimes Install Visual Studio 2019 add C++ build tools: C++ core features, MSVC v142 build tools If cl.exe is not in your path after installation, run Developer Command Prompt for Visual Studio and run the commands there. With Cygwin \uf0c1 Windows installation is, of course, a bit different. It\u2019s a breeze if you use Cygwin. You just have to prep your machine first. Here\u2019s how. Install the latest Cygwin . This may take a while (it downloads a lot of stuff), but it is well worth it in any case. But before you kick off the install, make sure the following packages are all manually enabled in the interface provided by setup.exe (they\u2019re not selected by default): python3 python3-devel python3-setuptools git make gcc-core flex bison Start a new Cygwin bash shell (there should be a new icon on your desktop) and install the pip3 installer tool by running this command: easy_install-3.4 pip Make sure you invoke the version of easy_install which matches your Python version, e.g. easy_install-3.8 if you have Python 3.8 installed, or more. At this point, you should be able to follow the instructions from the previous sections as is, starting from \u201cInstall Beancount using pip\u201d. With WSL \uf0c1 The newly released Windows 10 Anniversary Update brings WSL 'Windows Subsystem for Linux' with bash on Ubuntu on Windows (installation instructions and more at https://msdn.microsoft.com/commandline/wsl/about ). This makes beancount installation easy, from bash: sudo apt-get install python3-pip sudo pip3 install m3-cdecimal sudo pip3 install beancount --pre This is not totally \u201cWindows compatible\u201d, as it is running in a pico-process, but provides a convenient way to get the Linux command-line experience on Windows. (Contrib: willwray) Checking your Install \uf0c1 You should be able to run the binaries from this document . For example, running bean-check should produce something like this: $ bean-check usage: bean-check [-h] [-v] filename bean-check: error: the following arguments are required: filename If this works, you can now go to the tutorial and begin learning how Beancount works. Reporting Problems \uf0c1 If you need to report a problem, either send email on the mailing-list or file a ticket on Github. Running the following command lists the presence and versions of dependencies installed on your computer and it might be useful to include the output of this command in your bug report: python3 -m beancount.scripts.deps Editor Support \uf0c1 There is support for some editors available: Emacs support is provided in a separate repo . See the Getting Started text for installation instruction. Support for editing with Sublime has been contributed by Martin Andreas Andersen . See his github repo . Support for editing with Vim has been contributed by Nathan Grigg . See his GitHub repo . Visual Studio Code currently has two extensions available. Both have been tested on Linux. Beancount , with syntax checking (bean-check) and support for accounts, currencies, etc. It not only allows selecting existing open accounts but also displays their balance and other metadata. Quite helpful! Beancount Formatter , which can format the whole document, aligning the numbers, etc. using bean-format. If You Have Problems \uf0c1 If you run into any installation problems, file a ticket or email the mailing-list . Post-Installation Usage \uf0c1 Normally the scripts located under beancount/bin should be automatically installed somewhere in your executable path. For example, you should be able to just run \u201cbean-check\u201d on the command-line.","title":"Installing Beancount"},{"location":"installing_beancount.html#installing-beancount","text":"Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/install Instructions for downloading and installing Beancount on your computer.","title":"Installing Beancount"},{"location":"installing_beancount.html#releases","text":"Beancount is a mature project: the first version was written in 2008. The current version of Beancount \u2014 branch \"v3\" \u2014 is stable and under continued maintenance and development. There is a mailing-list and a PyPI page. The \"master\" branch is the development branch. (Note: If you used the \"v2\" branch in the past, many of the tools have been removed from branch \"v3\" and moved to their own dedicated github projects under http://github.com/beancount . Some of the tools, e.g. bean-report, bean-web, have been deprecated.)","title":"Releases"},{"location":"installing_beancount.html#where-to-get-it","text":"This is the official location for the source code: https://github.com/beancount/beancount Download it like this, by using Git to make a clone on your machine: git clone https://github.com/beancount/beancount","title":"Where to Get It"},{"location":"installing_beancount.html#how-to-install","text":"","title":"How to Install"},{"location":"installing_beancount.html#installing-python","text":"Beancount uses Python 3.8 or above, which is a pretty recent version of Python (as of this writing), and a few common library dependencies. I try to minimize dependencies, but you do have to install a few. This is very easy. First, you should have a working Python install. Install the latest stable version >=3.8 using the download from python.org . Make sure you have the development headers and libraries installed as well (e.g., the \u201cPython.h\u201d header file). For example, on a Debian/Ubuntu system you would install the python3-dev package. Beancount supports setuptools since Feb 2016, and you will need to install dependencies. You will want to have the \u201cpip3\u201d tool installed. It\u2019s installed by default along with Python3 by now\u2014test this out by invoking \u201cpython3 -m pip --help\u201d command.","title":"Installing Python"},{"location":"installing_beancount.html#installing-beancount_1","text":"","title":"Installing Beancount"},{"location":"installing_beancount.html#installing-beancount-using-pip","text":"This is the easiest way to install Beancount. You just install Beancount using sudo -H python3 -m pip install beancount This should automatically download and install all the dependencies. Note, however, that this will install the latest version that was pushed to the PyPI repository and not the very latest version available from source. Releases to PyPI are made sporadically but frequently enough not to be too far behind. Consult the CHANGES file if you\u2019d like to find out what is not included since the release date.","title":"Installing Beancount using pip"},{"location":"installing_beancount.html#installing-beancount-using-pip-from-the-repository","text":"You can also use pip to install Beancount from its source code repository directly: sudo -H python3 -m pip install git+https://github.com/beancount/beancount#egg=beancount","title":"Installing Beancount using pip from the Repository"},{"location":"installing_beancount.html#installing-beancount-from-source","text":"Installing from source offers the advantage of providing you with the very latest version of the stable branch (\u201cmaster\u201d). The master branch should be as stable as the released version most of the time. Get the source code from the official repository: git clone https://github.com/beancount/beancount You might need to install some non-Python library dependencies, such as bison and flex and perhaps a few more (you'll find out when you try to build). It should be obvious what\u2019s missing. If on Ubuntu, use apt get to install those. If installing on Windows, see the Windows section below.","title":"Installing Beancount from Source"},{"location":"installing_beancount.html#build-and-install-beancount-from-source-using-pip3","text":"You can then install all the dependencies and Beancount itself using pip: cd beancount sudo -H python3 -m pip install .","title":"Build and Install Beancount from source using pip3"},{"location":"installing_beancount.html#installing-for-development","text":"If you want to execute the source in-place for making changes to it, you can use the setuptools \u201cdevelop\u201d command to point to it: cd beancount sudo python3 setup.py develop Warning: This modifies a .pth file in your Python installation to point to the path to your clone. You may or may not want this. I don't do this myself; the way I work with it is the \"old school\" way; I just build it locally and modify my shell's environment to find its libraries. You build it like this: cd beancount python3 setup.py build_ext -i # or \"make build\" and then both the PATH and PYTHONPATH environment variables need to be updated for it like this: export PATH=$PATH:/path/to/beancount/bin export PYTHONPATH=$PYTHONPATH:/path/to/beancount","title":"Installing for Development"},{"location":"installing_beancount.html#dependencies-for-development","text":"Beancount needs a few more tools for development. If you\u2019re reading this, you\u2019re a developer, so I\u2019ll assume you can figure out how to install packages, skip the detail, and just list what you might need: pytest: for unit tests ruff: for linting GNU flex : This lexer generator is needed if you intend to modify the lexer. It generated C code that chops up the input files into tokens for the parser to consume. GNU bison : This old-school parser generator is needed if you intend to modify the grammar. It generates C code that parses the tokens generated by flex. (I like using old tools like this because they are pretty low on dependencies, just C code. It should work everywhere.)` python-dateutil : to run the beancount.scripts.example example generator script.","title":"Dependencies for Development"},{"location":"installing_beancount.html#installing-from-distribution-packages","text":"Various distributions may package Beancount. Here are links to those known to exist: Arch: https://aur.archlinux.org/packages/beancount/","title":"Installing from Distribution Packages"},{"location":"installing_beancount.html#windows-installation","text":"","title":"Windows Installation"},{"location":"installing_beancount.html#native","text":"Installing this package by pip requires compiling some C++ code during the installation procedure which is only possible if an appropriate compiler is available on the computer, otherwise you will receive an error message about missing vsvarsall.bat or cl.exe . To be able to compile C++ code for Python you will need to install the same major version of the C++ compiler as your Python installation was compiled with. By running python in a console and looking for a text similar to [MSC v.1900 64 bit (AMD64)] you can determine which compiler was used for your particular Python distribution. In this example it is v.1900 . Using this number find the required Visual C++ version here . Since different versions seem to be compatible as long as the first two digits are the same you can in theory use any Visual C++ compiler between 1900 and 1999. According to my experience both Python 3.8 and 3.6 was compiled with MSC v.1900 so you can do either of the following to satisfy this requirement: Install the standalone Build Tools for Visual Studio 2017 or Install the standalone Visual C++ Build Tools 2015 or Modify an existing Visual Studio 2017 installation Start the Visual Studio 2017 installer from Add or remove programs Select Individual components Check VC++ 2017 version 15.9 v14.16 latest v141 tools or newer under Compilers, build tools, and runtimes Install Visual Studio 2019 add C++ build tools: C++ core features, MSVC v142 build tools If cl.exe is not in your path after installation, run Developer Command Prompt for Visual Studio and run the commands there.","title":"Native"},{"location":"installing_beancount.html#with-cygwin","text":"Windows installation is, of course, a bit different. It\u2019s a breeze if you use Cygwin. You just have to prep your machine first. Here\u2019s how. Install the latest Cygwin . This may take a while (it downloads a lot of stuff), but it is well worth it in any case. But before you kick off the install, make sure the following packages are all manually enabled in the interface provided by setup.exe (they\u2019re not selected by default): python3 python3-devel python3-setuptools git make gcc-core flex bison Start a new Cygwin bash shell (there should be a new icon on your desktop) and install the pip3 installer tool by running this command: easy_install-3.4 pip Make sure you invoke the version of easy_install which matches your Python version, e.g. easy_install-3.8 if you have Python 3.8 installed, or more. At this point, you should be able to follow the instructions from the previous sections as is, starting from \u201cInstall Beancount using pip\u201d.","title":"With Cygwin"},{"location":"installing_beancount.html#with-wsl","text":"The newly released Windows 10 Anniversary Update brings WSL 'Windows Subsystem for Linux' with bash on Ubuntu on Windows (installation instructions and more at https://msdn.microsoft.com/commandline/wsl/about ). This makes beancount installation easy, from bash: sudo apt-get install python3-pip sudo pip3 install m3-cdecimal sudo pip3 install beancount --pre This is not totally \u201cWindows compatible\u201d, as it is running in a pico-process, but provides a convenient way to get the Linux command-line experience on Windows. (Contrib: willwray)","title":"With WSL"},{"location":"installing_beancount.html#checking-your-install","text":"You should be able to run the binaries from this document . For example, running bean-check should produce something like this: $ bean-check usage: bean-check [-h] [-v] filename bean-check: error: the following arguments are required: filename If this works, you can now go to the tutorial and begin learning how Beancount works.","title":"Checking your Install"},{"location":"installing_beancount.html#reporting-problems","text":"If you need to report a problem, either send email on the mailing-list or file a ticket on Github. Running the following command lists the presence and versions of dependencies installed on your computer and it might be useful to include the output of this command in your bug report: python3 -m beancount.scripts.deps","title":"Reporting Problems"},{"location":"installing_beancount.html#editor-support","text":"There is support for some editors available: Emacs support is provided in a separate repo . See the Getting Started text for installation instruction. Support for editing with Sublime has been contributed by Martin Andreas Andersen . See his github repo . Support for editing with Vim has been contributed by Nathan Grigg . See his GitHub repo . Visual Studio Code currently has two extensions available. Both have been tested on Linux. Beancount , with syntax checking (bean-check) and support for accounts, currencies, etc. It not only allows selecting existing open accounts but also displays their balance and other metadata. Quite helpful! Beancount Formatter , which can format the whole document, aligning the numbers, etc. using bean-format.","title":"Editor Support"},{"location":"installing_beancount.html#if-you-have-problems","text":"If you run into any installation problems, file a ticket or email the mailing-list .","title":"If You Have Problems"},{"location":"installing_beancount.html#post-installation-usage","text":"Normally the scripts located under beancount/bin should be automatically installed somewhere in your executable path. For example, you should be able to just run \u201cbean-check\u201d on the command-line.","title":"Post-Installation Usage"},{"location":"installing_beancount_v3.html","text":"Installing Beancount (C++ version) \uf0c1 Martin Blais - July 2020 http://furius.ca/beancount/doc/v3-install Instructions for downloading and running Beancount v3 (in development) on your computer. For v2, see this document instead: Beancount - Install (v2) This document is about Beancount v3, an experimental in-development version (as of July 2020); Instructions for building the stable version (Beancount v2) can be found in this other document . Setup Python \uf0c1 Python dependencies are still required to run programs. pip install \u2013r requirements/dev.txt Building with Bazel \uf0c1 Warning: This is an experimental development branch. Do not expect everything to be polished perfectly. Bazel Dependencies \uf0c1 Beancount v3 uses the Bazel build system, which for the most part insulates you from local installs and dependencies from your computer. The dependencies to install are: Bazel itself. Follow instructions on https://bazel.build/ A C++ compiler. Either g++ or clang works. I'm using clang-11. A Python runtime (version 3.8 or above). Install from your distribution. Bazel will download and compile all the libraries it requires itself (even the code generators, e.g., Bison), building them at their precise versions as specified in the build, so you will not have to worry about them. Building & Testing \uf0c1 Simply run the following command: bazel build //bin:all There is currently no installation script, you have to run from source. You can run individual programs (e.g. bean-check) with this command: bazel run //bin:bean_check -- /path/to/ledger.beancount Or if you don't care to automatically rebuild modified code, like this: ./bazel-bin/bin/bean_check /path/to/ledger.beancount Development \uf0c1 You can run all the unit tests like this: bazel test //... Because Bazel has a detailed account of all dependencies, re-running this command after modifying code will result in only the touched targets being re-tested; this makes iterative development with testing a bit more fun. Another advantage is that since all the libraries the build depends on are downloaded and built, while this can be slow on the first build, it allows us to use very recently released versions of the code we depend on. Targets are defined in BUILD files local to their directories. All the build rules can be found under //third_party. Ingestion \uf0c1 The ingestion code involves importing code that lives outside the repository. Bazel binaries are self-contained and will fail to import modules that haven't been declared as dependencies, so running the //bin:bean_extract target, for example, probably won't work. This does not work yet (short of building your import configuration as a py_binary() target that would explicitly link to Beancount). This is doable without writing much Bazel code by defining a suitable WORKSPACE file that fetches the rules from it. I haven't produced an example of this yet (TBD). As a workaround, you can set your PYTHONPATH to import from the source location and create a symlink to the parser .so file beforehand. You can do it like this: make bazel-link TBD \uf0c1 A few build integration tasks remain to be done: pytype is not supported yet. pylint is not integrated in the build either. Installation for development with meson \uf0c1 Note : this section is updated base on the following discussion: https://groups.google.com/g/beancount/c/7ppbyz_5B5w The Bazel build for Beancount v3 builds some experimental C++ code that is at the time of writing (9 March 2024) not yet used for anything else than a technology demonstration. In \u201cproduction\u201d v3 uses meson-python to build the extension modules and pack them up in a Python wheel. This is what is used by pip during installation. This section describes installation processes for the purposes of development beancount v3 python code, rather than experimenting with C++ code. On Linux \uf0c1 Tested with python3.12 on Ubuntu Related links https://mesonbuild.com/meson-python/how-to-guides/editable-installs.html git clone https://github.com/beancount/beancount.git Create and activate virtual environment python3.12 -m venv beancount/venv . beancount/venv/bin/activate cd beancount Install required packages. python -m pip install meson-python meson ninja Install beancount in editable mode with no build isolation python -m pip install --no-build-isolation --editable . Note: There is an opinion that --no-build-isolation option is not needed, but it was also mentioned that the installation wasn\u2019t working without this option. Also the documentation suggests that this option is needed. This may depend on the type of Linux environment Install pytest python -m pip install pytest Run the tests and make sure everything is ok: pytest --import-mode=importlib beancount/ On Windows \uf0c1 Tested on 64 bit Windows 10 Pro prerequisites Install Microsoft Visual Studio (tested on v 2022) Procedure git clone https://github.com/beancount/beancount.git cd beancount If running x64 bit Windows, start the \"x64 Native Tools Command Prompt for VS 20XX\". To do this press and release the Windows keys and type x64 Open this prompt Go to the directory, where beancount is installed C:\\Program Files\\Microsoft Visual Studio\\2022\\Community>cd C:\\_code\\t\\beancount C:\\_code\\t\\beancount> Activate the virtual environment C:\\_code\\t\\beancount>venv\\Scripts\\Activate (venv) C:\\_code\\t\\beancount> Instal required packages (venv) C:\\_code\\t\\beancount>py -m pip install meson-python meson ninja Install beancount in editable mode. Unlike on Linux the --no-build-isolation on Windows from one side throws some errors from the other side does not seem to be needed (venv) C:\\_code\\t\\beancount> py -m pip install --editable . Install pytest (venv) C:\\_code\\t\\beancount>py -m pip install pytest Run tests (venv) C:\\_code\\t\\beancount>pytest --import-mode=importlib beancount Note: some of the tests on Windows fail, but this is due to general portability issue","title":"Installing Beancount"},{"location":"installing_beancount_v3.html#installing-beancount-c-version","text":"Martin Blais - July 2020 http://furius.ca/beancount/doc/v3-install Instructions for downloading and running Beancount v3 (in development) on your computer. For v2, see this document instead: Beancount - Install (v2) This document is about Beancount v3, an experimental in-development version (as of July 2020); Instructions for building the stable version (Beancount v2) can be found in this other document .","title":"Installing Beancount (C++ version)"},{"location":"installing_beancount_v3.html#setup-python","text":"Python dependencies are still required to run programs. pip install \u2013r requirements/dev.txt","title":"Setup Python"},{"location":"installing_beancount_v3.html#building-with-bazel","text":"Warning: This is an experimental development branch. Do not expect everything to be polished perfectly.","title":"Building with Bazel"},{"location":"installing_beancount_v3.html#bazel-dependencies","text":"Beancount v3 uses the Bazel build system, which for the most part insulates you from local installs and dependencies from your computer. The dependencies to install are: Bazel itself. Follow instructions on https://bazel.build/ A C++ compiler. Either g++ or clang works. I'm using clang-11. A Python runtime (version 3.8 or above). Install from your distribution. Bazel will download and compile all the libraries it requires itself (even the code generators, e.g., Bison), building them at their precise versions as specified in the build, so you will not have to worry about them.","title":"Bazel Dependencies"},{"location":"installing_beancount_v3.html#building-testing","text":"Simply run the following command: bazel build //bin:all There is currently no installation script, you have to run from source. You can run individual programs (e.g. bean-check) with this command: bazel run //bin:bean_check -- /path/to/ledger.beancount Or if you don't care to automatically rebuild modified code, like this: ./bazel-bin/bin/bean_check /path/to/ledger.beancount","title":"Building & Testing"},{"location":"installing_beancount_v3.html#development","text":"You can run all the unit tests like this: bazel test //... Because Bazel has a detailed account of all dependencies, re-running this command after modifying code will result in only the touched targets being re-tested; this makes iterative development with testing a bit more fun. Another advantage is that since all the libraries the build depends on are downloaded and built, while this can be slow on the first build, it allows us to use very recently released versions of the code we depend on. Targets are defined in BUILD files local to their directories. All the build rules can be found under //third_party.","title":"Development"},{"location":"installing_beancount_v3.html#ingestion","text":"The ingestion code involves importing code that lives outside the repository. Bazel binaries are self-contained and will fail to import modules that haven't been declared as dependencies, so running the //bin:bean_extract target, for example, probably won't work. This does not work yet (short of building your import configuration as a py_binary() target that would explicitly link to Beancount). This is doable without writing much Bazel code by defining a suitable WORKSPACE file that fetches the rules from it. I haven't produced an example of this yet (TBD). As a workaround, you can set your PYTHONPATH to import from the source location and create a symlink to the parser .so file beforehand. You can do it like this: make bazel-link","title":"Ingestion"},{"location":"installing_beancount_v3.html#tbd","text":"A few build integration tasks remain to be done: pytype is not supported yet. pylint is not integrated in the build either.","title":"TBD"},{"location":"installing_beancount_v3.html#installation-for-development-with-meson","text":"Note : this section is updated base on the following discussion: https://groups.google.com/g/beancount/c/7ppbyz_5B5w The Bazel build for Beancount v3 builds some experimental C++ code that is at the time of writing (9 March 2024) not yet used for anything else than a technology demonstration. In \u201cproduction\u201d v3 uses meson-python to build the extension modules and pack them up in a Python wheel. This is what is used by pip during installation. This section describes installation processes for the purposes of development beancount v3 python code, rather than experimenting with C++ code.","title":"Installation for development with meson"},{"location":"installing_beancount_v3.html#on-linux","text":"Tested with python3.12 on Ubuntu Related links https://mesonbuild.com/meson-python/how-to-guides/editable-installs.html git clone https://github.com/beancount/beancount.git Create and activate virtual environment python3.12 -m venv beancount/venv . beancount/venv/bin/activate cd beancount Install required packages. python -m pip install meson-python meson ninja Install beancount in editable mode with no build isolation python -m pip install --no-build-isolation --editable . Note: There is an opinion that --no-build-isolation option is not needed, but it was also mentioned that the installation wasn\u2019t working without this option. Also the documentation suggests that this option is needed. This may depend on the type of Linux environment Install pytest python -m pip install pytest Run the tests and make sure everything is ok: pytest --import-mode=importlib beancount/","title":"On Linux"},{"location":"installing_beancount_v3.html#on-windows","text":"Tested on 64 bit Windows 10 Pro prerequisites Install Microsoft Visual Studio (tested on v 2022) Procedure git clone https://github.com/beancount/beancount.git cd beancount If running x64 bit Windows, start the \"x64 Native Tools Command Prompt for VS 20XX\". To do this press and release the Windows keys and type x64 Open this prompt Go to the directory, where beancount is installed C:\\Program Files\\Microsoft Visual Studio\\2022\\Community>cd C:\\_code\\t\\beancount C:\\_code\\t\\beancount> Activate the virtual environment C:\\_code\\t\\beancount>venv\\Scripts\\Activate (venv) C:\\_code\\t\\beancount> Instal required packages (venv) C:\\_code\\t\\beancount>py -m pip install meson-python meson ninja Install beancount in editable mode. Unlike on Linux the --no-build-isolation on Windows from one side throws some errors from the other side does not seem to be needed (venv) C:\\_code\\t\\beancount> py -m pip install --editable . Install pytest (venv) C:\\_code\\t\\beancount>py -m pip install pytest Run tests (venv) C:\\_code\\t\\beancount>pytest --import-mode=importlib beancount Note: some of the tests on Windows fail, but this is due to general portability issue","title":"On Windows"},{"location":"ledgerhub_design_doc.html","text":"Design Doc for Ledgerhub \uf0c1 Martin Blais , February 2014 http://furius.ca/beancount/doc/ledgerhub-design-doc Motivation Goals & Stages Details of Stages Fetching Identification Extraction Transform Rendering Filing Implementation Details Importers Interface References Please note that this document is the original design doc for LedgerHub. LedgerHub is being transitioned back to Beancount. See this postmortem document for details [blais, 2015-12]. Motivation \uf0c1 Several open source projects currently exist that provide the capability to create double-entry transactions for bookkeeping from a text file input. These various double-entry bookkeeping projects include Beancount , Ledger , HLedger , Abandon , and they are independent implementations of a similar goal: the creation of an in-memory representation for double-entry accounting transactions from a text file, and the production of various reports from it, such as balance sheets, income statements, journals, and others. Each implementation explores slightly different feature sets, but essentially all work by reading their input from a file whose format is custom declarative language that describe the transactions, a language which is meant to be written by humans and whose syntax is designed with that goal in mind. While the languages do vary somewhat, the underlying data structures that they define are fairly similar. An essential part of the process of regularly updating one\u2019s journal files is the replication of a real-world account\u2019s transaction detail to a single input file in a consistent data format. This is essentially a translation step, meant to bring the transaction details of many institutions\u2019 accounts into a single system. Various banks and credit card companies provide downloadable transaction data in either Quicken or Microsoft Money (OFX) formats, and many institutions provide custom CSV files with transaction detail. Moreover, many of these institutions also make regular statements available for download as PDF files, and these can be associated with one\u2019s ledger accounts. The process of translating these external data formats can be automated to some extent. These various files can be translated to output text that can then be massaged by the user to be integrated into input file formats accepted by a double-entry bookkeeping package. Several projects have begun to make inroads in that domain: Ledger-autosync aims at fetching transactions automatically from OFX servers for and translating them for Ledger and HLedger, and Reckon converts CSV files for Ledger. Beancount includes code that can automate the identification of downloaded files to the accounts from a ledger, extract their transaction detail, and automatically file them to a directory hierarchy that mirrors the ledger\u2019s chart of accounts. This code should probably live outside of Beancount. Ledger also sports a \u201cconvert\u201d command that attempts to do similar things and a CSV2Ledger Perl script is available that can convert CSV files. HLedger also had a convert command which translated CSV files with optional conversion hints defined in a separate file; HLedger now does the same conversion on-the-fly when the input file is CSV (i.e., CSV is considered a first-class input format). The programs that fetch and convert external data files do not have to be tied to a single system. Moreover, this is often cumbersome code that would benefit greatly from having a large number of contributors, which could each benefit each other from having common parsers ready and working for the various institutions that they\u2019re using or likely to use in the future. I - the author of Beancount - have decided to move Beancount\u2019s importing and filing source code outside of its home project and to decouple it from the Beancount source code, so that others can contribute to it, with the intent of providing project-agnostic functionality. This document describes the goals and design of this project. Goals & Stages \uf0c1 This new project should address the following aspects in a project-agnostic manner: Fetching : Automate obtaining the external data files by connecting to the data sources directly. External tools and libraries such as ofxclient for OFX sources can be leveraged for this purpose. Web scraping could be used to fetch downloadable files where possible. The output of this stage is a list of institution-specific files downloaded to a directory. Note that fetching does not just apply to transaction data here; we will also support fetching prices . A list of (date, price) entries may be created from this data. We will likely want to support an intermediate format for expressing a list of positions (and appropriate support in the ledgerhub-Ledger/Beancount/HLedger interface to obtain it). Identification : Given a filename and its contents, automatically guess which institution and account configuration the file is for, and ideally be able to extract the date from the file or statement. This should also work with PDF files. The output of this stage is an association of each input file to a particular extractor and configuration (e.g. a particular account name). Extraction : Parse each file (if possible) and extract a list of information required to generate double-entry transactions data structures from it, in some sort of generic data structure, such as dicts of strings and numbers, independent of the underlying project\u2019s desired output. If possible, a verbatim snippet of the original text that generated the transaction should be attached to the output data structure. The output of this stage is a data structure, e.g., a list of Python dictionaries in some defined format. Transform : Given some information from the past transaction history contained in a journal, using simple learning algorithms, a program should be able to apply transformations on the transactional information extracted from the previous step. The most common use case for this is to automatically add a categorization posting to transactions that have a single posting only. For example, transactions from credit card statements typically include the changes in balance of the credit card account but all transactions are left to be manually associated with a particular expense account. Some of this process can be automated at this stage. Rendering : Convert the internal transactions data structures to the particular syntax of a double-entry bookkeeping project implementation and to the particular desired syntax variants (e.g. currency formatting, comma vs. dot decimal separator, localized input date format). This steps spits out text to be inserted into an input file compatible with the ledger software of choice. Filing : Sanitize the downloaded files\u2019 filenames and move them into a well organized and structured directory hierarchy corresponding to the identified account. This can run from the same associations derived in the identification step. Apart from the Render stage, all the other stages should be implemented without regard for a particular project, this should work across all ledger implementations. The Rendering code, however, should specialize, import source code, and attempt to add as many of the particular features provided by each project to its output text. Where necessary, interfaces to obtain particular data sets from each ledger implementation\u2019s input files should be provided to shield the common code from the particular implementation details of that project. For instance, a categorization Transform step would need to train its algorithm on some of the transaction data (i.e., the narration fields and perhaps some of the amounts, account names, and dates). Each project should provide a way to obtain the necessary data from its input data file, in the same format. Details of Stages \uf0c1 Fetching \uf0c1 By default, a user should be able to click their way to their institution\u2019s website and download documents to their ~/Downloads directory. A directory with some files in it should be the reasonable default input to the identification stage. This directory should be allowed to have other/garbage files in it, the identification step should be able to skip those automatically. A module that can automatically fetch the data needs to be implemented. Ideally this would not require an external tool. The data extracted should also have a copy saved in some Downloads directory. This is the domain of the ledger-autosync project. Perhaps we should coordinate input/outputs or even integrate call some of its library code at this stage. The author notes that fetching data from OFX servers is pretty easy, though the begin/end dates will have to get processed and filtered. Automatic fetching support will vary widely depending on where the institutions are located. Some places have solid support, some less. Use the data from ofxhome.com to configure. Fetching Prices \uf0c1 For fetching prices, there are many libraries out there. Initially we will port Beancount\u2019s bean-prices to ledgerhub. Identification \uf0c1 The identification stage consists in running a driver program that Searches for files in a directory hierarchy (typically your ~/Downloads folder) If necessary, converts the files into some text/ascii format, so that regular expressions can be matched against it (even if the output is messy, e.g., with PDF files converted to ASCII). This works well for PDF files: despite the fact that we cannot typically extract transactional data from them, we can generally pretty reliably identify which account they\u2019re for and almost always extract the statement date as well. Check a list of regular expressions against the ASCII\u2019fied contents. If the regular expressions all match, the configuration is associated to the filename. Note that more than one configuration may be associated to the same file because some files contain many sections, sections for which different importers may be called on to extract their data (e.g., OFX banking + OFX credit card can be mixed in the same file, and some institutions do). The net result of this process is an association of each filename with the a specific importer object instantiated in the configuration file. These importer objects are created with a set of required account names which they use to produce the Ledger-like syntax from the downloaded file that was associated with it. Here is an example configuration for two importers: from ledgerhub.sources.rbc import rbcinvesting, rbcpdf CONFIG = [ ... (('FileType: application/vnd.ms-excel', r'Filename: .*Activity-123456789-', ), rbcinvesting.Importer({ 'FILE' : 'Assets:CA:RBC-Investing:Taxable', 'cash' : 'Assets:CA:RBC-Investing:Taxable:Cash', 'positions' : 'Assets:CA:RBC-Investing:Taxable', 'interest' : 'Income:CA:RBC-Investing:Taxable:Interest', 'dividend' : 'Income:CA:RBC-Investing:Taxable:Dividends', 'fees' : 'Expenses:Financial:Fees', 'commission' : 'Expenses:Financial:Commissions', 'transfer' : 'Assets:CA:RBC:Checking', })), (('FileType: application/pdf', 'Filename:.*/123456789-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d.pdf'), rbcpdf.Importer({ 'FILE': 'Assets:CA:RBC-Investing:RRSP', })), The configuration consists in a list, for each possible importer, of a pair of 1) a list of regular expressions which all should match against a \u201cmatch text\u201d, which is a \u201ctextified\u201d version of the contents of a file to be imported, and 2) an importer object, configured with a specific set of accounts to use for producing transactions. Each importer requires a particular set of output accounts which it uses to create its transactions and postings. The ledger\u2019s filename, and a list of these (regexps, importer) pairs is all that is necessary for the driver to carry out all of its work. The textification consists in a simple and imperfect conversion of downloaded file that are in binary format to something that we can run regular expressions against. For an OFX file or CSV file there is no conversion required for textification, we can just match against the text contents of those files; for an Excel/XLS file, we need to convert that to a CSV file, which can then be searched; for a PDF file, a number of different pdf-to-text converters are attempted until one succeeds (the tools for this are notoriously unreliable, so we have to try various ones). Note that this converted \"match text\" is only created temporarily and only for the purpose of identification; the importer will get the original binary file to do its work. It is not entirely clear whether the regular expressions can be standardized to avoid having the user configure them manually. In practice, I have found it often necessary, or at least very convenient, to place an account id in my import configuration. It is true that configuring each of the possible downloads can be a hassle that requires the user to do a bit of guesswork while looking at the contents of each file, but this has been much more reliable in practice than attempts at normalizing this process, likely because it is a much easier problem to uniquely distinguish between all the files of a particular user than to distinguish between all the types of files. Using an account id in one of the regular expressions is the easy way to do that, and it works well. This also provides a clear place to attach the list of accounts to a particular importer, something that necessarily requires user input anyway. Extraction \uf0c1 Once the association is made, we run the importers on each of the files. Some data structure is produced. The importers each do what they do - this is where the ugly tricks go. Ideally, we should build a library of common utilities to help parsing similar file types. Though each of the importer modules should be pretty much independent, some common functionality can be imagined, for example, how one deals with different stocks held in a single investment account, could be configured outside of each importer (e.g., preferred method could be to create a subaccount of that account, with the symbol of the stock, or otherwise). Note [AMaffei]: This could output a generic and well-defined CSV file format if you want to have the option of running the various steps as separate UNIX-style tools and/or process the intermediate files with regular text processing tools. Transform \uf0c1 Some transformations should be independent of importers. In particular, automatically categorizing incomplete transactions is not dependent on which importer created the transaction. I\u2019d like to keep this step as general as possible so that other embellishment steps can be inserted here in the future. Right now, I can only think of the following uses: Auto-categorization of transactions with only a single leg Detection of duplicate transactions: imported files often contain transactions which are already in the ledger; those should be either ignored or marked as such. In practice, this is not as easy as it sounds, because a straightforward date + narration comparison will fail: if the same transaction comes from two input data files, one side always ends up getting merged to the other, and sometimes even the date differs a bit. Some amount of fuzzy matching is required. Normalization of payee names: the imported names of payees are often cut short or include some irrelevant words, such as \u201cLLC\u201d, city names, and/or number codes. It may be desirable to somehow clean those up automatically. This step involves a bootstrapping phase, where we will extract some data from the actual ledger that the transactions are meant to be imported into. We will implement a generic interface that should allow each ledger language implementation to provide relevant data for training. The output data here should be in the same format as its input, so that we can optionally skip this phase. Rendering \uf0c1 An output renderer should be selected by the driver. This is where we convert the extracted data structures to the particular flavor of ledger implementation you\u2019re using. Each of the renderer implementations should be free to import modules from its particular implementation, and we should be careful to constraint these import dependencies to only these modules, to make sure that only a single ledger implementation is required in order for the code to run. Options for rendering style could be defined here, for each renderer, because each of the languages have particularies. [AMaffei] Also, it should be possible to provide a generic renderer that takes printf-style format strings to output in any desired format. Filing \uf0c1 Importers should be able to look at the textified contents of the files and find the file/statement date. This is useful, because we can rename the file by prepending the date of the statement, and the date at which we download the statement or transaction files is rarely the same date at which it was generated. In the case where we are not able to extract a date from the file, we fall back on the filename\u2019s last modified time. A target directory should be provided and we should move each file to the account with which it is associated. For example, a file like this: ~/Downloads/ofx32755.qbo should be moved to a directory .../Assets/US/RBC/Checking/2013-11-27.ofx32755.qbo if it is associated by the identification step with an importer for the Assets:US:RBC:Checking account. For this purpose, all the importers should have a required \u201cfiling\u201d account associated with them. As far as I know only Beancount implements this at the moment, but I suspect this convenient mechanism of organizing and preserving your imported files will be found useful by others. Given a list of directories, Beancount automatically finds those files and using the date in the filename, is able to render links to the files as line items in the journal web pages, and serve their contents when the user clicks on the links. Even without this capability, it can be used to maintain a cache of your documents (I maintain mine in a repository which I sync to an external drive for backup). Implementation Details \uf0c1 Notes about the initial implementation: The implementation of this project will be carried out in Python3. Why Python? The performance of importing and extracting is largely irrelevant, a dynamic language works well for this type of task Parsing in a dynamic language works great, there are many available libraries Python3 is now widely distributed and all desired parsing libraries are commonly available for it at this point All modules should be tested, including testing with sample input. If you want to add a new module, you should need to provide an anonymized sample file for it. We will have to have an automated test suite, because past experience has shown this type of code to be quite brittle and fragile to new and unexpected inputs. It\u2019s easy to write, but it\u2019s also easy to break. In order to test binary files that cannot be anonymized, we will provide the ability to test from match-text instead of from original binary statement PDF. Those files are generally not extractable anyhow and are only there for identification and filing (e.g. a PDF statement, we can\u2019t extract any meaningful data out of those except perhaps for the statement date). There should be a quick way to test a particular importer with a particular downloaded file with zero configuration, even if the output account names are a little wonky. There needs to be clean and readable tracing for what the importers are doing, including a debugging/verbose option. We provide a single function to call as the driver for your own import script. Your configuration is a script / your script is the configuration. You call a function at the end. We will also provide a script that imports a filename and fetches an attribute from it, for those who want a more traditional invocation. We should keep types simples, but use the standard datetime types for dates, decimal.Decimal for numbers, and strings for currencies/commodities. This is obviously based on my current importers code in Beancount. I\u2019m very open to new ideas and suggestions for this project. Collaborations will be most welcome. The more importers we can support, the better. Importers Interface \uf0c1 Each importer should be implemented as a class that derives from this one: class ImporterBase: \"Base class/interface for all source importers.\" # A dict of required configuration variables to their docstring. # This declares the list of options required for the importer # to be provided with, and their meaning. REQUIRED_CONFIG = {} def __init__(self, config): \"\"\"Create an importer. Most concrete implementations can just use this without overriding. Args: config: A dict of configuration accounts, that must match the REQUIRED_CONFIG values. \"\"\" # a dict of Configuration values. This can be accessed publicly. assert isinstance(config, dict) self.config = config # Check that the config has just the required configuration values. if not verify_config(self, config, self.REQUIRED_CONFIG): raise ValueError(\"Invalid config {}, requires {}\".format( config, self.REQUIRED_CONFIG)) def get_filing_account(self): \"\"\"Return the account for moving the input file to. Returns: The name of the account that corresponds to this importer. \"\"\" return self.config['FILE'] def import_file (self, filename): \"\"\"Attempt to import a file. Args: filename: the name of the file to be imported. Returns: A list of new, imported entries extracted from the file. \"\"\" raise NotImplementedError def import_date (self, filename, text_contents): \"\"\"Attempt to obtain a date that corresponds to the given file. Args: filename: the name of the file to extract the date from text_contents: an ASCII text version of the file contents, whatever format it is originally in. Returns: A date object, if successful, or None. \"\"\" raise NotImplementedError For each importer, a detailed explanation of how the original input file on the institution\u2019s website is to be found and downloaded should be provided, to help those find the correct download when adding this importer (some institutions provide a variety of download formats). In addition, a one-line description of the input file support should be provided, so that we can render at runtime a list of the supported file types. References \uf0c1 Other projects with the same goal as importing account data into Ledger are listed here. Ledger\u2019s \u201cconvert\u201d command HLedger with its built-in readers Reckon OFXmate (GUI for ledger-autosync) CSV2Ledger icsv2ledger csv2ledger (seems to lack active maintainers) Update (Nov 2015): This design doc has been implemented and the project is being transitioned back to Beancount. Read the details here .","title":"Ledgerhub Design Doc"},{"location":"ledgerhub_design_doc.html#design-doc-for-ledgerhub","text":"Martin Blais , February 2014 http://furius.ca/beancount/doc/ledgerhub-design-doc Motivation Goals & Stages Details of Stages Fetching Identification Extraction Transform Rendering Filing Implementation Details Importers Interface References Please note that this document is the original design doc for LedgerHub. LedgerHub is being transitioned back to Beancount. See this postmortem document for details [blais, 2015-12].","title":"Design Doc for Ledgerhub"},{"location":"ledgerhub_design_doc.html#motivation","text":"Several open source projects currently exist that provide the capability to create double-entry transactions for bookkeeping from a text file input. These various double-entry bookkeeping projects include Beancount , Ledger , HLedger , Abandon , and they are independent implementations of a similar goal: the creation of an in-memory representation for double-entry accounting transactions from a text file, and the production of various reports from it, such as balance sheets, income statements, journals, and others. Each implementation explores slightly different feature sets, but essentially all work by reading their input from a file whose format is custom declarative language that describe the transactions, a language which is meant to be written by humans and whose syntax is designed with that goal in mind. While the languages do vary somewhat, the underlying data structures that they define are fairly similar. An essential part of the process of regularly updating one\u2019s journal files is the replication of a real-world account\u2019s transaction detail to a single input file in a consistent data format. This is essentially a translation step, meant to bring the transaction details of many institutions\u2019 accounts into a single system. Various banks and credit card companies provide downloadable transaction data in either Quicken or Microsoft Money (OFX) formats, and many institutions provide custom CSV files with transaction detail. Moreover, many of these institutions also make regular statements available for download as PDF files, and these can be associated with one\u2019s ledger accounts. The process of translating these external data formats can be automated to some extent. These various files can be translated to output text that can then be massaged by the user to be integrated into input file formats accepted by a double-entry bookkeeping package. Several projects have begun to make inroads in that domain: Ledger-autosync aims at fetching transactions automatically from OFX servers for and translating them for Ledger and HLedger, and Reckon converts CSV files for Ledger. Beancount includes code that can automate the identification of downloaded files to the accounts from a ledger, extract their transaction detail, and automatically file them to a directory hierarchy that mirrors the ledger\u2019s chart of accounts. This code should probably live outside of Beancount. Ledger also sports a \u201cconvert\u201d command that attempts to do similar things and a CSV2Ledger Perl script is available that can convert CSV files. HLedger also had a convert command which translated CSV files with optional conversion hints defined in a separate file; HLedger now does the same conversion on-the-fly when the input file is CSV (i.e., CSV is considered a first-class input format). The programs that fetch and convert external data files do not have to be tied to a single system. Moreover, this is often cumbersome code that would benefit greatly from having a large number of contributors, which could each benefit each other from having common parsers ready and working for the various institutions that they\u2019re using or likely to use in the future. I - the author of Beancount - have decided to move Beancount\u2019s importing and filing source code outside of its home project and to decouple it from the Beancount source code, so that others can contribute to it, with the intent of providing project-agnostic functionality. This document describes the goals and design of this project.","title":"Motivation"},{"location":"ledgerhub_design_doc.html#goals-stages","text":"This new project should address the following aspects in a project-agnostic manner: Fetching : Automate obtaining the external data files by connecting to the data sources directly. External tools and libraries such as ofxclient for OFX sources can be leveraged for this purpose. Web scraping could be used to fetch downloadable files where possible. The output of this stage is a list of institution-specific files downloaded to a directory. Note that fetching does not just apply to transaction data here; we will also support fetching prices . A list of (date, price) entries may be created from this data. We will likely want to support an intermediate format for expressing a list of positions (and appropriate support in the ledgerhub-Ledger/Beancount/HLedger interface to obtain it). Identification : Given a filename and its contents, automatically guess which institution and account configuration the file is for, and ideally be able to extract the date from the file or statement. This should also work with PDF files. The output of this stage is an association of each input file to a particular extractor and configuration (e.g. a particular account name). Extraction : Parse each file (if possible) and extract a list of information required to generate double-entry transactions data structures from it, in some sort of generic data structure, such as dicts of strings and numbers, independent of the underlying project\u2019s desired output. If possible, a verbatim snippet of the original text that generated the transaction should be attached to the output data structure. The output of this stage is a data structure, e.g., a list of Python dictionaries in some defined format. Transform : Given some information from the past transaction history contained in a journal, using simple learning algorithms, a program should be able to apply transformations on the transactional information extracted from the previous step. The most common use case for this is to automatically add a categorization posting to transactions that have a single posting only. For example, transactions from credit card statements typically include the changes in balance of the credit card account but all transactions are left to be manually associated with a particular expense account. Some of this process can be automated at this stage. Rendering : Convert the internal transactions data structures to the particular syntax of a double-entry bookkeeping project implementation and to the particular desired syntax variants (e.g. currency formatting, comma vs. dot decimal separator, localized input date format). This steps spits out text to be inserted into an input file compatible with the ledger software of choice. Filing : Sanitize the downloaded files\u2019 filenames and move them into a well organized and structured directory hierarchy corresponding to the identified account. This can run from the same associations derived in the identification step. Apart from the Render stage, all the other stages should be implemented without regard for a particular project, this should work across all ledger implementations. The Rendering code, however, should specialize, import source code, and attempt to add as many of the particular features provided by each project to its output text. Where necessary, interfaces to obtain particular data sets from each ledger implementation\u2019s input files should be provided to shield the common code from the particular implementation details of that project. For instance, a categorization Transform step would need to train its algorithm on some of the transaction data (i.e., the narration fields and perhaps some of the amounts, account names, and dates). Each project should provide a way to obtain the necessary data from its input data file, in the same format.","title":"Goals & Stages"},{"location":"ledgerhub_design_doc.html#details-of-stages","text":"","title":"Details of Stages"},{"location":"ledgerhub_design_doc.html#fetching","text":"By default, a user should be able to click their way to their institution\u2019s website and download documents to their ~/Downloads directory. A directory with some files in it should be the reasonable default input to the identification stage. This directory should be allowed to have other/garbage files in it, the identification step should be able to skip those automatically. A module that can automatically fetch the data needs to be implemented. Ideally this would not require an external tool. The data extracted should also have a copy saved in some Downloads directory. This is the domain of the ledger-autosync project. Perhaps we should coordinate input/outputs or even integrate call some of its library code at this stage. The author notes that fetching data from OFX servers is pretty easy, though the begin/end dates will have to get processed and filtered. Automatic fetching support will vary widely depending on where the institutions are located. Some places have solid support, some less. Use the data from ofxhome.com to configure.","title":"Fetching"},{"location":"ledgerhub_design_doc.html#fetching-prices","text":"For fetching prices, there are many libraries out there. Initially we will port Beancount\u2019s bean-prices to ledgerhub.","title":"Fetching Prices"},{"location":"ledgerhub_design_doc.html#identification","text":"The identification stage consists in running a driver program that Searches for files in a directory hierarchy (typically your ~/Downloads folder) If necessary, converts the files into some text/ascii format, so that regular expressions can be matched against it (even if the output is messy, e.g., with PDF files converted to ASCII). This works well for PDF files: despite the fact that we cannot typically extract transactional data from them, we can generally pretty reliably identify which account they\u2019re for and almost always extract the statement date as well. Check a list of regular expressions against the ASCII\u2019fied contents. If the regular expressions all match, the configuration is associated to the filename. Note that more than one configuration may be associated to the same file because some files contain many sections, sections for which different importers may be called on to extract their data (e.g., OFX banking + OFX credit card can be mixed in the same file, and some institutions do). The net result of this process is an association of each filename with the a specific importer object instantiated in the configuration file. These importer objects are created with a set of required account names which they use to produce the Ledger-like syntax from the downloaded file that was associated with it. Here is an example configuration for two importers: from ledgerhub.sources.rbc import rbcinvesting, rbcpdf CONFIG = [ ... (('FileType: application/vnd.ms-excel', r'Filename: .*Activity-123456789-', ), rbcinvesting.Importer({ 'FILE' : 'Assets:CA:RBC-Investing:Taxable', 'cash' : 'Assets:CA:RBC-Investing:Taxable:Cash', 'positions' : 'Assets:CA:RBC-Investing:Taxable', 'interest' : 'Income:CA:RBC-Investing:Taxable:Interest', 'dividend' : 'Income:CA:RBC-Investing:Taxable:Dividends', 'fees' : 'Expenses:Financial:Fees', 'commission' : 'Expenses:Financial:Commissions', 'transfer' : 'Assets:CA:RBC:Checking', })), (('FileType: application/pdf', 'Filename:.*/123456789-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d.pdf'), rbcpdf.Importer({ 'FILE': 'Assets:CA:RBC-Investing:RRSP', })), The configuration consists in a list, for each possible importer, of a pair of 1) a list of regular expressions which all should match against a \u201cmatch text\u201d, which is a \u201ctextified\u201d version of the contents of a file to be imported, and 2) an importer object, configured with a specific set of accounts to use for producing transactions. Each importer requires a particular set of output accounts which it uses to create its transactions and postings. The ledger\u2019s filename, and a list of these (regexps, importer) pairs is all that is necessary for the driver to carry out all of its work. The textification consists in a simple and imperfect conversion of downloaded file that are in binary format to something that we can run regular expressions against. For an OFX file or CSV file there is no conversion required for textification, we can just match against the text contents of those files; for an Excel/XLS file, we need to convert that to a CSV file, which can then be searched; for a PDF file, a number of different pdf-to-text converters are attempted until one succeeds (the tools for this are notoriously unreliable, so we have to try various ones). Note that this converted \"match text\" is only created temporarily and only for the purpose of identification; the importer will get the original binary file to do its work. It is not entirely clear whether the regular expressions can be standardized to avoid having the user configure them manually. In practice, I have found it often necessary, or at least very convenient, to place an account id in my import configuration. It is true that configuring each of the possible downloads can be a hassle that requires the user to do a bit of guesswork while looking at the contents of each file, but this has been much more reliable in practice than attempts at normalizing this process, likely because it is a much easier problem to uniquely distinguish between all the files of a particular user than to distinguish between all the types of files. Using an account id in one of the regular expressions is the easy way to do that, and it works well. This also provides a clear place to attach the list of accounts to a particular importer, something that necessarily requires user input anyway.","title":"Identification"},{"location":"ledgerhub_design_doc.html#extraction","text":"Once the association is made, we run the importers on each of the files. Some data structure is produced. The importers each do what they do - this is where the ugly tricks go. Ideally, we should build a library of common utilities to help parsing similar file types. Though each of the importer modules should be pretty much independent, some common functionality can be imagined, for example, how one deals with different stocks held in a single investment account, could be configured outside of each importer (e.g., preferred method could be to create a subaccount of that account, with the symbol of the stock, or otherwise). Note [AMaffei]: This could output a generic and well-defined CSV file format if you want to have the option of running the various steps as separate UNIX-style tools and/or process the intermediate files with regular text processing tools.","title":"Extraction"},{"location":"ledgerhub_design_doc.html#transform","text":"Some transformations should be independent of importers. In particular, automatically categorizing incomplete transactions is not dependent on which importer created the transaction. I\u2019d like to keep this step as general as possible so that other embellishment steps can be inserted here in the future. Right now, I can only think of the following uses: Auto-categorization of transactions with only a single leg Detection of duplicate transactions: imported files often contain transactions which are already in the ledger; those should be either ignored or marked as such. In practice, this is not as easy as it sounds, because a straightforward date + narration comparison will fail: if the same transaction comes from two input data files, one side always ends up getting merged to the other, and sometimes even the date differs a bit. Some amount of fuzzy matching is required. Normalization of payee names: the imported names of payees are often cut short or include some irrelevant words, such as \u201cLLC\u201d, city names, and/or number codes. It may be desirable to somehow clean those up automatically. This step involves a bootstrapping phase, where we will extract some data from the actual ledger that the transactions are meant to be imported into. We will implement a generic interface that should allow each ledger language implementation to provide relevant data for training. The output data here should be in the same format as its input, so that we can optionally skip this phase.","title":"Transform"},{"location":"ledgerhub_design_doc.html#rendering","text":"An output renderer should be selected by the driver. This is where we convert the extracted data structures to the particular flavor of ledger implementation you\u2019re using. Each of the renderer implementations should be free to import modules from its particular implementation, and we should be careful to constraint these import dependencies to only these modules, to make sure that only a single ledger implementation is required in order for the code to run. Options for rendering style could be defined here, for each renderer, because each of the languages have particularies. [AMaffei] Also, it should be possible to provide a generic renderer that takes printf-style format strings to output in any desired format.","title":"Rendering"},{"location":"ledgerhub_design_doc.html#filing","text":"Importers should be able to look at the textified contents of the files and find the file/statement date. This is useful, because we can rename the file by prepending the date of the statement, and the date at which we download the statement or transaction files is rarely the same date at which it was generated. In the case where we are not able to extract a date from the file, we fall back on the filename\u2019s last modified time. A target directory should be provided and we should move each file to the account with which it is associated. For example, a file like this: ~/Downloads/ofx32755.qbo should be moved to a directory .../Assets/US/RBC/Checking/2013-11-27.ofx32755.qbo if it is associated by the identification step with an importer for the Assets:US:RBC:Checking account. For this purpose, all the importers should have a required \u201cfiling\u201d account associated with them. As far as I know only Beancount implements this at the moment, but I suspect this convenient mechanism of organizing and preserving your imported files will be found useful by others. Given a list of directories, Beancount automatically finds those files and using the date in the filename, is able to render links to the files as line items in the journal web pages, and serve their contents when the user clicks on the links. Even without this capability, it can be used to maintain a cache of your documents (I maintain mine in a repository which I sync to an external drive for backup).","title":"Filing"},{"location":"ledgerhub_design_doc.html#implementation-details","text":"Notes about the initial implementation: The implementation of this project will be carried out in Python3. Why Python? The performance of importing and extracting is largely irrelevant, a dynamic language works well for this type of task Parsing in a dynamic language works great, there are many available libraries Python3 is now widely distributed and all desired parsing libraries are commonly available for it at this point All modules should be tested, including testing with sample input. If you want to add a new module, you should need to provide an anonymized sample file for it. We will have to have an automated test suite, because past experience has shown this type of code to be quite brittle and fragile to new and unexpected inputs. It\u2019s easy to write, but it\u2019s also easy to break. In order to test binary files that cannot be anonymized, we will provide the ability to test from match-text instead of from original binary statement PDF. Those files are generally not extractable anyhow and are only there for identification and filing (e.g. a PDF statement, we can\u2019t extract any meaningful data out of those except perhaps for the statement date). There should be a quick way to test a particular importer with a particular downloaded file with zero configuration, even if the output account names are a little wonky. There needs to be clean and readable tracing for what the importers are doing, including a debugging/verbose option. We provide a single function to call as the driver for your own import script. Your configuration is a script / your script is the configuration. You call a function at the end. We will also provide a script that imports a filename and fetches an attribute from it, for those who want a more traditional invocation. We should keep types simples, but use the standard datetime types for dates, decimal.Decimal for numbers, and strings for currencies/commodities. This is obviously based on my current importers code in Beancount. I\u2019m very open to new ideas and suggestions for this project. Collaborations will be most welcome. The more importers we can support, the better.","title":"Implementation Details"},{"location":"ledgerhub_design_doc.html#importers-interface","text":"Each importer should be implemented as a class that derives from this one: class ImporterBase: \"Base class/interface for all source importers.\" # A dict of required configuration variables to their docstring. # This declares the list of options required for the importer # to be provided with, and their meaning. REQUIRED_CONFIG = {} def __init__(self, config): \"\"\"Create an importer. Most concrete implementations can just use this without overriding. Args: config: A dict of configuration accounts, that must match the REQUIRED_CONFIG values. \"\"\" # a dict of Configuration values. This can be accessed publicly. assert isinstance(config, dict) self.config = config # Check that the config has just the required configuration values. if not verify_config(self, config, self.REQUIRED_CONFIG): raise ValueError(\"Invalid config {}, requires {}\".format( config, self.REQUIRED_CONFIG)) def get_filing_account(self): \"\"\"Return the account for moving the input file to. Returns: The name of the account that corresponds to this importer. \"\"\" return self.config['FILE'] def import_file (self, filename): \"\"\"Attempt to import a file. Args: filename: the name of the file to be imported. Returns: A list of new, imported entries extracted from the file. \"\"\" raise NotImplementedError def import_date (self, filename, text_contents): \"\"\"Attempt to obtain a date that corresponds to the given file. Args: filename: the name of the file to extract the date from text_contents: an ASCII text version of the file contents, whatever format it is originally in. Returns: A date object, if successful, or None. \"\"\" raise NotImplementedError For each importer, a detailed explanation of how the original input file on the institution\u2019s website is to be found and downloaded should be provided, to help those find the correct download when adding this importer (some institutions provide a variety of download formats). In addition, a one-line description of the input file support should be provided, so that we can render at runtime a list of the supported file types.","title":"Importers Interface"},{"location":"ledgerhub_design_doc.html#references","text":"Other projects with the same goal as importing account data into Ledger are listed here. Ledger\u2019s \u201cconvert\u201d command HLedger with its built-in readers Reckon OFXmate (GUI for ledger-autosync) CSV2Ledger icsv2ledger csv2ledger (seems to lack active maintainers) Update (Nov 2015): This design doc has been implemented and the project is being transitioned back to Beancount. Read the details here .","title":"References"},{"location":"precision_tolerances.html","text":"Beancount Precision & Tolerances \uf0c1 Martin Blais , May 2015 http://furius.ca/beancount/doc/tolerances This document describes how Beancount handles the limited precision of numbers in transaction balance checks and balance assertions. It also documents rounding that may occur in inferring numbers automatically. Motivation \uf0c1 Beancount automatically enforces that the amounts on the Postings of Transactions entered in an input file sum up to zero. In order for Beancount to verify this in a realistic way, it must tolerate a small amount of imprecision. This is because Beancount lets you replicate what happens in real world account transactions , and in the real world, institutions round amounts up or down for practical reasons. Here\u2019s an example: Consider the following transaction which consists in a transfer between two accounts denominated in different currencies (US dollars and Euros): 2015-05-01 * \"Transfer from secret Swiss bank account\" Assets:CH:SBS:Checking -9000.00 CHF Assets:US:BofA:Checking 9643.82 USD @ 0.93324 CHF In this example, the exchange rate used was 0.93324 USD/CHF, that is, 0.93324 Swiss Francs per US dollar. This rate was quoted to 5 digits of precision by the bank. A full-precision conversion of 9000.00 CHF / 0.93324 CHF yields 9643.82152501... USD. Similarly, converting the US dollars to Francs using the given rate yields an imprecise result as well: 9643.82 x 0.93324 = 8999.9985768\u2026 . Here is another example where this type of rounding may occur: A transaction for a fractional number of shares of a mutual fund: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD Once again, rounding occurs in this transaction: not only the Net Asset Value of the fund is rounded to its nearest penny value ($37.61), but the number of units is also rounded and accounted for by Vanguard with a fixed number of digits (10.22626 units of VPMBX). And the balance of the entire transaction needs to tolerate some imprecision, whether you compute the value of the shares (10.22626 x $37.61 = $384.6096386 ) or whether you compute the number of shares from the desired dollar amount of the contribution ($384.61 / $37.61 = 10.2262696091 ). From Beancount\u2019s point-of-view, both of the examples above are balancing transactions. Clearly, if we are to try to represent and reproduce the transactions of external accounts to our input file, there needs to be some tolerance in the balance verification algorithm. How Precision is Determined \uf0c1 Beancount attempts to derive the precision from each transaction automatically , from the input, for each Transaction in isolation 1 . Let us inspect our last example again: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD In this transaction, Beancount will infer the tolerance of RGAGX at 5 fractional digits, that is, 0.000005 RGAGX , and USD at 2 fractional digits, that is, 0.005 USD . Note that the tolerance used is half of the last digit of precision provided by the user. This is entirely inferred from the input, without having to fetch any global tolerance declaration. Also note how the precision is calculated separately for each currency . Observe that although we are inferring a tolerance for units of RGAGX, it is actually not used in the balancing of this transaction, because the \u201cweight\u201d of the first posting is in USD (10.22626 x 37.61 = 384.6096386 USD). So what happens here? The weights of each postings are calculated: 384.6096386 USD for the first posting -384.61 USD for the second These are summed together, by currency (there is only USD in the weights of this transaction) which results in a residual value of -0.0003614 USD. This value is compared to the tolerance for units of USD: |-0.0003614| < 0.005, and this transaction balances. Prices and Costs \uf0c1 For the purpose of inferring the tolerance to be used, the price and cost amounts declared on a transaction\u2019s Postings are ignored . This makes sense if you consider that these are usually specified at a higher precision than the base amounts of the postings\u2014and sometimes this extra precision is necessary to make the transaction balance. These should not be used in setting the precision of the whole transaction. For example, in the following transaction: 1999-09-30 * \"Vest ESPP - Bought at discount: 18.5980 USD\" Assets:US:Schwab:ESPP 54 HOOL {21.8800 USD} Income:CA:ESPP:PayContrib -1467.84 CAD @ 0.6842 USD Income:CA:ESPP:Discount -259.03 CAD @ 0.6842 USD The only tolerance inferred here is 0.005 for CAD. (54 HOOL does not yield anything in this case because it is integral; the next section explains this). There is no tolerance inferred for USD, neither from the cost from the first posting (21.8800 USD), nor from the prices of the remaining postings (0.6842 USD). Integer Amounts \uf0c1 For integer amounts in the input, the precision is not inferred to 0.5, that is, this should fail to balance: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD In other words, integer amounts do not contribute a number of digits to the determination of the tolerance for their currency. By default, the tolerance used on amounts without an inferred precision is zero . So in this example, because we cannot infer the precision of USD (recall that the cost is ignored), this transaction will fail to balance, because its residual is non-zero (|-0.0003614| > 0). You can customize what the default tolerance should be for each currency separately and for any currency as well (see section below on how to do this). This treatment of integer amounts implies that the maximum amount of precision that one can specify just by inputting numbers is 0.05 units of the currency, for example, by providing a number such as 10.7 as input 2 . On the other hand, the settings for the default tolerance to use allows specifying arbitrary numbers. Resolving Ambiguities \uf0c1 A case that presents itself rarely is one where multiple different precisions are being input for the same currency. In this case, the largest (coarsest) of the inferred input tolerances is used. For example, if we wanted to track income to more than pennies, we might write this: 1999-08-20 * \"Sell\" Assets:US:BRS:ESPP -81 HOOL {26.3125 USD} Assets:US:BRS:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:CA:ESPP:PnL -10.125 USD The amounts we have for USD in this case are 2141.36, 0.08 and -10.125, which infer tolerances of either 0.005 or 0.0005. We select the coarsest amount: this transaction tolerates an imprecision of 0.005 USD. Default Tolerances \uf0c1 When a transaction\u2019s numbers do not provide enough information to infer a tolerance locally , we fall back to some default tolerance value. As seen in previous examples, this may occur either because (a) the numbers associated with the currency we need it for are integral, or (b) sufficient numbers are simply absent from the input. By default, this default tolerance is zero for all currencies. This can be specified with an option, like this: option \"inferred_tolerance_default\" \"*:0.001\" The default tolerance can be further refined for each currency involved, by providing the currency to the option, like this: option \"inferred_tolerance_default\" \"USD:0.003\" If provided, the currency-specific tolerance will be used over the global value. The general form for this option is: option \"inferred_tolerance_default\" \":\" Just to be clear: this option is only used when the tolerance cannot be inferred. If you have overly large rounding errors and the numbers in your transactions do infer some tolerance value, this value will be ignored (e.g., setting it to a larger number to try to address that fix will not work). If you need to loosen up the tolerance, see the \u201c inferred_tolerance_multiplier \u201d in the next section. (Note: I\u2019ve been considering dedicating a special meta-data field to the Commodity directive for this, but this would break from the invariant that meta-data is only there to be used by users and plugins, so I\u2019ve refrained so far.) Tolerance Multiplier \uf0c1 We\u2019re shown previously that when the tolerance value isn\u2019t provided explicitly, that it is inferred from the numbers on the postings. By default, the smallest digit found on those numbers is divided by half to obtain the tolerance because we assume that the institutions which we\u2019re reproducing the transactions apply rounding and so the error should never be more than half. But in reality, you may find that the rounding errors sometime exceed this value. For this reason, we provide an option to set the multiplier for the inferred tolerance: option \"inferred_tolerance_multiplier\" \"1.2\" This value overrides the default multiplier. In this example, for a transaction with postings only with values such as 24.45 CHF, the inferred tolerance for CHF would be +/- 0.012 CHF. Inferring Tolerances from Cost \uf0c1 There is also a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 / 2 = 0.045 USD and the sum of all such possible rounding errors is calculate for all postings held at cost or converted from a price, and the resulting tolerance is added to the list of candidates used to figure out the tolerance we should use for the given commodity (we use the maximum value of all the inferred tolerances). You turn on the feature like this: option \"infer_tolerance_from_cost\" \"TRUE\" Enabling this flag only makes the tolerances potentially wider, never smaller. Balance Assertions & Padding \uf0c1 There are a few other places where approximate comparisons are needed. Balance assertions also compare two numbers: 2015-05-08 balance Assets:Investments:RGAGX 4.271 RGAGX This asserts that the accumulated balance for this account has 4.271 units of RGAGX, plus or minus 0.001 RGAGX. So accumulated values of 4.270 RGAGX up to 4.272 RGAGX will check as asserted. The tolerance is inferred automatically to be 1 unit of the least significant digit of the number on the balance assertion. If you wanted a looser assertion, you could have declared: 2015-05-08 balance Assets:Investments:RGAGX 4.27 RGAGX This assertion would accept values from 4.26 RGAGX to 4.28 RGAGX. Note that the inferred tolerances are also expanded by the inferred tolerance multiplier discussed above. Tolerances that Trigger Padding \uf0c1 Pad directives automatically insert transactions to bring account balances in-line with a subsequent balance assertion. The insertion only triggers if the balance differs from the expected value, and the tolerance for this to occur behaves exactly the same as for balance assertions. Explicit Tolerances on Balance Assertions \uf0c1 Beancount supports the specification of an explicit tolerance amount, like this: 2015-05-08 balance Assets:Investments:RGAGX 4.271 ~ 0.01 RGAGX This feature was added because of some observed peculiarities in Vanguard investment accounts whereby rounding appears to follow odd rules and balances don\u2019t match. Saving Rounding Error \uf0c1 As we saw previously, transactions don\u2019t have to balance exactly, they allow for a small amount of imprecision. This bothers some people. If you would like to track and measure the residual amounts allowed by the tolerances, Beancount offers an option to automatically insert postings that will make each transaction balance exactly. You enable the feature like this: option \"account_rounding\" \"Equity:RoundingError\" This tells Beancount to insert postings to compensate for the rounding error to an \u201c Equity:RoundingError \u201d account. For example, with the feature enabled, the following transaction: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD will be automatically transformed into this: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD Equity:RoundingError -0.00135 USD You can verify that this transaction balances exactly. If the transaction already balances exactly (this is the case for most transactions) no posting is inserted. Finally, if you require that all accounts be opened explicitly, you should remember to declare the rounding account in your file at an appropriate date, like this: 2000-01-01 open Equity:RoundingError Precision of Inferred Numbers \uf0c1 Beancount is able to infer some missing numbers in the input. For example, the second posting in this transaction is \u201cinterpolated\u201d automatically by Beancount: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash The calculated amount to be inserted from the first posting is -227.2067 USD. Now, you might ask, to which precision is it inserted at? Does it insert 227.2067 USD at the full precision or does the number get rounded to a penny, e.g. 227.21 USD? It depends on the tolerance inferred for that currency. In this example, no tolerance is able to get inferred (there is no USD amount provided other than the cost amount, which is ignored for the purpose of inferring the tolerance), so we have to defer to the default tolerance. If the default tolerance is not overridden in the input file\u2014and therefore is zero\u2014the full precision will be used; no rounding occurs. This will result in the following transaction: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.2067 USD Note that if a tolerance could be inferred from other numbers on that transaction, it would be used for rounding, such as in this example where the Cash posting is rounded to two digits because of the 9.95 USD number on the Commissions posting: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Expenses:Commissions 9.95 USD Assets:Investments:Cash -237.16 USD However, if no inference is possible, and the default tolerance for USD is set to 0.001, the number will be quantized to 0.001 before insertion, that is, 227.207 USD will be stored: option \"default_tolerance\" \"USD:0.001\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Finally, if you enabled the accumulation of rounding error, the posting\u2019s amount will reflect the correct residual, taking into account the rounded amount that was automatically inserted: option \"default_tolerance\" \"USD:0.01\" option \"account_rounding\" \"Equity:RoundingError\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Equity:RoundingError 0.0003 USD Porting Existing Input \uf0c1 The inference of tolerance values from the transaction\u2019s numbers is generally good enough to keep existing files working without changes. There may be new errors appearing in older files once we process them with the method described in this document, but they should either point to previously undetected errors in the input, or be fixable with simple addition of a suitable number of digits. As a testimony, porting the author\u2019s very large input file has been a relatively painless process that took less than 1 hour. In order to ease the transition, you will probably want to change the default tolerance for all currencies to match the previous value that Beancount had been using, like this: option \"inferred_tolerance_default\" \"*:0.005\" I would recommend you start with this and fix all errors in your file, then proceed to removing this and fix the rest of errors. This should make it easier to adapt your file to this new behavior. As an example of how to fix a new error\u2026 converting this newly failing transaction from the Integer Amounts section: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD by inserting zero\u2019s to provide a locally inferred value like this: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.00 USD is sufficient to silence the balance check. Representational Issues \uf0c1 Internally, Beancount uses a decimal number representation (not a binary/float representation, neither rational numbers). Calculations that result in a large number of fractional digits are carried out to 28 decimal places (the default precision from the context of Python\u2019s IEEE decimal implementation). This is plenty sufficient, because the method we propose above rarely trickles these types of numbers throughout the system: the tolerances allows us to post the precise amounts declared by users, and only automatically derived prices and costs will possibly result in precisions calculated to an unrealistic number of digits that could creep into aggregations in the rest of the system. References \uf0c1 The original proposal that led to this implementation can be found here . In particular, the proposal highlights on the other systems have attempted to deal with this issue. There are also some discussions on the mailing-list dedicated to this topic. Note that for the longest time, Beancount used a fixed precision of 0.005 across all currencies. This was eliminated once the method described in this document was implemented. Also, for Balance and Pad directives, there used to be a \u201ctolerance\u201d option that was set by default to 0.015 of any units. This option has been deprecated with the merging of the changes described in this document. Historical Notes \uf0c1 Here\u2019s an overview of the status of numbers rendering in Beancount as of March 2016, from the mailing-list : First, it's important to realize how these numbers are represented in memory. They are using the Decimal representation which beyond being able to accurately representing decimal numbers (as opposed to the approximation that binary floats provides) also contains a specific precision. That is, the number 2.00 is represented differently than the numbers 2.0 and 2.000. The numbers \"remember\" which precision they are represented up to. This is important. When I say rendering the numbers to their \"natural precision\" I mean the precision with which they are represented, i.e., 2.0 renders as \"2.0\", 2.000 renders as \"2.000\". Then, there are two DISTINCT topics: (1) tolerances, and (2) precision. \"Tolerances\" are values used to determine how much imprecision is acceptable in balancing transactions. This is used in the verification stage, to determine how much looseness to allow. It should not affect how numbers are rendered. \"Precision\" is perhaps a bit of a misnomer: By that I'm referring to is how many digits the numbers are to be rendered with. Once upon a time - after the shell was already written - these concepts weren't well defined in Beancount and I wasn't dealing with these things consistently. At some point it became clear what I needed to do and I created a class called \"DisplayContext\" which could contain appropriate settings for rendering the precision of numbers for each currency (each currency tends to have its own most common rendering precision, e.g. two digits for USD, one digit for MXN, no digits for JPY and in reports we're typically fine rounding the actual numbers to that precision). So an instance of this DisplayContext is automatically instantiated in the parser and in order to avoid the user having to set these values manually - for Beancount to \"do the right thing\" by default - it is able to accumulate the numbers seen and to deduce the most common and maximum number of digits used from the input, and to use that as the default number of digits for rendering numbers. The most common format/number of digits is used to render the number of units, and the maximum number of digits seen is used to render costs and prices. In addition, this class also has capabilities for aligning to the decimal dot and to insert commas on thousands as well. It separates the control of the formatting from the numbers themselves. MOST of the code that renders numbers uses the DisplayContext (via the to_string() methods) to convert the numbers into strings, such as the web interface and explicit text reports. But NOT ALL... there's a bit of HISTORY here... the SQL shell uses some old special-purpose code to render numbers that I never bothered to convert to the DisplayContext class. There's a TODO item for it. It needs to get converted at some point, but I've neglected doing this so far because I have much bigger plans for the SQL query engine that involve a full rewrite of it with many improvements and I figured I'd do that then. If you recall, the SQL query engine was a prototype, and actually it works, but it is not well covered by unit tests. My purpose with it was to discover through usage what would be useful and to then write a v2 of it that would be much better. Now, about that PRINT command... this is not intended as a reporting tool. The printer's purpose is to print input that accurately represents the content of the transactions. In order to do this, it needs to render the numbers at their \"natural\" precision, so that when they get read back in, they parse into the very same number, that is, with the same number of digits (even if zeros). For this reason, the PRINT command does not attempt to render using the DisplayContext instance derived from the input file - this is on purpose. I could change that, but then round-trip would break: the rounding resulting from formatting using the display context may output transactions which don't balance anymore. As you can see, it's not an obvious topic... Hopefully this should allow you to understand what comes out of Beancount in terms of the precision of the numbers it renders. Note: \"default_tolerances\" has been renamed to \"inferred_tolerance_default\" recently because the name was too general and confusing. Old name will work but generate a warning. I just noticed from your comments and some grepping around that the \"render_commas\" option is not used anymore. I'm not sure how that happened, but I'll go ad fix that right away and set the default value of the DisplayContext derived from the input. I should probably also convert the SQL shell rendering to use the display context regardless of future plans, so that it renders consistently with all the rest. Not sure I can do that this weekend, but I'll log a ticket, here . I hope this helps. You're welcome to ask questions if the above isn't clear. I'm sorry if this isn't entirely obvious... there's been a fair bit of history there and there's a lot of code. I should review the naming of options, I think the tolerance options all have \"tolerance\" in their name, but there aren't options to override the rendering and when I add them they should all have a common name as well. Further Reading \uf0c1 What Every Computer Scientist Should Know About Floating-Point Arithmetic This stands in contrast to Ledger which attempts to infer the precision based on other transactions recently parsed in the file, in file order. This has the unfortunate effect of creating \u201ccross-talk\u201d between the transactions in terms of what precision can be used. \u21a9 Note that due to the way Beancount represents numbers internally, it is also not able to distinguish between \u201c230\u201d and \u201c230.\u201d; these parse into the same representation for Beancount. Therefore, we are not able to use that distinction in the input to support a precision of 0.5. \u21a9","title":"Precision Tolerances"},{"location":"precision_tolerances.html#beancount-precision-tolerances","text":"Martin Blais , May 2015 http://furius.ca/beancount/doc/tolerances This document describes how Beancount handles the limited precision of numbers in transaction balance checks and balance assertions. It also documents rounding that may occur in inferring numbers automatically.","title":"Beancount Precision & Tolerances"},{"location":"precision_tolerances.html#motivation","text":"Beancount automatically enforces that the amounts on the Postings of Transactions entered in an input file sum up to zero. In order for Beancount to verify this in a realistic way, it must tolerate a small amount of imprecision. This is because Beancount lets you replicate what happens in real world account transactions , and in the real world, institutions round amounts up or down for practical reasons. Here\u2019s an example: Consider the following transaction which consists in a transfer between two accounts denominated in different currencies (US dollars and Euros): 2015-05-01 * \"Transfer from secret Swiss bank account\" Assets:CH:SBS:Checking -9000.00 CHF Assets:US:BofA:Checking 9643.82 USD @ 0.93324 CHF In this example, the exchange rate used was 0.93324 USD/CHF, that is, 0.93324 Swiss Francs per US dollar. This rate was quoted to 5 digits of precision by the bank. A full-precision conversion of 9000.00 CHF / 0.93324 CHF yields 9643.82152501... USD. Similarly, converting the US dollars to Francs using the given rate yields an imprecise result as well: 9643.82 x 0.93324 = 8999.9985768\u2026 . Here is another example where this type of rounding may occur: A transaction for a fractional number of shares of a mutual fund: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD Once again, rounding occurs in this transaction: not only the Net Asset Value of the fund is rounded to its nearest penny value ($37.61), but the number of units is also rounded and accounted for by Vanguard with a fixed number of digits (10.22626 units of VPMBX). And the balance of the entire transaction needs to tolerate some imprecision, whether you compute the value of the shares (10.22626 x $37.61 = $384.6096386 ) or whether you compute the number of shares from the desired dollar amount of the contribution ($384.61 / $37.61 = 10.2262696091 ). From Beancount\u2019s point-of-view, both of the examples above are balancing transactions. Clearly, if we are to try to represent and reproduce the transactions of external accounts to our input file, there needs to be some tolerance in the balance verification algorithm.","title":"Motivation"},{"location":"precision_tolerances.html#how-precision-is-determined","text":"Beancount attempts to derive the precision from each transaction automatically , from the input, for each Transaction in isolation 1 . Let us inspect our last example again: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD In this transaction, Beancount will infer the tolerance of RGAGX at 5 fractional digits, that is, 0.000005 RGAGX , and USD at 2 fractional digits, that is, 0.005 USD . Note that the tolerance used is half of the last digit of precision provided by the user. This is entirely inferred from the input, without having to fetch any global tolerance declaration. Also note how the precision is calculated separately for each currency . Observe that although we are inferring a tolerance for units of RGAGX, it is actually not used in the balancing of this transaction, because the \u201cweight\u201d of the first posting is in USD (10.22626 x 37.61 = 384.6096386 USD). So what happens here? The weights of each postings are calculated: 384.6096386 USD for the first posting -384.61 USD for the second These are summed together, by currency (there is only USD in the weights of this transaction) which results in a residual value of -0.0003614 USD. This value is compared to the tolerance for units of USD: |-0.0003614| < 0.005, and this transaction balances.","title":"How Precision is Determined"},{"location":"precision_tolerances.html#prices-and-costs","text":"For the purpose of inferring the tolerance to be used, the price and cost amounts declared on a transaction\u2019s Postings are ignored . This makes sense if you consider that these are usually specified at a higher precision than the base amounts of the postings\u2014and sometimes this extra precision is necessary to make the transaction balance. These should not be used in setting the precision of the whole transaction. For example, in the following transaction: 1999-09-30 * \"Vest ESPP - Bought at discount: 18.5980 USD\" Assets:US:Schwab:ESPP 54 HOOL {21.8800 USD} Income:CA:ESPP:PayContrib -1467.84 CAD @ 0.6842 USD Income:CA:ESPP:Discount -259.03 CAD @ 0.6842 USD The only tolerance inferred here is 0.005 for CAD. (54 HOOL does not yield anything in this case because it is integral; the next section explains this). There is no tolerance inferred for USD, neither from the cost from the first posting (21.8800 USD), nor from the prices of the remaining postings (0.6842 USD).","title":"Prices and Costs"},{"location":"precision_tolerances.html#integer-amounts","text":"For integer amounts in the input, the precision is not inferred to 0.5, that is, this should fail to balance: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD In other words, integer amounts do not contribute a number of digits to the determination of the tolerance for their currency. By default, the tolerance used on amounts without an inferred precision is zero . So in this example, because we cannot infer the precision of USD (recall that the cost is ignored), this transaction will fail to balance, because its residual is non-zero (|-0.0003614| > 0). You can customize what the default tolerance should be for each currency separately and for any currency as well (see section below on how to do this). This treatment of integer amounts implies that the maximum amount of precision that one can specify just by inputting numbers is 0.05 units of the currency, for example, by providing a number such as 10.7 as input 2 . On the other hand, the settings for the default tolerance to use allows specifying arbitrary numbers.","title":"Integer Amounts"},{"location":"precision_tolerances.html#resolving-ambiguities","text":"A case that presents itself rarely is one where multiple different precisions are being input for the same currency. In this case, the largest (coarsest) of the inferred input tolerances is used. For example, if we wanted to track income to more than pennies, we might write this: 1999-08-20 * \"Sell\" Assets:US:BRS:ESPP -81 HOOL {26.3125 USD} Assets:US:BRS:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:CA:ESPP:PnL -10.125 USD The amounts we have for USD in this case are 2141.36, 0.08 and -10.125, which infer tolerances of either 0.005 or 0.0005. We select the coarsest amount: this transaction tolerates an imprecision of 0.005 USD.","title":"Resolving Ambiguities"},{"location":"precision_tolerances.html#default-tolerances","text":"When a transaction\u2019s numbers do not provide enough information to infer a tolerance locally , we fall back to some default tolerance value. As seen in previous examples, this may occur either because (a) the numbers associated with the currency we need it for are integral, or (b) sufficient numbers are simply absent from the input. By default, this default tolerance is zero for all currencies. This can be specified with an option, like this: option \"inferred_tolerance_default\" \"*:0.001\" The default tolerance can be further refined for each currency involved, by providing the currency to the option, like this: option \"inferred_tolerance_default\" \"USD:0.003\" If provided, the currency-specific tolerance will be used over the global value. The general form for this option is: option \"inferred_tolerance_default\" \":\" Just to be clear: this option is only used when the tolerance cannot be inferred. If you have overly large rounding errors and the numbers in your transactions do infer some tolerance value, this value will be ignored (e.g., setting it to a larger number to try to address that fix will not work). If you need to loosen up the tolerance, see the \u201c inferred_tolerance_multiplier \u201d in the next section. (Note: I\u2019ve been considering dedicating a special meta-data field to the Commodity directive for this, but this would break from the invariant that meta-data is only there to be used by users and plugins, so I\u2019ve refrained so far.)","title":"Default Tolerances"},{"location":"precision_tolerances.html#tolerance-multiplier","text":"We\u2019re shown previously that when the tolerance value isn\u2019t provided explicitly, that it is inferred from the numbers on the postings. By default, the smallest digit found on those numbers is divided by half to obtain the tolerance because we assume that the institutions which we\u2019re reproducing the transactions apply rounding and so the error should never be more than half. But in reality, you may find that the rounding errors sometime exceed this value. For this reason, we provide an option to set the multiplier for the inferred tolerance: option \"inferred_tolerance_multiplier\" \"1.2\" This value overrides the default multiplier. In this example, for a transaction with postings only with values such as 24.45 CHF, the inferred tolerance for CHF would be +/- 0.012 CHF.","title":"Tolerance Multiplier"},{"location":"precision_tolerances.html#inferring-tolerances-from-cost","text":"There is also a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 / 2 = 0.045 USD and the sum of all such possible rounding errors is calculate for all postings held at cost or converted from a price, and the resulting tolerance is added to the list of candidates used to figure out the tolerance we should use for the given commodity (we use the maximum value of all the inferred tolerances). You turn on the feature like this: option \"infer_tolerance_from_cost\" \"TRUE\" Enabling this flag only makes the tolerances potentially wider, never smaller.","title":"Inferring Tolerances from Cost"},{"location":"precision_tolerances.html#balance-assertions-padding","text":"There are a few other places where approximate comparisons are needed. Balance assertions also compare two numbers: 2015-05-08 balance Assets:Investments:RGAGX 4.271 RGAGX This asserts that the accumulated balance for this account has 4.271 units of RGAGX, plus or minus 0.001 RGAGX. So accumulated values of 4.270 RGAGX up to 4.272 RGAGX will check as asserted. The tolerance is inferred automatically to be 1 unit of the least significant digit of the number on the balance assertion. If you wanted a looser assertion, you could have declared: 2015-05-08 balance Assets:Investments:RGAGX 4.27 RGAGX This assertion would accept values from 4.26 RGAGX to 4.28 RGAGX. Note that the inferred tolerances are also expanded by the inferred tolerance multiplier discussed above.","title":"Balance Assertions & Padding"},{"location":"precision_tolerances.html#tolerances-that-trigger-padding","text":"Pad directives automatically insert transactions to bring account balances in-line with a subsequent balance assertion. The insertion only triggers if the balance differs from the expected value, and the tolerance for this to occur behaves exactly the same as for balance assertions.","title":"Tolerances that Trigger Padding"},{"location":"precision_tolerances.html#explicit-tolerances-on-balance-assertions","text":"Beancount supports the specification of an explicit tolerance amount, like this: 2015-05-08 balance Assets:Investments:RGAGX 4.271 ~ 0.01 RGAGX This feature was added because of some observed peculiarities in Vanguard investment accounts whereby rounding appears to follow odd rules and balances don\u2019t match.","title":"Explicit Tolerances on Balance Assertions"},{"location":"precision_tolerances.html#saving-rounding-error","text":"As we saw previously, transactions don\u2019t have to balance exactly, they allow for a small amount of imprecision. This bothers some people. If you would like to track and measure the residual amounts allowed by the tolerances, Beancount offers an option to automatically insert postings that will make each transaction balance exactly. You enable the feature like this: option \"account_rounding\" \"Equity:RoundingError\" This tells Beancount to insert postings to compensate for the rounding error to an \u201c Equity:RoundingError \u201d account. For example, with the feature enabled, the following transaction: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD will be automatically transformed into this: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD Equity:RoundingError -0.00135 USD You can verify that this transaction balances exactly. If the transaction already balances exactly (this is the case for most transactions) no posting is inserted. Finally, if you require that all accounts be opened explicitly, you should remember to declare the rounding account in your file at an appropriate date, like this: 2000-01-01 open Equity:RoundingError","title":"Saving Rounding Error"},{"location":"precision_tolerances.html#precision-of-inferred-numbers","text":"Beancount is able to infer some missing numbers in the input. For example, the second posting in this transaction is \u201cinterpolated\u201d automatically by Beancount: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash The calculated amount to be inserted from the first posting is -227.2067 USD. Now, you might ask, to which precision is it inserted at? Does it insert 227.2067 USD at the full precision or does the number get rounded to a penny, e.g. 227.21 USD? It depends on the tolerance inferred for that currency. In this example, no tolerance is able to get inferred (there is no USD amount provided other than the cost amount, which is ignored for the purpose of inferring the tolerance), so we have to defer to the default tolerance. If the default tolerance is not overridden in the input file\u2014and therefore is zero\u2014the full precision will be used; no rounding occurs. This will result in the following transaction: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.2067 USD Note that if a tolerance could be inferred from other numbers on that transaction, it would be used for rounding, such as in this example where the Cash posting is rounded to two digits because of the 9.95 USD number on the Commissions posting: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Expenses:Commissions 9.95 USD Assets:Investments:Cash -237.16 USD However, if no inference is possible, and the default tolerance for USD is set to 0.001, the number will be quantized to 0.001 before insertion, that is, 227.207 USD will be stored: option \"default_tolerance\" \"USD:0.001\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Finally, if you enabled the accumulation of rounding error, the posting\u2019s amount will reflect the correct residual, taking into account the rounded amount that was automatically inserted: option \"default_tolerance\" \"USD:0.01\" option \"account_rounding\" \"Equity:RoundingError\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Equity:RoundingError 0.0003 USD","title":"Precision of Inferred Numbers"},{"location":"precision_tolerances.html#porting-existing-input","text":"The inference of tolerance values from the transaction\u2019s numbers is generally good enough to keep existing files working without changes. There may be new errors appearing in older files once we process them with the method described in this document, but they should either point to previously undetected errors in the input, or be fixable with simple addition of a suitable number of digits. As a testimony, porting the author\u2019s very large input file has been a relatively painless process that took less than 1 hour. In order to ease the transition, you will probably want to change the default tolerance for all currencies to match the previous value that Beancount had been using, like this: option \"inferred_tolerance_default\" \"*:0.005\" I would recommend you start with this and fix all errors in your file, then proceed to removing this and fix the rest of errors. This should make it easier to adapt your file to this new behavior. As an example of how to fix a new error\u2026 converting this newly failing transaction from the Integer Amounts section: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD by inserting zero\u2019s to provide a locally inferred value like this: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.00 USD is sufficient to silence the balance check.","title":"Porting Existing Input"},{"location":"precision_tolerances.html#representational-issues","text":"Internally, Beancount uses a decimal number representation (not a binary/float representation, neither rational numbers). Calculations that result in a large number of fractional digits are carried out to 28 decimal places (the default precision from the context of Python\u2019s IEEE decimal implementation). This is plenty sufficient, because the method we propose above rarely trickles these types of numbers throughout the system: the tolerances allows us to post the precise amounts declared by users, and only automatically derived prices and costs will possibly result in precisions calculated to an unrealistic number of digits that could creep into aggregations in the rest of the system.","title":"Representational Issues"},{"location":"precision_tolerances.html#references","text":"The original proposal that led to this implementation can be found here . In particular, the proposal highlights on the other systems have attempted to deal with this issue. There are also some discussions on the mailing-list dedicated to this topic. Note that for the longest time, Beancount used a fixed precision of 0.005 across all currencies. This was eliminated once the method described in this document was implemented. Also, for Balance and Pad directives, there used to be a \u201ctolerance\u201d option that was set by default to 0.015 of any units. This option has been deprecated with the merging of the changes described in this document.","title":"References"},{"location":"precision_tolerances.html#historical-notes","text":"Here\u2019s an overview of the status of numbers rendering in Beancount as of March 2016, from the mailing-list : First, it's important to realize how these numbers are represented in memory. They are using the Decimal representation which beyond being able to accurately representing decimal numbers (as opposed to the approximation that binary floats provides) also contains a specific precision. That is, the number 2.00 is represented differently than the numbers 2.0 and 2.000. The numbers \"remember\" which precision they are represented up to. This is important. When I say rendering the numbers to their \"natural precision\" I mean the precision with which they are represented, i.e., 2.0 renders as \"2.0\", 2.000 renders as \"2.000\". Then, there are two DISTINCT topics: (1) tolerances, and (2) precision. \"Tolerances\" are values used to determine how much imprecision is acceptable in balancing transactions. This is used in the verification stage, to determine how much looseness to allow. It should not affect how numbers are rendered. \"Precision\" is perhaps a bit of a misnomer: By that I'm referring to is how many digits the numbers are to be rendered with. Once upon a time - after the shell was already written - these concepts weren't well defined in Beancount and I wasn't dealing with these things consistently. At some point it became clear what I needed to do and I created a class called \"DisplayContext\" which could contain appropriate settings for rendering the precision of numbers for each currency (each currency tends to have its own most common rendering precision, e.g. two digits for USD, one digit for MXN, no digits for JPY and in reports we're typically fine rounding the actual numbers to that precision). So an instance of this DisplayContext is automatically instantiated in the parser and in order to avoid the user having to set these values manually - for Beancount to \"do the right thing\" by default - it is able to accumulate the numbers seen and to deduce the most common and maximum number of digits used from the input, and to use that as the default number of digits for rendering numbers. The most common format/number of digits is used to render the number of units, and the maximum number of digits seen is used to render costs and prices. In addition, this class also has capabilities for aligning to the decimal dot and to insert commas on thousands as well. It separates the control of the formatting from the numbers themselves. MOST of the code that renders numbers uses the DisplayContext (via the to_string() methods) to convert the numbers into strings, such as the web interface and explicit text reports. But NOT ALL... there's a bit of HISTORY here... the SQL shell uses some old special-purpose code to render numbers that I never bothered to convert to the DisplayContext class. There's a TODO item for it. It needs to get converted at some point, but I've neglected doing this so far because I have much bigger plans for the SQL query engine that involve a full rewrite of it with many improvements and I figured I'd do that then. If you recall, the SQL query engine was a prototype, and actually it works, but it is not well covered by unit tests. My purpose with it was to discover through usage what would be useful and to then write a v2 of it that would be much better. Now, about that PRINT command... this is not intended as a reporting tool. The printer's purpose is to print input that accurately represents the content of the transactions. In order to do this, it needs to render the numbers at their \"natural\" precision, so that when they get read back in, they parse into the very same number, that is, with the same number of digits (even if zeros). For this reason, the PRINT command does not attempt to render using the DisplayContext instance derived from the input file - this is on purpose. I could change that, but then round-trip would break: the rounding resulting from formatting using the display context may output transactions which don't balance anymore. As you can see, it's not an obvious topic... Hopefully this should allow you to understand what comes out of Beancount in terms of the precision of the numbers it renders. Note: \"default_tolerances\" has been renamed to \"inferred_tolerance_default\" recently because the name was too general and confusing. Old name will work but generate a warning. I just noticed from your comments and some grepping around that the \"render_commas\" option is not used anymore. I'm not sure how that happened, but I'll go ad fix that right away and set the default value of the DisplayContext derived from the input. I should probably also convert the SQL shell rendering to use the display context regardless of future plans, so that it renders consistently with all the rest. Not sure I can do that this weekend, but I'll log a ticket, here . I hope this helps. You're welcome to ask questions if the above isn't clear. I'm sorry if this isn't entirely obvious... there's been a fair bit of history there and there's a lot of code. I should review the naming of options, I think the tolerance options all have \"tolerance\" in their name, but there aren't options to override the rendering and when I add them they should all have a common name as well.","title":"Historical Notes"},{"location":"precision_tolerances.html#further-reading","text":"What Every Computer Scientist Should Know About Floating-Point Arithmetic This stands in contrast to Ledger which attempts to infer the precision based on other transactions recently parsed in the file, in file order. This has the unfortunate effect of creating \u201ccross-talk\u201d between the transactions in terms of what precision can be used. \u21a9 Note that due to the way Beancount represents numbers internally, it is also not able to distinguish between \u201c230\u201d and \u201c230.\u201d; these parse into the same representation for Beancount. Therefore, we are not able to use that distinction in the input to support a precision of 0.5. \u21a9","title":"Further Reading"},{"location":"rounding_precision_in_beancount.html","text":"Proposal: Rounding & Precision in Beancount \uf0c1 Martin Blais , October 2014 This document describes the problem of rounding errors on Beancount transactions and how they are handled. It also includes a proposal for better handling precision issues in Beancount. Motivation \uf0c1 Balancing Precision \uf0c1 Balancing transactions cannot be done precisely. This has been discussed on the Ledger mailing-list before . It is necessary to allow for some tolerance on the amounts used to balance a transaction. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits, something like this example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.21 USD If you calculate it, the first posting\u2019s precise balance amount is 227.2067 USD, not 227.21 USD. However, the broker company managing the investment account will apply rounding to the closest cent for the cash withdrawal, and the rounded amount is the correct one to be used. This transaction has to balance; we need to allow for some looseness somehow. The great majority of the cases where mathematical operations occur involve the conversion from a number of units and a price or a cost to a corresponding cash value (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters. Automatic Rounding \uf0c1 Another related issue is that of automatically rounding amounts for interpolated numbers. Let\u2019s take our original problematic example again: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash ;; Interpolated posting = -227.2067 USD Here the amount from the second posting is interpolated from the balance amount for the first posting. Ideally, we should find a way to specify how it should round to 2 fractional digits of precision. Note that this affects interpolated prices and costs too: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {USD} Assets:Investments:Cash -227.21 USD Here, the cost is intended to be automatically calculated from the cash legs: 227.21 / 4.27 = 53.2107728337\u2026 USD. The correct cost to be inferred is also the rounded amount of 53.21 USD. We would like a mechanism to allow us to infer the desired precision. This mechanism cannot unfortunately be solely based on commodity: different accounts may track currencies with different precisions. As a real-world example, I have a retail FOREX trading account that really uses 4 digits of precision for its prices and deposits. Precision of Balance Assertions \uf0c1 The precision of a balance assertions is also subject to this problem, assertions like this one: 2014-04-01 balance Assets:Investments:Cash 4526.77 USD The user does not intend for this balance check to precisely sum up to 4526.77000000\u2026 USD. However, it this cash account previously received a deposit with a greater precision as in the previous section\u2019s example, then we have a problem. Now the cash amount contains some of the crumbs deposited from the interpolation (0.0067 USD). If we were able to find a good solution for the automatic rounding of postings in the previous section, this would not be a problem. But in the meantime, we must find a solution. Beancount\u2019s current approach is a kludge: it uses a user-configurable tolerance of 0.0150 (in any unit). We\u2019d like to change this so that the tolerance used is able to depend on the commodity, the account, or even the particular directive in use. Other Systems \uf0c1 Other command-line accounting systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used. Proposal \uf0c1 Automatically Inferring Tolerance \uf0c1 Beancount should derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults. That is, for each transaction, it will inspect postings with simple amounts (no cost, no price) and infer the precision to be used for tolerance as half of that of the most precise amount entered by the user on this transaction. For example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.278 RGAGX {53.21 USD} Assets:Investments:Cash -227.6324 USD Expenses:Commissions 9.95 USD The number of digits for the precision to be used here is the maximum of the 2nd and 3rd postings, that is, max(4, 2) = 4. The first postings is ignored because its amount is the result of a mathematical operation. The tolerance value should be half of the most precise digit, that is 0.00005 USD. This should allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision\u2026 an integer should imply exact matching. The user could specify a single trailing period to imply a sub-dollar precision. For example, the following transaction should fail to balance because the calculated amount is 999.999455 USD: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000 USD Instead, the user should explicitly allow for some tolerance to be used: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000. USD Or better, use 1000.00 USD. This has the disadvantage that is prevents the user from specifying the simpler integer amount. I\u2019m not sure if this is a big deal. Finally, no global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context. Inference on Amounts Held at Cost \uf0c1 An idea from by Matthew Harris ( here ) is that we could also use the value of the to the smallest decimal of the number of units times the cost as a number to use in establishing the tolerance for balancing transactions. For example, in the following transaction: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} ... The tolerance value that could be derived is 0.01 RGAGX x 42.6439 USD = 0.426439 USD The original use case presented by Matthew was of a transaction that did not contain a simple amount, just a conversion with both legs held at cost: 2011-01-25 * \"Transfer of Assets, 3467.90 USD\" * Assets:RothIRA:Vanguard:VTIVX 250.752 VTIVX {18.35 USD} @ 13.83 USD * Assets:RothIRA:DodgeCox:DODGX -30.892 DODGX {148.93 USD} @ 112.26 USD I\u2019ve actually tried to implement this and the resulting tolerances are either unacceptably wide or unacceptably small. It does not work well in practice so I\u2019ve abandoned the idea. Automated Rounding \uf0c1 For values that are automatically calculated, for example, on auto-postings where the remaining value is derived automatically, we should consider rounding the values. No work has been done on this yet; these values are currently not rounded. Fixing Balance Assertions \uf0c1 To fix balance assertions, we will derive the required precision by the number of digits used in the balance amount itself, by looking at the most precision fractional digit and using half of that digit\u2019s value to compute the tolerance: 2014-04-01 balance Assets:Investments:Cash 4526.7702 USD This balance check implies a precision of 0.00005 USD. If you use an integer number of units, no tolerance is allowed. The precise number should match: 2014-04-01 balance Assets:Investments:Cash 4526 USD If you want to allow for sub-dollar variance, use a single comma: 2014-04-01 balance Assets:Investments:Cash 4526. USD This balance check implies a precision of 0.50 USD. Approximate Assertions \uf0c1 Another idea, proposed in this ticket on Ledger , proposes an explicitly approximate assertion. We could implement it this way (just an idea): 2014-04-01 balance Assets:Investments:Cash 4526.00 +/- 0.05 USD Accumulating & Reporting Residuals \uf0c1 In order to explicitly render and monitor the amount of rounding errors that occur in a Ledger, we should accumulate it to an Equity account , such as \u201cEquity:Rounding\u201d. This should be turned on optionally. It should be possible for the user to specify an account to be used to accumulate the error. Whenever a transaction does not balance exactly, the residual, or rounding error, will be inserted as a posting of the transaction to the equity account. By default, this accumulation should be turned off. It\u2019s not clear whether the extra postings will be disruptive yet (if they\u2019re not, maybe this should be turned on by default; practice will inform us). Implementation \uf0c1 The implementation of this proposal is documented here .","title":"Rounding Precision in Beancount"},{"location":"rounding_precision_in_beancount.html#proposal-rounding-precision-in-beancount","text":"Martin Blais , October 2014 This document describes the problem of rounding errors on Beancount transactions and how they are handled. It also includes a proposal for better handling precision issues in Beancount.","title":"Proposal: Rounding & Precision in Beancount"},{"location":"rounding_precision_in_beancount.html#motivation","text":"","title":"Motivation"},{"location":"rounding_precision_in_beancount.html#balancing-precision","text":"Balancing transactions cannot be done precisely. This has been discussed on the Ledger mailing-list before . It is necessary to allow for some tolerance on the amounts used to balance a transaction. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits, something like this example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.21 USD If you calculate it, the first posting\u2019s precise balance amount is 227.2067 USD, not 227.21 USD. However, the broker company managing the investment account will apply rounding to the closest cent for the cash withdrawal, and the rounded amount is the correct one to be used. This transaction has to balance; we need to allow for some looseness somehow. The great majority of the cases where mathematical operations occur involve the conversion from a number of units and a price or a cost to a corresponding cash value (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters.","title":"Balancing Precision"},{"location":"rounding_precision_in_beancount.html#automatic-rounding","text":"Another related issue is that of automatically rounding amounts for interpolated numbers. Let\u2019s take our original problematic example again: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash ;; Interpolated posting = -227.2067 USD Here the amount from the second posting is interpolated from the balance amount for the first posting. Ideally, we should find a way to specify how it should round to 2 fractional digits of precision. Note that this affects interpolated prices and costs too: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {USD} Assets:Investments:Cash -227.21 USD Here, the cost is intended to be automatically calculated from the cash legs: 227.21 / 4.27 = 53.2107728337\u2026 USD. The correct cost to be inferred is also the rounded amount of 53.21 USD. We would like a mechanism to allow us to infer the desired precision. This mechanism cannot unfortunately be solely based on commodity: different accounts may track currencies with different precisions. As a real-world example, I have a retail FOREX trading account that really uses 4 digits of precision for its prices and deposits.","title":"Automatic Rounding"},{"location":"rounding_precision_in_beancount.html#precision-of-balance-assertions","text":"The precision of a balance assertions is also subject to this problem, assertions like this one: 2014-04-01 balance Assets:Investments:Cash 4526.77 USD The user does not intend for this balance check to precisely sum up to 4526.77000000\u2026 USD. However, it this cash account previously received a deposit with a greater precision as in the previous section\u2019s example, then we have a problem. Now the cash amount contains some of the crumbs deposited from the interpolation (0.0067 USD). If we were able to find a good solution for the automatic rounding of postings in the previous section, this would not be a problem. But in the meantime, we must find a solution. Beancount\u2019s current approach is a kludge: it uses a user-configurable tolerance of 0.0150 (in any unit). We\u2019d like to change this so that the tolerance used is able to depend on the commodity, the account, or even the particular directive in use.","title":"Precision of Balance Assertions"},{"location":"rounding_precision_in_beancount.html#other-systems","text":"Other command-line accounting systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used.","title":"Other Systems"},{"location":"rounding_precision_in_beancount.html#proposal","text":"","title":"Proposal"},{"location":"rounding_precision_in_beancount.html#automatically-inferring-tolerance","text":"Beancount should derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults. That is, for each transaction, it will inspect postings with simple amounts (no cost, no price) and infer the precision to be used for tolerance as half of that of the most precise amount entered by the user on this transaction. For example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.278 RGAGX {53.21 USD} Assets:Investments:Cash -227.6324 USD Expenses:Commissions 9.95 USD The number of digits for the precision to be used here is the maximum of the 2nd and 3rd postings, that is, max(4, 2) = 4. The first postings is ignored because its amount is the result of a mathematical operation. The tolerance value should be half of the most precise digit, that is 0.00005 USD. This should allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision\u2026 an integer should imply exact matching. The user could specify a single trailing period to imply a sub-dollar precision. For example, the following transaction should fail to balance because the calculated amount is 999.999455 USD: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000 USD Instead, the user should explicitly allow for some tolerance to be used: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000. USD Or better, use 1000.00 USD. This has the disadvantage that is prevents the user from specifying the simpler integer amount. I\u2019m not sure if this is a big deal. Finally, no global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context.","title":"Automatically Inferring Tolerance"},{"location":"rounding_precision_in_beancount.html#inference-on-amounts-held-at-cost","text":"An idea from by Matthew Harris ( here ) is that we could also use the value of the to the smallest decimal of the number of units times the cost as a number to use in establishing the tolerance for balancing transactions. For example, in the following transaction: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} ... The tolerance value that could be derived is 0.01 RGAGX x 42.6439 USD = 0.426439 USD The original use case presented by Matthew was of a transaction that did not contain a simple amount, just a conversion with both legs held at cost: 2011-01-25 * \"Transfer of Assets, 3467.90 USD\" * Assets:RothIRA:Vanguard:VTIVX 250.752 VTIVX {18.35 USD} @ 13.83 USD * Assets:RothIRA:DodgeCox:DODGX -30.892 DODGX {148.93 USD} @ 112.26 USD I\u2019ve actually tried to implement this and the resulting tolerances are either unacceptably wide or unacceptably small. It does not work well in practice so I\u2019ve abandoned the idea.","title":"Inference on Amounts Held at Cost"},{"location":"rounding_precision_in_beancount.html#automated-rounding","text":"For values that are automatically calculated, for example, on auto-postings where the remaining value is derived automatically, we should consider rounding the values. No work has been done on this yet; these values are currently not rounded.","title":"Automated Rounding"},{"location":"rounding_precision_in_beancount.html#fixing-balance-assertions","text":"To fix balance assertions, we will derive the required precision by the number of digits used in the balance amount itself, by looking at the most precision fractional digit and using half of that digit\u2019s value to compute the tolerance: 2014-04-01 balance Assets:Investments:Cash 4526.7702 USD This balance check implies a precision of 0.00005 USD. If you use an integer number of units, no tolerance is allowed. The precise number should match: 2014-04-01 balance Assets:Investments:Cash 4526 USD If you want to allow for sub-dollar variance, use a single comma: 2014-04-01 balance Assets:Investments:Cash 4526. USD This balance check implies a precision of 0.50 USD.","title":"Fixing Balance Assertions"},{"location":"rounding_precision_in_beancount.html#approximate-assertions","text":"Another idea, proposed in this ticket on Ledger , proposes an explicitly approximate assertion. We could implement it this way (just an idea): 2014-04-01 balance Assets:Investments:Cash 4526.00 +/- 0.05 USD","title":"Approximate Assertions"},{"location":"rounding_precision_in_beancount.html#accumulating-reporting-residuals","text":"In order to explicitly render and monitor the amount of rounding errors that occur in a Ledger, we should accumulate it to an Equity account , such as \u201cEquity:Rounding\u201d. This should be turned on optionally. It should be possible for the user to specify an account to be used to accumulate the error. Whenever a transaction does not balance exactly, the residual, or rounding error, will be inserted as a posting of the transaction to the equity account. By default, this accumulation should be turned off. It\u2019s not clear whether the extra postings will be disruptive yet (if they\u2019re not, maybe this should be turned on by default; practice will inform us).","title":"Accumulating & Reporting Residuals"},{"location":"rounding_precision_in_beancount.html#implementation","text":"The implementation of this proposal is documented here .","title":"Implementation"},{"location":"running_beancount_and_generating_reports.html","text":"Running Beancount & Generating Reports \uf0c1 Martin Blais , July-Sep 2014 http://furius.ca/beancount/doc/tools Introduction Tools bean-check bean-report bean-query bean-web Global Pages View Reports Pages bean-bake bean-doctor Context bean-format bean-example Filtering Transactions Reports Balance Reports Trial Balance (balances) Balance Sheet (balsheet) Opening Balances (openbal) Income Statement (income) Journal Reports Journal (journal) Rendering at Cost Adding a Balance Column Character Width Precision Compact, Normal or Verbose Summary Equivalent SQL Query Conversions (conversions) Documents (documents) Holdings Reports Holdings & Aggregations (holdings*) Net Worth (networth) Other Report Types Cash Prices (prices) Statistics (stats) Update Activity (activity) Introduction \uf0c1 This document describes the tools you use to process Beancount input files, and many of the reports available from it. The syntax of the language is described in the Beancount Language Syntax document. This manual only covers the technical details for using Beancount from the command-line. Tools \uf0c1 bean-check \uf0c1 bean-check is the program you use to verify that your input syntax and transactions work correctly. All it does is load your input file and run the various plugins you configured in it, plus some extra validation checks. It report errors (if any), and then exits. You run it on your input file, like this: bean-check /path/to/my/file.beancount If there are no errors, there should be no output, it should exit quietly. If there were errors, they will be printed to stderr with the filename, line number and error description (in a format that is understood by Emacs, so you can just use next-error and previous-error to navigate to the file if you want): /home/user/myledger.beancount:44381: Transaction does not balance: 34.46 USD 2014-07-12 * \"Black Iron Burger\" \"\" Expenses:Food:Restaurant 17.23 USD Assets:Cash 17.23 USD You should always fix all the errors before producing reports. bean-report \uf0c1 This is the main tool used to extract specialized reports to the console in text or one of the various other formats. You invoke it like this: bean-report /path/to/my/file.beancount For example: bean-report /path/to/my/file.beancount balances There are many reports available. See the section on reports for a description of the main ones. If you want to produce the full list of reports, ask it for help: bean-report --help-reports Report names can sometimes accept arguments. At the moment the arguments are specified as part of the report name itself, often separated by a colon (:), like this: bean-report /path/to/my/file.beancount balances:Vanguard There are a few special reports you should know about: check, or validate: This is the same as running the bean-check command. print: This simply prints out the entries that Beancount has parsed, in Beancount syntax. This can be used to confirm that Beancount has read and interpreted your input data correctly (if you\u2019re debugging something difficult). The other reports are what you\u2019d expect: they print out various tables of aggregations of amounts. The reports you can generate are described in a dedicated section below. PLEASE NOTE! At the moment of release, the list of reports available from the web page will differ from the list available from the console. In a future release, I will consolidate those two lists and all the reports that are available from the web pages will also be available from the console, and in many different formats. Stay tuned. bean-query \uf0c1 Beancount\u2019s parsed list of transactions and postings is like an in-memory database. bean-query is a command-line tool that acts like a client to that in-memory database in which you can type queries in a variant of SQL. You invoke it like this: bean-query /path/to/my/file.beancount Input file: \"/path/to/my/file.beancount\" Ready with 14212 directives (21284 postings in 8879 transactions). beancount> _ More details are available in its own document . bean-web \uf0c1 bean-web serves all the reports on a web server that runs on your computer. You run it like this: bean-web /path/to/my/file.beancount It will serve all pages on port 8080 on your machine. Navigate to http://localhost:8080 with a web browser. You should be able to click your way through all the reports easily. The web interface provides a set of global pages and a set of report pages for each \u201cview.\u201d Global Pages \uf0c1 The top-level table of contents page provides links to all the global pages at the top: The table of contents (the page you\u2019re looking at) A list of the errors that occurred in your ledger file A view of the source code of your Ledger file (this is used by various other links when referring to a location in your input file.) The table of contents provides a convenient list of links to all the common views, such as \u201cview by year\u201d, \u201cview by tag\u201d, and of course, \u201cview all transactions.\u201d There are a few more. View Reports Pages \uf0c1 When you click on a view report page, you enter a set of pages for the subset of transactions for that view. Various reports about those transactions are available from here: Opening balances (a balance sheet at the beginning of the view) Balance sheet (a balance sheet at the end of the view) Income statement (for the period of the view) Various journals for each account (just click on an account name) Various reports of holdings at the end of the view Lists of documents and prices included in the view\u2019s entries Some statistics about the view data \u2026 and much more. There should be an index of all the available view reports. bean-bake \uf0c1 bean-bake runs a bean-web instance and bakes all the pages to a directory: bean-bake /path/to/my/file.beancount myfinances It also support baking directly to an archive file: bean-bake /path/to/my/file.beancount myfinances.zip Various compression methods are supported, e.g. .tar.gz. This is useful to share the web interface with your accountant or other people, who usually don\u2019t have the ability to run Beancount. The page links have all been converted to relative links, and they should be able to extract the archive to a directory and browse all the reports the same way you do with bean-web . bean-doctor \uf0c1 This is a debugging tool used to perform various diagnostics and run debugging commands, and to help provide information for reporting bugs. For example, it can do the following: List the Beancount dependencies that are installed on your machine, and the ones that are missing. It can tell you what you\u2019re missing that you should be installing. Print a dump of the parser\u2019s lexer tokens for your input file. If you report a parsing bug, it can be useful to look at the lexer output (if you know what you\u2019re doing). It can run your input file through a parsing round-trip, that is, print out the file and re-read it again and compare it. This is a useful parser and printer test. It can check that a directory hierarchy corresponds to a Beancount input file\u2019s chart-of-accounts, and reporting directories that do not comply. This is useful in case you decide to change some account names and are maintaining a corresponding archive of documents which needs to be adjusted accordingly. Context \uf0c1 It can list the context upon which a transaction is applied, i.e., the inventory balances of each account the transaction postings modify, before and after it is applied. Use it like this: $ bean-doctor context /home/blais/accounting/blais.beancount 28514 /home/blais/accounting/blais.beancount:28513: ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; Assets:US:ETrade:Cash 8143.97 USD 2014-07-25 * \"(TRD) BOT +50 BND @82.10\" ^273755872 Assets:US:ETrade:BND 50.00 BND {82.10 USD} Assets:US:ETrade:Cash -4105.00 USD ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; ! Assets:US:ETrade:BND 50.00 BND {82.10 USD} ; ! Assets:US:ETrade:Cash 4038.97 USD There is a corresponding Emacs binding (C-c x) to invoke this around the cursor. bean-format \uf0c1 This pure text processing tool will reformat Beancount input to right-align all the numbers at the same, minimal column. It left-aligns all the currencies. It only modifies whitespace. This tool accepts a filename as arguments and outputs the aligned file on stdout (similar to UNIX cat ). bean-example \uf0c1 This program generates an example Beancount input file. See the Tutorial for more details about the contents of this example file. Filtering Transactions \uf0c1 In order to produce different views of your financial transactions, we select a subset of full list of parsed transactions, for example, \u201call the transactions that occurred in year 2013\u201d, and then use that to produce the various available reports that Beancount provides. At the moment, only a preset list of filters are available as \u201cviews\u201d from the web interface. These views include: All transactions Transactions that occur in a particular year Transactions with a particular tag Transactions with a particular payee Transactions that involve at least one account with a particular name component At the moment, in order to access reports from these subsets of transactions, you need to use the bean-web web interface, and click on the related keyword in the root global page, which enters you into a set of reports for that view. Reports \uf0c1 The whole point of entering your transactions in a single input file in the first place is that it allows you to sum, filter, aggregate and arrange various subsets of your data into well-known reports. There are three distinct ways to produce reports from Beancount: by using bean-web and browsing to a view and then to a specific report (this is the easy way), by using bean-report and providing the name of a desired report (and possibly some report-specific arguments), and by using bean-query and requesting data by specifying an SQL statement. Reports can sometimes be rendered in different file formats. Each report type will support being rendered in a list of common ones, such as console text, HTML and CSV. Some reports render in Beancount syntax itself, and we simply call this format name \u201cbeancount.\u201d There are many types of reports available, and there will be many more in the future, as many of the features on the roadmap involve new types of output. This section provides an overview of the most common ones. Use bean-report to inquire about the full list supported by your installed version: bean-report --help-reports PLEASE NOTE! At the moment, the sets of reports that are available from the web interface and from the console are different, though there is some overlap. In a subsequent version, the list of reports will be reconciled and all reports will be made available via both the web interface and the console, in a variety of data formats (text, CSV, HTML and maybe others.) For now, we will document these in the sections below. Balance Reports \uf0c1 All the balance reports are similar in that they produce tables of some set of accounts and their associated balances: [output] |-- Assets | `-- US | |-- BofA | | `-- Checking 596.05 USD | |-- ETrade | | |-- Cash 5,120.50 USD | | |-- GLD 70.00 GLD | | |-- ITOT 17.00 ITOT | | |-- VEA 36.00 VEA | | `-- VHT 294.00 VHT | |-- Federal | | `-- PreTax401k | |-- Hoogle | | `-- Vacation 337.26 VACHR | `-- Vanguard ... Balances for commodities held \u201cat cost\u201d are rendered at their book value. (Unrealized gains, if any, are inserted as separate transactions by an optional plugin and the result amounts get mixed in with the cost basis if rendered in the same account.) If an account\u2019s balance contains many different types of currencies (commodities not held \u201cat cost\u201d, such as dollars, euros, yen), each gets printed on its own line. This can render the balance column a bit too busy and messes with the vertical regularity of account names. This is to some extent an unavoidable compromise, but in practice, there are only a small number of commodities that form the large majority of a user\u2019s ledger: the home country\u2019s currency units. To this extent, amounts in common currencies can be broken out into their own column using the \u201c operating_currency \u201d option: option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" You may use this option multiple times if you have many of them (this is my case, for instance, because I am an expat and hold assets in both my host and home countries). Declaring operating currencies also hold the advantage that the name of the currency need not be rendered, and it can thus more easily be imported into a spreadsheet. Finally, some accounts are deemed \u201cactive\u201d if they have not been closed. A closed account with no transactions in the filtered set of transactions will not be rendered. Trial Balance ( balances ) \uf0c1 A trial balance report simply produces a table of final balances of all the active accounts, with all the accounts rendered vertically. The sum total of all balances is reported at the bottom. Unlike a balance sheet, it may not always balance to zero because of currency conversions. (This is the equivalent of Ledger\u2019s bal report.) The equivalent bean-query command is: SELECT account, sum(position) GROUP BY account ORDER BY account; Balance Sheet ( balsheet ) \uf0c1 A balance sheet is a snapshot of the balances of the Assets, Liabilities and Equity accounts at a particular point in time. In order to build such a report, we have to move balances from the other accounts to it: compute the balances of the Income and Expenses account at that point in time, insert transactions that will zero out balances from these accounts by transferring them to an equity account ( Equity:Earnings:Current ), render a tree of the balances of the Assets accounts on the left side, render a tree of the Liabilities accounts on the right side, render a tree of the Equity accounts below the Liabilities accounts. See the introduction document for an example. Note that the Equity accounts include the amounts reported from the Income and Expenses accounts, also often called \u201cNet Income.\u201d Also, in practice, we make two transfers because we\u2019re typically looking at a reporting period, and we want to differentiate between Net Income amounts transferred before the beginning of the period ( Equity:Earnings:Previous ) and during the period itself ( Equity:Earnings:Current ). And similar pair of transfers is carried out in order to handle currency conversions (this is a bit of a hairy topic, but one with a great solution; refer to the dedicated document if you want all the details). The equivalent bean-query command is: SELECT account, sum(position) FROM CLOSE ON 2016-01-01 GROUP BY account ORDER BY account; Opening Balances ( openbal ) \uf0c1 The opening balances report is simply a balance sheet drawn at the beginning of the reporting period. This report only makes sense for a list of filtered entries that represents a period of time, such as \u201cyear 2014.\u201d The balance sheet is generated using only the summarization entries that were synthesized when the transactions were filtered (see the double-entry method document ). Income Statement ( income ) \uf0c1 An income statement lists the final balances of the Income and Expenses accounts. It represents a summary of the transient activity within these accounts. If the balance sheet is the snapshot at a particular point in time, this is the difference between the beginning and the end of a period (in our case: of a filtered set of transactions). The balances of the active Income accounts are rendered on the left, and those of the active Expenses accounts on the right. See the introduction document for an example. The difference between the total of Income and Expenses balances is the Net Income. Note that the initial balances of the Income and Expenses accounts should have been zero\u2019ed out by summarization transactions that occur at the beginning of the period, because we\u2019re only interested in the changes in these accounts. Journal Reports \uf0c1 The reports in this section render lists of transactions and other directives in a linear fashion. Journal ( journal ) \uf0c1 This report is the equivalent of an \u201caccount statement\u201d from an institution, a list of transactions with at least one posting in that account. This is the equivalent of Ledger\u2019s register report ( reg ). You generate a journal report like this: bean-report myfile.beancount journal \u2026 By default, this renders a journal of all the transactions, which is unlikely to be what you want. Select a particular account to render like this: bean-report myfile.beancount journal -a Expenses:Restaurant At the moment, the \u201c -a \u201d option accepts only a complete account name, or the name of one of the parent accounts. Eventually we will extend it to handle expressions. Rendering at Cost \uf0c1 The numbers column on the right displays the changes from the postings of the selected account. Notice that only the balances for the postings affecting the given account are rendered. The change column renders the changes in the units affected. For example, if this posting is selected: Assets:Investments:Apple 2 AAPL {402.00 USD} The value reported for the change will be \u201c 2 AAPL \u201d. If you would like to render the values at cost, use the \u201c --at-cost \u201d or \u201c -c \u201d option, which will in this case render \u201c 804.00 USD \u201d instead. There is no \u201cmarket value\u201d option. Unrealized gains are automatically inserted at the end of the history by the \u201c beancount.plugins.unrealized \u201d plugin. See options for that plugin to insert its unrealized gains. Note that if the sum of the selected postings is zero, no amount is rendered in the change column. Adding a Balance Column \uf0c1 If you want to add a column that sums up the running balance for the reported changes, use the \u201c --render-balance \u201d or \u201c -b \u201d option. This does not always make sense to report, so it is up to you to decide whether you want a running balance. Character Width \uf0c1 By default, the report will be as wide as your terminal allows. Restrict the width to a set number of characters with the \u201c-w\u201d option. Precision \uf0c1 The number of fractional digits for the number rendering can be specified via \u201c --precision \u201d or \u201c -k \u201d. Compact, Normal or Verbose \uf0c1 In its normal operation, Beancount renders an empty line between transactions. This helps delineate transactions where there are multiple currencies affected, as they render on separate lines. If you want a more compact rendering, use the \u201c --compact \u201d or \u201c -x \u201d option. On the other hand, if you want to render the affected postings under the transaction line, use the \u201c --verbose \u201d or \u201c -X \u201d option. Summary \uf0c1 Here is a summary of its arguments: optional arguments: -h, --help show this help message and exit -a ACCOUNT, --account ACCOUNT Account to render -w WIDTH, --width WIDTH The number of characters wide to render the report to -k PRECISION, --precision PRECISION The number of digits to render after the period -b, --render-balance, --balance If true, render a running balance -c, --at-cost, --cost If true, render values at cost -x, --compact Rendering compactly -X, --verbose Rendering verbosely Equivalent SQL Query \uf0c1 The equivalent bean-query command is: SELECT date, flag, description, account, cost(position), cost(balance); Conversions ( conversions ) \uf0c1 This report lists the total of currency conversions that result from the selected transactions. (Most people won\u2019t need this.) Documents ( documents ) \uf0c1 This report produces an HTML list of all the external documents found in the ledger, either from explicit directives or from plugins that automatically find the documents and add them to the stream of transactions. Holdings Reports \uf0c1 These reports produces aggregations for assets held at cost. Holdings & Aggregations ( holdings* ) \uf0c1 This report produces a detailed list of all holdings found in the ledger. You can produce aggregates by commodity and accounts using the \u201c-g\u201d option. Net Worth ( networth ) \uf0c1 This report produces a short summary of the net worth (equity) of the ledger, in each of the operating currencies. Other Report Types \uf0c1 Cash \uf0c1 This report renders balances in commodities not held at cost, in other words, cash: bean-report example.beancount cash -c USD Account Units Currency Cost Currency Average Cost Price Book Value Market Value -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ Assets:US:BofA:Checking 596.05 USD USD 596.05 596.05 Assets:US:ETrade:Cash 5,120.50 USD USD 5,120.50 5,120.50 Assets:US:Hoogle:Vacation 337.26 VACHR Assets:US:Vanguard:Cash -0.02 USD USD -0.02 -0.02 Liabilities:US:Chase:Slate -2,891.85 USD USD -2,891.85 -2,891.85 -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ The report allows you to convert all currencies to a common currency (in the example above, \"convert everything to USD\"). There's also an option to report only on the operating currencies. I use this to get an overview of all uninvested cash. Prices ( prices ) \uf0c1 This report renders a list of price points for a base currency in terms of a quote currency. The list is sorted by date. You can output this table in beancount format as well. This is convenient to save a price database to a file, that can then be combined and loaded into another input file. Statistics ( stats ) \uf0c1 This report simply provides various statistics on the parsed entries. Update Activity ( activity ) \uf0c1 This table renders for each account the date of the last entry.","title":"Running Beancount and Generating Reports"},{"location":"running_beancount_and_generating_reports.html#running-beancount-generating-reports","text":"Martin Blais , July-Sep 2014 http://furius.ca/beancount/doc/tools Introduction Tools bean-check bean-report bean-query bean-web Global Pages View Reports Pages bean-bake bean-doctor Context bean-format bean-example Filtering Transactions Reports Balance Reports Trial Balance (balances) Balance Sheet (balsheet) Opening Balances (openbal) Income Statement (income) Journal Reports Journal (journal) Rendering at Cost Adding a Balance Column Character Width Precision Compact, Normal or Verbose Summary Equivalent SQL Query Conversions (conversions) Documents (documents) Holdings Reports Holdings & Aggregations (holdings*) Net Worth (networth) Other Report Types Cash Prices (prices) Statistics (stats) Update Activity (activity)","title":"Running Beancount & Generating Reports"},{"location":"running_beancount_and_generating_reports.html#introduction","text":"This document describes the tools you use to process Beancount input files, and many of the reports available from it. The syntax of the language is described in the Beancount Language Syntax document. This manual only covers the technical details for using Beancount from the command-line.","title":"Introduction"},{"location":"running_beancount_and_generating_reports.html#tools","text":"","title":"Tools"},{"location":"running_beancount_and_generating_reports.html#bean-check","text":"bean-check is the program you use to verify that your input syntax and transactions work correctly. All it does is load your input file and run the various plugins you configured in it, plus some extra validation checks. It report errors (if any), and then exits. You run it on your input file, like this: bean-check /path/to/my/file.beancount If there are no errors, there should be no output, it should exit quietly. If there were errors, they will be printed to stderr with the filename, line number and error description (in a format that is understood by Emacs, so you can just use next-error and previous-error to navigate to the file if you want): /home/user/myledger.beancount:44381: Transaction does not balance: 34.46 USD 2014-07-12 * \"Black Iron Burger\" \"\" Expenses:Food:Restaurant 17.23 USD Assets:Cash 17.23 USD You should always fix all the errors before producing reports.","title":"bean-check"},{"location":"running_beancount_and_generating_reports.html#bean-report","text":"This is the main tool used to extract specialized reports to the console in text or one of the various other formats. You invoke it like this: bean-report /path/to/my/file.beancount For example: bean-report /path/to/my/file.beancount balances There are many reports available. See the section on reports for a description of the main ones. If you want to produce the full list of reports, ask it for help: bean-report --help-reports Report names can sometimes accept arguments. At the moment the arguments are specified as part of the report name itself, often separated by a colon (:), like this: bean-report /path/to/my/file.beancount balances:Vanguard There are a few special reports you should know about: check, or validate: This is the same as running the bean-check command. print: This simply prints out the entries that Beancount has parsed, in Beancount syntax. This can be used to confirm that Beancount has read and interpreted your input data correctly (if you\u2019re debugging something difficult). The other reports are what you\u2019d expect: they print out various tables of aggregations of amounts. The reports you can generate are described in a dedicated section below. PLEASE NOTE! At the moment of release, the list of reports available from the web page will differ from the list available from the console. In a future release, I will consolidate those two lists and all the reports that are available from the web pages will also be available from the console, and in many different formats. Stay tuned.","title":"bean-report"},{"location":"running_beancount_and_generating_reports.html#bean-query","text":"Beancount\u2019s parsed list of transactions and postings is like an in-memory database. bean-query is a command-line tool that acts like a client to that in-memory database in which you can type queries in a variant of SQL. You invoke it like this: bean-query /path/to/my/file.beancount Input file: \"/path/to/my/file.beancount\" Ready with 14212 directives (21284 postings in 8879 transactions). beancount> _ More details are available in its own document .","title":"bean-query"},{"location":"running_beancount_and_generating_reports.html#bean-web","text":"bean-web serves all the reports on a web server that runs on your computer. You run it like this: bean-web /path/to/my/file.beancount It will serve all pages on port 8080 on your machine. Navigate to http://localhost:8080 with a web browser. You should be able to click your way through all the reports easily. The web interface provides a set of global pages and a set of report pages for each \u201cview.\u201d","title":"bean-web"},{"location":"running_beancount_and_generating_reports.html#global-pages","text":"The top-level table of contents page provides links to all the global pages at the top: The table of contents (the page you\u2019re looking at) A list of the errors that occurred in your ledger file A view of the source code of your Ledger file (this is used by various other links when referring to a location in your input file.) The table of contents provides a convenient list of links to all the common views, such as \u201cview by year\u201d, \u201cview by tag\u201d, and of course, \u201cview all transactions.\u201d There are a few more.","title":"Global Pages"},{"location":"running_beancount_and_generating_reports.html#view-reports-pages","text":"When you click on a view report page, you enter a set of pages for the subset of transactions for that view. Various reports about those transactions are available from here: Opening balances (a balance sheet at the beginning of the view) Balance sheet (a balance sheet at the end of the view) Income statement (for the period of the view) Various journals for each account (just click on an account name) Various reports of holdings at the end of the view Lists of documents and prices included in the view\u2019s entries Some statistics about the view data \u2026 and much more. There should be an index of all the available view reports.","title":"View Reports Pages"},{"location":"running_beancount_and_generating_reports.html#bean-bake","text":"bean-bake runs a bean-web instance and bakes all the pages to a directory: bean-bake /path/to/my/file.beancount myfinances It also support baking directly to an archive file: bean-bake /path/to/my/file.beancount myfinances.zip Various compression methods are supported, e.g. .tar.gz. This is useful to share the web interface with your accountant or other people, who usually don\u2019t have the ability to run Beancount. The page links have all been converted to relative links, and they should be able to extract the archive to a directory and browse all the reports the same way you do with bean-web .","title":"bean-bake"},{"location":"running_beancount_and_generating_reports.html#bean-doctor","text":"This is a debugging tool used to perform various diagnostics and run debugging commands, and to help provide information for reporting bugs. For example, it can do the following: List the Beancount dependencies that are installed on your machine, and the ones that are missing. It can tell you what you\u2019re missing that you should be installing. Print a dump of the parser\u2019s lexer tokens for your input file. If you report a parsing bug, it can be useful to look at the lexer output (if you know what you\u2019re doing). It can run your input file through a parsing round-trip, that is, print out the file and re-read it again and compare it. This is a useful parser and printer test. It can check that a directory hierarchy corresponds to a Beancount input file\u2019s chart-of-accounts, and reporting directories that do not comply. This is useful in case you decide to change some account names and are maintaining a corresponding archive of documents which needs to be adjusted accordingly.","title":"bean-doctor"},{"location":"running_beancount_and_generating_reports.html#context","text":"It can list the context upon which a transaction is applied, i.e., the inventory balances of each account the transaction postings modify, before and after it is applied. Use it like this: $ bean-doctor context /home/blais/accounting/blais.beancount 28514 /home/blais/accounting/blais.beancount:28513: ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; Assets:US:ETrade:Cash 8143.97 USD 2014-07-25 * \"(TRD) BOT +50 BND @82.10\" ^273755872 Assets:US:ETrade:BND 50.00 BND {82.10 USD} Assets:US:ETrade:Cash -4105.00 USD ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; ! Assets:US:ETrade:BND 50.00 BND {82.10 USD} ; ! Assets:US:ETrade:Cash 4038.97 USD There is a corresponding Emacs binding (C-c x) to invoke this around the cursor.","title":"Context"},{"location":"running_beancount_and_generating_reports.html#bean-format","text":"This pure text processing tool will reformat Beancount input to right-align all the numbers at the same, minimal column. It left-aligns all the currencies. It only modifies whitespace. This tool accepts a filename as arguments and outputs the aligned file on stdout (similar to UNIX cat ).","title":"bean-format"},{"location":"running_beancount_and_generating_reports.html#bean-example","text":"This program generates an example Beancount input file. See the Tutorial for more details about the contents of this example file.","title":"bean-example"},{"location":"running_beancount_and_generating_reports.html#filtering-transactions","text":"In order to produce different views of your financial transactions, we select a subset of full list of parsed transactions, for example, \u201call the transactions that occurred in year 2013\u201d, and then use that to produce the various available reports that Beancount provides. At the moment, only a preset list of filters are available as \u201cviews\u201d from the web interface. These views include: All transactions Transactions that occur in a particular year Transactions with a particular tag Transactions with a particular payee Transactions that involve at least one account with a particular name component At the moment, in order to access reports from these subsets of transactions, you need to use the bean-web web interface, and click on the related keyword in the root global page, which enters you into a set of reports for that view.","title":"Filtering Transactions"},{"location":"running_beancount_and_generating_reports.html#reports","text":"The whole point of entering your transactions in a single input file in the first place is that it allows you to sum, filter, aggregate and arrange various subsets of your data into well-known reports. There are three distinct ways to produce reports from Beancount: by using bean-web and browsing to a view and then to a specific report (this is the easy way), by using bean-report and providing the name of a desired report (and possibly some report-specific arguments), and by using bean-query and requesting data by specifying an SQL statement. Reports can sometimes be rendered in different file formats. Each report type will support being rendered in a list of common ones, such as console text, HTML and CSV. Some reports render in Beancount syntax itself, and we simply call this format name \u201cbeancount.\u201d There are many types of reports available, and there will be many more in the future, as many of the features on the roadmap involve new types of output. This section provides an overview of the most common ones. Use bean-report to inquire about the full list supported by your installed version: bean-report --help-reports PLEASE NOTE! At the moment, the sets of reports that are available from the web interface and from the console are different, though there is some overlap. In a subsequent version, the list of reports will be reconciled and all reports will be made available via both the web interface and the console, in a variety of data formats (text, CSV, HTML and maybe others.) For now, we will document these in the sections below.","title":"Reports"},{"location":"running_beancount_and_generating_reports.html#balance-reports","text":"All the balance reports are similar in that they produce tables of some set of accounts and their associated balances: [output] |-- Assets | `-- US | |-- BofA | | `-- Checking 596.05 USD | |-- ETrade | | |-- Cash 5,120.50 USD | | |-- GLD 70.00 GLD | | |-- ITOT 17.00 ITOT | | |-- VEA 36.00 VEA | | `-- VHT 294.00 VHT | |-- Federal | | `-- PreTax401k | |-- Hoogle | | `-- Vacation 337.26 VACHR | `-- Vanguard ... Balances for commodities held \u201cat cost\u201d are rendered at their book value. (Unrealized gains, if any, are inserted as separate transactions by an optional plugin and the result amounts get mixed in with the cost basis if rendered in the same account.) If an account\u2019s balance contains many different types of currencies (commodities not held \u201cat cost\u201d, such as dollars, euros, yen), each gets printed on its own line. This can render the balance column a bit too busy and messes with the vertical regularity of account names. This is to some extent an unavoidable compromise, but in practice, there are only a small number of commodities that form the large majority of a user\u2019s ledger: the home country\u2019s currency units. To this extent, amounts in common currencies can be broken out into their own column using the \u201c operating_currency \u201d option: option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" You may use this option multiple times if you have many of them (this is my case, for instance, because I am an expat and hold assets in both my host and home countries). Declaring operating currencies also hold the advantage that the name of the currency need not be rendered, and it can thus more easily be imported into a spreadsheet. Finally, some accounts are deemed \u201cactive\u201d if they have not been closed. A closed account with no transactions in the filtered set of transactions will not be rendered.","title":"Balance Reports"},{"location":"running_beancount_and_generating_reports.html#trial-balance-balances","text":"A trial balance report simply produces a table of final balances of all the active accounts, with all the accounts rendered vertically. The sum total of all balances is reported at the bottom. Unlike a balance sheet, it may not always balance to zero because of currency conversions. (This is the equivalent of Ledger\u2019s bal report.) The equivalent bean-query command is: SELECT account, sum(position) GROUP BY account ORDER BY account;","title":"Trial Balance (balances)"},{"location":"running_beancount_and_generating_reports.html#balance-sheet-balsheet","text":"A balance sheet is a snapshot of the balances of the Assets, Liabilities and Equity accounts at a particular point in time. In order to build such a report, we have to move balances from the other accounts to it: compute the balances of the Income and Expenses account at that point in time, insert transactions that will zero out balances from these accounts by transferring them to an equity account ( Equity:Earnings:Current ), render a tree of the balances of the Assets accounts on the left side, render a tree of the Liabilities accounts on the right side, render a tree of the Equity accounts below the Liabilities accounts. See the introduction document for an example. Note that the Equity accounts include the amounts reported from the Income and Expenses accounts, also often called \u201cNet Income.\u201d Also, in practice, we make two transfers because we\u2019re typically looking at a reporting period, and we want to differentiate between Net Income amounts transferred before the beginning of the period ( Equity:Earnings:Previous ) and during the period itself ( Equity:Earnings:Current ). And similar pair of transfers is carried out in order to handle currency conversions (this is a bit of a hairy topic, but one with a great solution; refer to the dedicated document if you want all the details). The equivalent bean-query command is: SELECT account, sum(position) FROM CLOSE ON 2016-01-01 GROUP BY account ORDER BY account;","title":"Balance Sheet (balsheet)"},{"location":"running_beancount_and_generating_reports.html#opening-balances-openbal","text":"The opening balances report is simply a balance sheet drawn at the beginning of the reporting period. This report only makes sense for a list of filtered entries that represents a period of time, such as \u201cyear 2014.\u201d The balance sheet is generated using only the summarization entries that were synthesized when the transactions were filtered (see the double-entry method document ).","title":"Opening Balances (openbal)"},{"location":"running_beancount_and_generating_reports.html#income-statement-income","text":"An income statement lists the final balances of the Income and Expenses accounts. It represents a summary of the transient activity within these accounts. If the balance sheet is the snapshot at a particular point in time, this is the difference between the beginning and the end of a period (in our case: of a filtered set of transactions). The balances of the active Income accounts are rendered on the left, and those of the active Expenses accounts on the right. See the introduction document for an example. The difference between the total of Income and Expenses balances is the Net Income. Note that the initial balances of the Income and Expenses accounts should have been zero\u2019ed out by summarization transactions that occur at the beginning of the period, because we\u2019re only interested in the changes in these accounts.","title":"Income Statement (income)"},{"location":"running_beancount_and_generating_reports.html#journal-reports","text":"The reports in this section render lists of transactions and other directives in a linear fashion.","title":"Journal Reports"},{"location":"running_beancount_and_generating_reports.html#journal-journal","text":"This report is the equivalent of an \u201caccount statement\u201d from an institution, a list of transactions with at least one posting in that account. This is the equivalent of Ledger\u2019s register report ( reg ). You generate a journal report like this: bean-report myfile.beancount journal \u2026 By default, this renders a journal of all the transactions, which is unlikely to be what you want. Select a particular account to render like this: bean-report myfile.beancount journal -a Expenses:Restaurant At the moment, the \u201c -a \u201d option accepts only a complete account name, or the name of one of the parent accounts. Eventually we will extend it to handle expressions.","title":"Journal (journal)"},{"location":"running_beancount_and_generating_reports.html#rendering-at-cost","text":"The numbers column on the right displays the changes from the postings of the selected account. Notice that only the balances for the postings affecting the given account are rendered. The change column renders the changes in the units affected. For example, if this posting is selected: Assets:Investments:Apple 2 AAPL {402.00 USD} The value reported for the change will be \u201c 2 AAPL \u201d. If you would like to render the values at cost, use the \u201c --at-cost \u201d or \u201c -c \u201d option, which will in this case render \u201c 804.00 USD \u201d instead. There is no \u201cmarket value\u201d option. Unrealized gains are automatically inserted at the end of the history by the \u201c beancount.plugins.unrealized \u201d plugin. See options for that plugin to insert its unrealized gains. Note that if the sum of the selected postings is zero, no amount is rendered in the change column.","title":"Rendering at Cost"},{"location":"running_beancount_and_generating_reports.html#adding-a-balance-column","text":"If you want to add a column that sums up the running balance for the reported changes, use the \u201c --render-balance \u201d or \u201c -b \u201d option. This does not always make sense to report, so it is up to you to decide whether you want a running balance.","title":"Adding a Balance Column"},{"location":"running_beancount_and_generating_reports.html#character-width","text":"By default, the report will be as wide as your terminal allows. Restrict the width to a set number of characters with the \u201c-w\u201d option.","title":"Character Width"},{"location":"running_beancount_and_generating_reports.html#precision","text":"The number of fractional digits for the number rendering can be specified via \u201c --precision \u201d or \u201c -k \u201d.","title":"Precision"},{"location":"running_beancount_and_generating_reports.html#compact-normal-or-verbose","text":"In its normal operation, Beancount renders an empty line between transactions. This helps delineate transactions where there are multiple currencies affected, as they render on separate lines. If you want a more compact rendering, use the \u201c --compact \u201d or \u201c -x \u201d option. On the other hand, if you want to render the affected postings under the transaction line, use the \u201c --verbose \u201d or \u201c -X \u201d option.","title":"Compact, Normal or Verbose"},{"location":"running_beancount_and_generating_reports.html#summary","text":"Here is a summary of its arguments: optional arguments: -h, --help show this help message and exit -a ACCOUNT, --account ACCOUNT Account to render -w WIDTH, --width WIDTH The number of characters wide to render the report to -k PRECISION, --precision PRECISION The number of digits to render after the period -b, --render-balance, --balance If true, render a running balance -c, --at-cost, --cost If true, render values at cost -x, --compact Rendering compactly -X, --verbose Rendering verbosely","title":"Summary"},{"location":"running_beancount_and_generating_reports.html#equivalent-sql-query","text":"The equivalent bean-query command is: SELECT date, flag, description, account, cost(position), cost(balance);","title":"Equivalent SQL Query"},{"location":"running_beancount_and_generating_reports.html#conversions-conversions","text":"This report lists the total of currency conversions that result from the selected transactions. (Most people won\u2019t need this.)","title":"Conversions (conversions)"},{"location":"running_beancount_and_generating_reports.html#documents-documents","text":"This report produces an HTML list of all the external documents found in the ledger, either from explicit directives or from plugins that automatically find the documents and add them to the stream of transactions.","title":"Documents (documents)"},{"location":"running_beancount_and_generating_reports.html#holdings-reports","text":"These reports produces aggregations for assets held at cost.","title":"Holdings Reports"},{"location":"running_beancount_and_generating_reports.html#holdings-aggregations-holdings","text":"This report produces a detailed list of all holdings found in the ledger. You can produce aggregates by commodity and accounts using the \u201c-g\u201d option.","title":"Holdings & Aggregations (holdings*)"},{"location":"running_beancount_and_generating_reports.html#net-worth-networth","text":"This report produces a short summary of the net worth (equity) of the ledger, in each of the operating currencies.","title":"Net Worth (networth)"},{"location":"running_beancount_and_generating_reports.html#other-report-types","text":"","title":"Other Report Types"},{"location":"running_beancount_and_generating_reports.html#cash","text":"This report renders balances in commodities not held at cost, in other words, cash: bean-report example.beancount cash -c USD Account Units Currency Cost Currency Average Cost Price Book Value Market Value -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ Assets:US:BofA:Checking 596.05 USD USD 596.05 596.05 Assets:US:ETrade:Cash 5,120.50 USD USD 5,120.50 5,120.50 Assets:US:Hoogle:Vacation 337.26 VACHR Assets:US:Vanguard:Cash -0.02 USD USD -0.02 -0.02 Liabilities:US:Chase:Slate -2,891.85 USD USD -2,891.85 -2,891.85 -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ The report allows you to convert all currencies to a common currency (in the example above, \"convert everything to USD\"). There's also an option to report only on the operating currencies. I use this to get an overview of all uninvested cash.","title":"Cash"},{"location":"running_beancount_and_generating_reports.html#prices-prices","text":"This report renders a list of price points for a base currency in terms of a quote currency. The list is sorted by date. You can output this table in beancount format as well. This is convenient to save a price database to a file, that can then be combined and loaded into another input file.","title":"Prices (prices)"},{"location":"running_beancount_and_generating_reports.html#statistics-stats","text":"This report simply provides various statistics on the parsed entries.","title":"Statistics (stats)"},{"location":"running_beancount_and_generating_reports.html#update-activity-activity","text":"This table renders for each account the date of the last entry.","title":"Update Activity (activity)"},{"location":"settlement_dates_in_beancount.html","text":"Settlement Dates & Transfer Accounts in Beancount \uf0c1 Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-dates Motivation Proposal Description Remaining Questions Previous Work Ledger Effective and Auxiliary Dates References Motivation \uf0c1 When a trade executes in an investment account, there is most often a delay between the date that the transaction is carried out (the \u201ctransaction date\u201d) and the date that the funds are deposited in an associated cash account (the \u201csettlement date\u201d). This makes imported balance assertions sometimes requiring the fudging of their dates, and sometimes they can even be impossible. This document proposes the addition of an optional \u201csettlement date\u201d to be attached to a transaction or a posting, and associated semantics for how to deal with the problem. Proposal Description \uf0c1 Settlement Dates \uf0c1 In the first implementation of Beancount I used to have two dates attached to a transaction, but I never did anything with them. The alternate date would get attached but was ignored thereafter. The meaning of it is that it should have split the transaction into two, with some sort of transfer account, that might have been useful semantics, I never developed it. Something like this as input: 2014-06-23=2014-06-28 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} S Assets:ScottTrade:Cash where the \u201cS\u201d posting flag marks the leg \u201cto be postponed to the settlement date.\u201d Alternatively, you could attach the date to a posting: 2014-06-23 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} 2014-06-28 Assets:ScottTrade:Cash Both of the above syntax proposals allow you to specify which postings are meant to be postponed to settlement. The second one is more flexible, as each posting could potentially have a different date, but the more constrained syntax of the first would create less complications. Either of these could get translated to multiple transactions with a transfer account to absorb the pending amount: 2014-06-23 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} Assets:ScottTrade:Transfer 2014-06-28 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:Transfer Assets:ScottTrade:Cash 5890.00 USD So far, I\u2019ve been getting away with fudging the dates on balance assertions where necessary. I have relatively few sales, so this hasn\u2019t been a big problem so far. I\u2019m not convinced it needs a solution yet, maybe the best thing to do is just to document how to deal with the issue when it occurs. Maybe someone can convince me otherwise. Transfer Accounts \uf0c1 In the previous section, we discuss a style whereby a single entry moving money between two accounts contains two dates and results in two separate entries. An auxiliary problem, which is related in its solution, is how to carry out the reverse operation, that is, how to merge two separate entries posting to a common transfer account (sometimes called a \u201c suspense account \u201d). For example, a user may want to input the two sides of a transaction separately, e.g. by running import scripts on separate input files, and instead of having to reconcile and merge those by hand, we would want to explicitly support this by identifying matching transactions to these transfer accounts and creating a common link between them. Most importantly, we want to be able to easily identify which of the transactions is not matched on the other side, which indicates missing data. There is a prototype of this under beancount.plugins.tag_pending . Also see redstreet0\u2019s \u201c zerosum \u201d plugin from this thread . Remaining Questions \uf0c1 How do we determine a proper transfer account name? Is a subaccount a reasonable approach? What if a user would like to have a single global limbo account? TODO Does this property solve the problem of making balance assertions between trade and settlement? TODO [Write out a detailed example] Any drawbacks? TODO How does this affect the balance sheet and income statement, if any? Is it going to be obvious to users what the amounts in these limbo/transfer accounts are? TODO Unrooting Transactions \uf0c1 A wilder idea would be to add an extra level in the transaction-posting hierarchy, adding the capability to group multiple partial transactions, and move the balancing rule to that level. Basically, two transactions input separately and then grouped - by some rule, or trivially by themselves - could form a new unit of balance rule. That would be a much more demanding change on the schema and on the Beancount design but would allow to natively support partial transactions, keeping their individual dates, descriptions, etc. Maybe that's a better model? Consider the advantages. Previous Work \uf0c1 Ledger Effective and Auxiliary Dates \uf0c1 Ledger has the concept of \u201c auxiliary dates \u201d. The way these work is straightforward: any transaction may have a second date, and the user can select at runtime (with --aux-date ) whether the main date or the auxiliary dates are meant to be used. It is unclear to me how this is meant to be used in practice, in the presence of balance assertions. Without balance assertions, I can see how it would just work: you\u2019d render everything with settlement dates only. This would probably only make sense for specific reports. I would much rather keep a single semantic for the set of transactions that gets parsed in; the idea that the meaning of the transactions varies depending on the invocation conditions would set a precedent in Beancount, I\u2019d prefer not to break this nice property, so by default I\u2019d prefer to avoid implementing this solution. Auxiliary dates are also known as \u201c effective dates \u201d and can be associated with each individual posting. Auxiliary dates are secondary to the the \u201cprimary date\u201d or the \u201cactual date\u201d, being the posting date of the record): 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] Expenses:Food:Groceries $ 37.50 ; [=2008/12/01] Expenses:Food:Groceries $ 37.50 ; [=2009/01/01] Expenses:Food:Groceries $ 37.50 ; [=2009/02/01] Expenses:Food:Groceries $ 37.50 ; [=2009/03/01] Assets:Checking The original motivation for this was for budgeting, allow one to move accounting of expenses to neighboring budget periods in order to carry over actual paid amounts to those periods. Bank amounts in one month could be set against a budget from the immediately preceding or following month, as needed. (Note: John Wiegley) This is similar to one of the syntaxes I\u2019m suggesting above\u2014letting the user specify a date for each posting\u2014but the other postings are not split as independent transactions. The usage of those dates is similarly triggered by a command-line option ( --effective ). I\u2019m assuming that the posting on the checking account above occurs at once at 2008/10/16, regardless of reporting date. Let\u2019s verify this: $ ledger -f settlement1.lgr reg checking --effective 08-Oct-16 Bountiful Blessings.. Assets:Checking $ -225.00 $ -225.00 That\u2019s what I thought. This works, but a problem with this approach is that any balance sheet drawn between 2008/10/01 (the earliest effective date) and 2009/03/01 (the latest effective date) would not balance. Between those dates, some amounts are \u201cin limbo\u201d and drawing up a balance sheet at one of those dates would not balance. This would break an invariant in Beancount: we require that you should always be able to draw a balance sheet at any point in time, and any subset of transactions should balance. I would rather implement this by splitting this example transaction into many other ones, as in the proposal above, moving those temporary amounts living in limbo in an explicit \u201climbo\u201d or \u201ctransfer\u201d account, where each transaction balances. Moreover, this step can be implemented as a transformation stage, replacing the transaction with effective dates by one transaction for each posting where the effective date differs from the transaction\u2019s date (this could be enabled on demand via a plugin). GnuCash \uf0c1 TODO(blais) - How is this handled in GnuCash and other GUI systems? Is there a standard account method? References \uf0c1 The IRS requires you to use the trade date and NOT the settlement date for tax reporting; from the IRS Publication 17: Securities traded on established market. For securities traded on an established securities market, your holding period begins the day after the trade date you bought the securities, and ends on the trade date you sold them. Do not confuse the trade date with the settlement date, which is the date by which the stock must be delivered and payment must be made. Example. You are a cash method, calendar year taxpayer. You sold stock at a gain on December 30, 2013. According to the rules of the stock exchange, the sale was closed by delivery of the stock 4 trading days after the sale, on January 6, 2014. You received payment of the sales price on that same day. Report your gain on your 2013 return, even though you received the payment in 2014. The gain is long term or short term depending on whether you held the stock more than 1 year. Your holding period ended on December 30. If you had sold the stock at a loss, you would also report it on your 2013 return. Threads \uf0c1 An interesting \"feature by coincidence\" First Opinions, Coming from Ledger","title":"Settlement Dates in Beancount"},{"location":"settlement_dates_in_beancount.html#settlement-dates-transfer-accounts-in-beancount","text":"Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-dates Motivation Proposal Description Remaining Questions Previous Work Ledger Effective and Auxiliary Dates References","title":"Settlement Dates & Transfer Accounts in Beancount"},{"location":"settlement_dates_in_beancount.html#motivation","text":"When a trade executes in an investment account, there is most often a delay between the date that the transaction is carried out (the \u201ctransaction date\u201d) and the date that the funds are deposited in an associated cash account (the \u201csettlement date\u201d). This makes imported balance assertions sometimes requiring the fudging of their dates, and sometimes they can even be impossible. This document proposes the addition of an optional \u201csettlement date\u201d to be attached to a transaction or a posting, and associated semantics for how to deal with the problem.","title":"Motivation"},{"location":"settlement_dates_in_beancount.html#proposal-description","text":"","title":"Proposal Description"},{"location":"settlement_dates_in_beancount.html#settlement-dates","text":"In the first implementation of Beancount I used to have two dates attached to a transaction, but I never did anything with them. The alternate date would get attached but was ignored thereafter. The meaning of it is that it should have split the transaction into two, with some sort of transfer account, that might have been useful semantics, I never developed it. Something like this as input: 2014-06-23=2014-06-28 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} S Assets:ScottTrade:Cash where the \u201cS\u201d posting flag marks the leg \u201cto be postponed to the settlement date.\u201d Alternatively, you could attach the date to a posting: 2014-06-23 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} 2014-06-28 Assets:ScottTrade:Cash Both of the above syntax proposals allow you to specify which postings are meant to be postponed to settlement. The second one is more flexible, as each posting could potentially have a different date, but the more constrained syntax of the first would create less complications. Either of these could get translated to multiple transactions with a transfer account to absorb the pending amount: 2014-06-23 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} Assets:ScottTrade:Transfer 2014-06-28 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:Transfer Assets:ScottTrade:Cash 5890.00 USD So far, I\u2019ve been getting away with fudging the dates on balance assertions where necessary. I have relatively few sales, so this hasn\u2019t been a big problem so far. I\u2019m not convinced it needs a solution yet, maybe the best thing to do is just to document how to deal with the issue when it occurs. Maybe someone can convince me otherwise.","title":"Settlement Dates"},{"location":"settlement_dates_in_beancount.html#transfer-accounts","text":"In the previous section, we discuss a style whereby a single entry moving money between two accounts contains two dates and results in two separate entries. An auxiliary problem, which is related in its solution, is how to carry out the reverse operation, that is, how to merge two separate entries posting to a common transfer account (sometimes called a \u201c suspense account \u201d). For example, a user may want to input the two sides of a transaction separately, e.g. by running import scripts on separate input files, and instead of having to reconcile and merge those by hand, we would want to explicitly support this by identifying matching transactions to these transfer accounts and creating a common link between them. Most importantly, we want to be able to easily identify which of the transactions is not matched on the other side, which indicates missing data. There is a prototype of this under beancount.plugins.tag_pending . Also see redstreet0\u2019s \u201c zerosum \u201d plugin from this thread .","title":"Transfer Accounts"},{"location":"settlement_dates_in_beancount.html#remaining-questions","text":"How do we determine a proper transfer account name? Is a subaccount a reasonable approach? What if a user would like to have a single global limbo account? TODO Does this property solve the problem of making balance assertions between trade and settlement? TODO [Write out a detailed example] Any drawbacks? TODO How does this affect the balance sheet and income statement, if any? Is it going to be obvious to users what the amounts in these limbo/transfer accounts are? TODO","title":"Remaining Questions"},{"location":"settlement_dates_in_beancount.html#unrooting-transactions","text":"A wilder idea would be to add an extra level in the transaction-posting hierarchy, adding the capability to group multiple partial transactions, and move the balancing rule to that level. Basically, two transactions input separately and then grouped - by some rule, or trivially by themselves - could form a new unit of balance rule. That would be a much more demanding change on the schema and on the Beancount design but would allow to natively support partial transactions, keeping their individual dates, descriptions, etc. Maybe that's a better model? Consider the advantages.","title":"Unrooting Transactions"},{"location":"settlement_dates_in_beancount.html#previous-work","text":"","title":"Previous Work"},{"location":"settlement_dates_in_beancount.html#ledger-effective-and-auxiliary-dates","text":"Ledger has the concept of \u201c auxiliary dates \u201d. The way these work is straightforward: any transaction may have a second date, and the user can select at runtime (with --aux-date ) whether the main date or the auxiliary dates are meant to be used. It is unclear to me how this is meant to be used in practice, in the presence of balance assertions. Without balance assertions, I can see how it would just work: you\u2019d render everything with settlement dates only. This would probably only make sense for specific reports. I would much rather keep a single semantic for the set of transactions that gets parsed in; the idea that the meaning of the transactions varies depending on the invocation conditions would set a precedent in Beancount, I\u2019d prefer not to break this nice property, so by default I\u2019d prefer to avoid implementing this solution. Auxiliary dates are also known as \u201c effective dates \u201d and can be associated with each individual posting. Auxiliary dates are secondary to the the \u201cprimary date\u201d or the \u201cactual date\u201d, being the posting date of the record): 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] Expenses:Food:Groceries $ 37.50 ; [=2008/12/01] Expenses:Food:Groceries $ 37.50 ; [=2009/01/01] Expenses:Food:Groceries $ 37.50 ; [=2009/02/01] Expenses:Food:Groceries $ 37.50 ; [=2009/03/01] Assets:Checking The original motivation for this was for budgeting, allow one to move accounting of expenses to neighboring budget periods in order to carry over actual paid amounts to those periods. Bank amounts in one month could be set against a budget from the immediately preceding or following month, as needed. (Note: John Wiegley) This is similar to one of the syntaxes I\u2019m suggesting above\u2014letting the user specify a date for each posting\u2014but the other postings are not split as independent transactions. The usage of those dates is similarly triggered by a command-line option ( --effective ). I\u2019m assuming that the posting on the checking account above occurs at once at 2008/10/16, regardless of reporting date. Let\u2019s verify this: $ ledger -f settlement1.lgr reg checking --effective 08-Oct-16 Bountiful Blessings.. Assets:Checking $ -225.00 $ -225.00 That\u2019s what I thought. This works, but a problem with this approach is that any balance sheet drawn between 2008/10/01 (the earliest effective date) and 2009/03/01 (the latest effective date) would not balance. Between those dates, some amounts are \u201cin limbo\u201d and drawing up a balance sheet at one of those dates would not balance. This would break an invariant in Beancount: we require that you should always be able to draw a balance sheet at any point in time, and any subset of transactions should balance. I would rather implement this by splitting this example transaction into many other ones, as in the proposal above, moving those temporary amounts living in limbo in an explicit \u201climbo\u201d or \u201ctransfer\u201d account, where each transaction balances. Moreover, this step can be implemented as a transformation stage, replacing the transaction with effective dates by one transaction for each posting where the effective date differs from the transaction\u2019s date (this could be enabled on demand via a plugin).","title":"Ledger Effective and Auxiliary Dates"},{"location":"settlement_dates_in_beancount.html#gnucash","text":"TODO(blais) - How is this handled in GnuCash and other GUI systems? Is there a standard account method?","title":"GnuCash"},{"location":"settlement_dates_in_beancount.html#references","text":"The IRS requires you to use the trade date and NOT the settlement date for tax reporting; from the IRS Publication 17: Securities traded on established market. For securities traded on an established securities market, your holding period begins the day after the trade date you bought the securities, and ends on the trade date you sold them. Do not confuse the trade date with the settlement date, which is the date by which the stock must be delivered and payment must be made. Example. You are a cash method, calendar year taxpayer. You sold stock at a gain on December 30, 2013. According to the rules of the stock exchange, the sale was closed by delivery of the stock 4 trading days after the sale, on January 6, 2014. You received payment of the sales price on that same day. Report your gain on your 2013 return, even though you received the payment in 2014. The gain is long term or short term depending on whether you held the stock more than 1 year. Your holding period ended on December 30. If you had sold the stock at a loss, you would also report it on your 2013 return.","title":"References"},{"location":"settlement_dates_in_beancount.html#threads","text":"An interesting \"feature by coincidence\" First Opinions, Coming from Ledger","title":"Threads"},{"location":"sharing_expenses_with_beancount.html","text":"Sharing Expenses in Beancount \uf0c1 Martin Blais , May 2015 http://furius.ca/beancount/doc/shared Introduction \uf0c1 This document presents a method for precisely and easily accounting for shared expenses in complicated group situations. For example, traveling with a group of friends where people pay for different things. We show it is possible to deal with expenses to be split evenly among the group as well as group expenses that should be allocated to specific persons. The method uses the double-entry accounting system. The essence of the method consists in separating the accounting of expenses and their associated payments. This makes it much easier to deal with shared costs, because using this method it doesn\u2019t matter who pays for what: we reconcile the precise amounts owed for each individual at the end and automatically compute final correcting transfers for each. This article is structured around a simple trip with expenses shared equally by two people. However, the same method generalizes to any type of project with incomes and expenses to be shared among multiple participants, and splitting expenses evenly or not. A Travel Example \uf0c1 Martin (the author) and Caroline (his girlfriend) visited Mexico in March 2015. We visited the island of Cozumel for a SCUBA diving trip for three days and then headed to Tulum for two days to relax and dive in the cenotes . Our assumptions are: Chaotic payments . We lead very busy lives\u2026 both of us are going to make payments ahead of time and during the trip, without any forethought regarding who will pay for what, though we will carefully record every payment. Each of us just pays for whatever preparation costs as needed to arrange this trip. For example, Caroline selected and booked flights early while I paid for the resort and booked the rental and activities at the local dive shop one week before departure. Use of shared and individual assets. Both of us are bringing cash, and are going to make payments during the trip from our respective wallets as well as from a shared pool of cash converted to local currency (Mexican pesos, for which I will use the \u201cMXN\u201d symbol), and we will use credit cards as necessary before and during the trip. Multiple currencies. Some of our expenses will be denominated in US dollars and some in Mexican pesos. For example, flights were paid in US dollars, local meals and the accommodation were paid in pesos, but the local dive shop charged us dollars. Converted amounts will come from both cash and credit cards sources. Individual expenses in a shared pool. While most of the expenses are to be shared equally, some of the expenses will apply to only one of us, and we want to account for those explicitly. For example, Caroline took a SCUBA certification course ( PADI Open Water ) and will pay for that on her own; similarly, she should not be paying for Martin\u2019s expensive boat diving costs. To complicate matters, the dive shop issued us a single joint bill for everything at the end of our stay. A Note About Sharing \uf0c1 I feel that something should be said about the \u201csharing\u201d aspect of our expenses, as this topic has come up on previous discussions on the mailing-lists involving sharing examples. We are nitpicking on purpose. For the purpose of this exercise, we are accounting for every little penny spent in an incredibly detailed manner. The point of this document is specifically to show how a complex set of transactions well accounted for can be efficiently and simply disentangled to a precise accounting of expenses for each participant, regardless of who actually makes the payments. We are not cheapskates. We will assume that we\u2019ve decided to split expenses evenly. Our \u201cgenerosity\u201d to each other is not a topic relevant to this document. We\u2019re both well compensated working professionals and you can assume that we\u2019ve agreed to split the common costs for this trip evenly (50/50). One of the attributes of the method we show here is that the decision of how we choose to split expenses can be made separately from the actual payment of costs. For example, we may decide in the end that I pay for \u2154rd of the trip, but that can be decided precisely rather than in an ad-hoc \u201coh, I think I remember I paid for this\u201d manner. This is especially useful in a larger group of people because when expenses aren\u2019t tracked accurately, usually everyone is left with a feeling that they paid more than the others. The sum of the perceived fractions of expenses paid in a group is always greater than 100%... Overview of the Method \uf0c1 In this section we present a brief illustrated overview of the method. A set of common Assets accounts that belong to the project, and book all our individual expenses and transfer for the trip as coming from external Income accounts: During the trip, we use the common Assets to make expenses. Most of the expenses are attributed to both of us (and to be shared eventually), but some of the expenses are intended to be attributed to each of us individually: After the trip, remaining Assets (like cash we walked home with) gets distributed back to ourselves to zero out the balances of the Assets accounts and we record this through contra postings to Income accounts: Finally, the list of shared Expenses are split between each other\u2014using a plugin that forks every posting that is intended to be a shared expense\u2014and the final amount is used to make a final transfer between each other so that we\u2019ve each paid for our respective expenses and we\u2019re square: Note that the final balance of expenses for each participant may differ, and these are due to particular expenses that were attributed separately, or if we decide to split the total unevenly. How to Track Expenses \uf0c1 In order to write down all expenses for this trip we will open a brand new Beancount input file. Despite the fact that the expenses will come from each person\u2019s personal accounts, it is useful to think of the trip as a special project, as if it were a separate entity, e.g., a company that exists only for the duration of the trip. The example file we wrote for our trip can be found here and should help you follow along this text. Accounts \uf0c1 The set of accounts in the input file should not have to match your personal Beancount file\u2019s account names. The accounts we will use include accounts that correspond to Martin and Caroline\u2019s personal accounts but with generic names (e.g., Income:Martin:CreditCard instead of Liabilities:US:Chase ), and expense accounts may not match any regular expenses in my personal Beancount file\u2014it\u2019s not important. As a convention, any account that pertains specifically to one of the travelers will include that person\u2019s name in the account name. For example, Caroline\u2019s credit card will be named \u201c Income:Caroline:CreditCard \u201d. This is important, as we will use this later on to split contributions and expenses. Let\u2019s examine the different types of accounts we will need to carry this out. External Income Accounts \uf0c1 The \u201cproject\u201d will receive income in the form of transfers from personal accounts of either traveler. These are accounts we will consider external to the project and so will define them as Income accounts: ;; External accounts for Martin. 2015-02-01 open Income:Martin:Cash 2015-02-01 open Income:Martin:Cash:Foreign 2015-02-01 open Income:Martin:Wallet 2015-02-01 open Income:Martin:CreditCard ;; External accounts for Caroline. 2015-02-01 open Income:Caroline:Cash 2015-02-01 open Income:Caroline:Wallet 2015-02-01 open Income:Caroline:MetroCard 2015-02-01 open Income:Caroline:CreditCard Transactions carried out from these accounts must be copied from your personal Beancount file. Obviously, you must be careful to include all the transactions pertaining to the trip. I used a tag to do this in my personal file. Assets & Liabilities Accounts \uf0c1 There will be a few Asset accounts that will be active and exist for the duration of the trip. These temporary accounts will be zero\u2019ed out at the end of it. One example is a pool of petty cash in local currency: 2015-02-01 open Assets:Cash:Pesos description: \"A shared account to contain our pocket of pesos\" We also carried cash in each of our pockets while traveling, so I created two separate accounts for that: 2015-02-01 open Assets:Cash:Martin description: \"Cash for the trip held by Martin\" 2015-02-01 open Assets:Cash:Caroline description: \"Cash for the trip held by Caroline\" Note however that despite their individual names, those accounts are considered as part of the project. It was just convenient to separately track balances for the cash we each held during the trip. Expenses Accounts \uf0c1 We will define various accounts to book our Expenses to. For example, \u201c Expenses:Flights \u201d will contain our costs associated with flight travel. For convenience, and because there are many types of expenses in this file, we chose to leverage the \u201cauto-accounts\u201d plugin and let Beancount automatically open these accounts: plugin \"beancount.ops.auto_accounts\" The great majority of these accounts are for shared expenses to be split between us. For example, shared SCUBA diving expenses will be booked to \u201c Expenses:Scuba \u201d. However, for expenses that are intended to be covered by one of us only, we simply include the name of the traveler in the account name. For example, Martin\u2019s extra costs for boat diving will be booked to the \u201c Expenses:Scuba:Martin \u201d account. Example Transactions \uf0c1 Let\u2019s turn our attention to the different types of transactions present in the file. In this section I will walk you through some representative transactions (the names I give to these are arbitrary). Contributing Transactions \uf0c1 Contributions to the project\u2019s costs are usually done in the form of expenses paid from external accounts. For example, Caroline paying for flights with her credit card looks like this: 2015-02-01 * \"Flights to Cancun\" Income:Caroline:CreditCard -976.00 USD Expenses:Flights Martin paying for the taxi to the airport looks like this: 2015-02-25 * \"Taxi to airport\" ^433f66ea0e4e Expenses:Transport:Taxi 62.80 USD Income:Martin:CreditCard Bringing Cash \uf0c1 We both brought some cash on us, as we were warned it might be difficult to use credit cards in Mexico. In my personal Beancount file, the cash account is \u201c Assets:Cash \u201d but here it must get booked as an external contribution with my name on it: ;; Initial cash on us. 2015-02-24 * \"Getting cash for travel\" Income:Martin:Cash -1200 USD Assets:Cash:Martin 1200 USD Caroline\u2019s cash is similar: 2015-02-24 * \"Getting cash for travel\" Income:Caroline:Cash -300 USD Assets:Cash:Caroline 300 USD Once again, note that the Assets:Cash:Martin and Assets:Cash:Caroline accounts are considered a part of the project, in this case it just refers to who is carrying it. (These accounts are cleared at the end, so it does not matter that our names are in them.) Transfers \uf0c1 Before the trip, I was really busy. It looked like Caroline was going to make most of arrangements and upfront payments, so I made a transfer to her Google Wallet to help her cover for some expenses ahead of time: 2015-02-01 * \"Transfer Martin -> Caroline on Google Wallet\" Income:Martin:Wallet -1000 USD Income:Caroline:Wallet 1000 USD Cash Conversions \uf0c1 Obtaining local currency was done by changing a small amount of cash at the airport (at a very bad rate): 2015-02-25 * \"Exchanged cash at XIC at CUN airport\" Assets:Cash:Caroline -100.00 USD @ 12.00 MXN Assets:Cash:Pesos 1200 MXN The \u201c Assets:Cash:Pesos \u201d account tracks our common pool of local currency that we use for various small expenses. Cash Expenses in US Dollars \uf0c1 Some local expenses will call for US money, which in this example I paid from my cash pocket: 2015-03-01 * \"Motmot Diving\" | \"Deposit for cenote diving\" Expenses:Scuba 50.00 USD Assets:Cash:Martin Cash Expenses in Local Currency \uf0c1 Paying cash using pesos from our shared stash of pesos looks like this: 2015-02-25 * \"UltraMar Ferry across to Cozumel\" Expenses:Transport:Bus 326 MXN Assets:Cash:Pesos Sometimes we even had to pay with a mix of US dollars and pesos. In this example, we ran out of pesos, so we have to give them dollars and pesos (all the restaurants and hotels in the beach part of Tulum accept US currency): 2015-03-01 * \"Hartwood\" | \"Dinner - ran out of pesos\" Expenses:Restaurant 1880 MXN Assets:Cash:Pesos -1400 MXN Assets:Cash:Martin -40.00 USD @ 12.00 MXN I used the unfavorable rate the restaurant was offering to accept dollars at (the market rate was 14.5 at the time). Individual Expenses \uf0c1 Here is an example of booking individual expenses using shared money. In order for us to have access to the reef for diving, we had to pay a \u201cmarine park\u201d fee of $2.50 per day to the island. This was a short trip where I dove only three days there and Caroline\u2019s fee was included in her course except for one day: 2015-02-25 * \"Marine Park (3 days Martin, 1 day Caroline)\" Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Scuba:ParkFees:Caroline 2.50 USD Assets:Cash:Martin All that needs to be done is to book these to expense accounts with our names in them, which will get separated out at the end. Here is a more complicated example: the dive shop at Scuba Club Cozumel charged us a single bill at the end of our stay for all our rental gear and extra dives. All I did here was translate the itemized bill into a single transaction, booking to each other: 2015-03-01 * \"Scuba Club Cozumel\" | \"Dive shop bill\" ^69b409189b37 Income:Martin:CreditCard -381.64 USD Expenses:Scuba:Martin 27 USD ;; Regulator w/ Gauge Expenses:Scuba:Caroline 9 USD ;; Regulator w/ Gauge Expenses:Scuba:Martin 27 USD ;; BCD Expenses:Scuba:Caroline 9 USD ;; BCD Expenses:Scuba:Martin 6 USD ;; Fins Expenses:Scuba:Martin 24 USD ;; Wetsuit Expenses:Scuba:Caroline 8 USD ;; Wetsuit Expenses:Scuba:Caroline 9 USD ;; Dive computer Expenses:Scuba:Martin 5 USD ;; U/W Light Expenses:Scuba:Caroline 70 USD ;; Dive trip (2 tank) Expenses:Scuba:Martin 45 USD ;; Wreck Dive w/ Lite Expenses:Scuba:Martin 45 USD ;; Afternoon dive Expenses:Scuba:Caroline 45 USD ;; Afternoon dive Expenses:Scuba:Martin 28.64 USD ;; Taxes Expenses:Scuba:Caroline 24.00 USD ;; Taxes Final Balances \uf0c1 Of course you can use balance assertions at any time during the trip. For example, just before leaving at the Cancun airport we knew we wouldn\u2019t spend any more Mexican pesos for a while, so I counted what we had left after Caroline decided to splurge the remainder of them into overpriced chocolate sold at the airport shop: 2015-03-04 balance Assets:Cash:Pesos 65 MXN Ideally the bookkeeper should want to do this at a quiet moment at the end of every day or couple of days, which makes it easier to triangulate an expense you might have forgotten to enter (we are on vacation after all, in our relaxed state of mind we forget to write down stuff here and there). Clearing Asset Accounts \uf0c1 At the end of the trip, you should clear the final balances of all Assets and Liabilities accounts by transferring the remaining funds out to the participants, i.e., to the Income accounts. This should leave all the balances of the trip at zero and ensures all the cash put forward for trip expenses has been moved out to travelers. By the way, it doesn\u2019t matter much who keeps this money, because at the end we will end up making a single correcting transfer that should account for the balance required to create an even split. You can transfer it to anyone; the end result will be the same. Our clearing transaction looked like this: 2015-03-06 * \"Final transfer to clear internal balances to external ones\" Assets:Cash:Pesos -65 MXN Income:Martin:Cash:Foreign 60 MXN Income:Caroline:Cash 5 MXN Assets:Cash:Martin -330 USD Income:Martin:Cash 330 USD Assets:Cash:Caroline -140 USD Income:Caroline:Cash 140 USD 2015-03-07 balance Assets:Cash:Pesos 0 MXN 2015-03-07 balance Assets:Cash:Pesos 0 USD 2015-03-07 balance Assets:Cash:Martin 0 USD 2015-03-07 balance Assets:Cash:Caroline 0 USD We had three 20 peso bills, and I kept the bills for future travels. Caroline kept the 5 peso coin (forgot to hand it over as a tip). We transferred out the respective cash amounts we had been carrying together during the trip. How to Take Notes \uf0c1 During this trip I did not carry a laptop\u2014this was vacation after all. I like to disconnect. I did not carry a notebook either. Instead, I just took notes at the end of the day on a few sheets of paper at the hotel. This process took about 5-10 minutes each night, just recalling from memory and writing things down. These notes look like this: I made a paper spreadsheet where each line had The narration for the transaction (a description that would allow me to select an Expenses account later on) Who paid (which Assets or Income account the money came from) The amount (either in USD or MXN) After our trip, I sat down at the computer and typed the corresponding Beancount file . If I had a computer during my vacation I probably would have typed it as we went along. Of course, I had to do a few adjustments here and there because of mistakes. The bottom line is: if you\u2019re organized well, the overhead of doing this is minimal. Reconciling Expenses \uf0c1 Running bean-web on the trip\u2019s file: bean-web beancount/examples/sharing/cozumel2015.beancount You can view the balances in the \u201cAll Transactions\u201d view (click on \u201cAll Transactions\u201d). The Balance Sheet should show empty balances for Assets accounts: The balances of the equity accounts should reflect the total amount of currency conversions made during the trip. You can verify this by calculating the amount-weight average rate like this: 7539.00 / 559.88 ~= 13.465 USD/MXN (which is about right). Reviewing Contributions \uf0c1 The Income Statement should show a summary of all expenses and contributions to the project: The Income account balances show the total amounts of contributions for each person. Note that in creating the Income accounts, I went through the extra step of creating some specific accounts for each source of payment, like \u201cCaroline\u2019s Credit Card\u201d, etc. From this view, we can see that we contributed a total of 4254.28 USD (and were left with 65 MXN in hand) for this trip. The expenses side should match, considering the currency exchanges: 3694.40 + 7474 / 13.465 ~= 4249 USD which is approximately right (the small difference can be explained by the varying currency conversions). If you want to view the list of contribution payments and the final balance, click on a particular traveler\u2019s root account, e.g., \u201cIncome:Caroline\u201d (click on \u201cCaroline\u201d) which should take you to the Journal for that root account: This journal includes all the transactions in its sub-accounts. The final value at the bottom should show the total balance of those accounts, and thus, the amount of money Caroline contributed to this trip: 415 USD, and kept 5 MXN (in coin). We can do the same for Martin and find the final balance of 3839.28 USD and kept 60 MXN (in bills). You can also get the same amounts by using bean-query to achieve the same thing: ~/p/.../examples/sharing$ bean-query cozumel2015.beancount Input file: \"Mexico Trip: Cozumel & Tulum SCUBA Diving\" Ready with 105 directives (160 postings in 45 transactions). beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' sum_positio ----------- -415.00 USD 5 MXN Splitting Expenses \uf0c1 The expenses side of the Income Statement shows the breakdown of expenses. Note how some of the expense accounts are explicitly booked to each member separately by their account name, e.g., \u201c Expenses:Scuba:Martin \u201d. The other accounts, e.g. \u201c Expenses:Groceries \u201d are intended to be split. How we\u2019re going to carry this out is by adding a plugin that will transform all the transactions to actually split the postings which are intended to be shared, that is, postings without any member\u2019s name as a component. For example, the following input transaction: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation 1201.90 USD Will be transformed by the plugin into one like this: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation:Martin 600.95 USD Expenses:Accommodation:Caroline 600.95 USD Note that: Only Expenses accounts get split into multiple postings. Accounts with the name of a member are assumed by convention to be already attributed to that person. In order to achieve this, the plugin has to be provided with the names of the members. All the resulting Income and Expenses accounts include the name of a member. The plugin is enabled like this: plugin \"beancount.plugins.split_expenses\" \"Martin Caroline\" Reloading the web page after uncommenting this line from the input file and visiting the Income Statement page should show a long list of Expenses accounts split by person. Now, in order to generate the total amount of expenses incurred for each person, we have to produce the balance of all Expenses accounts with a member\u2019s name in it, accounts like \u201c Expenses:.*Martin \u201d. The web tool does not provide such a filtering capability at the moment 1 , but we can use the bean-query tool to produce the total of expenses for each person: beancount> SELECT sum(position) WHERE account ~ '^Expenses:.*Martin' sum_positio ----------- 2007.43 USD 3837.0 MXN beancount> SELECT sum(position) WHERE account '^Expenses:.*Caroline' sum_positio ----------- 1686.97 USD 3637.0 MXN This says \u201cMartin accrued expenses of 2007.43 USD and 3837.0 MXN.\u201d You can manually convert this to a dollar amount: yuzu:~$ dc -e '2007.43 3837.0 13.465 / +p' 2291.43 Or you can use the recently introduced \u201c CONVERT \u201d function of bean-query to do this: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Martin' convert_sum_position_c_ --------------------------------- 2288.528901098901098901098901 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Caroline' convert_sum_position_c_ --------------------------------- 1953.416886446886446886446886 USD (The difference between the 2291.43 and 2288.53 amounts can be attributed to the usage of slightly different exchange rates used in the converting transactions.) Similarly, you can generate the list of payments made by each person, e.g.: beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' Final Transfer \uf0c1 In order to figure out the total amount owed by each member, the process is similar: Simply sum up the balances of all the accounts attributed to that particular member: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ --------------------------------- 1538.783186813186813186813187 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ ---------------------------------- -1546.355494505494505494505494 USD Notice that this includes the Income and Expenses accounts for that person. It\u2019s as if we had two separate ledgers merged into one. (Once again, the small differences can be attributed to differences in exchange rate over time.) We can now make a final transfer amount in order to account for each of our expenses; we\u2019ve agreed to round this to 1500 USD: 2015-03-06 * \"Final transfer from Caroline to Martin to pay for difference\" Income:Caroline:Wallet -1500 USD Income:Martin:Wallet 1500 USD If you uncomment this transaction from the input file (near the end), you will find corrected balances: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ --------------------------------- -46.3554945054945054945054945 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ -------------------------------- 38.7831868131868131868131868 USD Updating your Personal Ledger \uf0c1 So great! Now we\u2019ve reconciled each other for the trip. But you still need to reflect these expenses in your personal ledger (if you have one). In this section, I will explain how you should reflect these transactions in that file. Account Names \uf0c1 First, let us take note that the accounts in your personal ledger do not have to match the account names used in the trip\u2019s ledger file. I never process those files together. (A good argument for not attempting this is that each trip will include account names from other people and I prefer not to have those leak into my main ledger file.) Using a Tag \uf0c1 I like to use a tag to report on the entire set of transactions related to the trip. In this example, I used the tag #trip-cozumel2015 . Booking Contributions \uf0c1 Your contributions into the project should be booked to a temporary holding account. I call mine \u201c Assets:Travel:Pending \u201d. For example, this transaction from the trip\u2019s file: 2015-02-25 * \"Yellow Transfers\" | \"SuperShuttle to Playa del Carmen\" Expenses:Transport:Bus 656 MXN Income:Martin:CreditCard -44.12 USD @ 14.86854 MXN will eventually be imported in my personal file and categorized like this: 2015-02-25 * \"YELLOW TRANSFER MX CO\" #trip-cozumel2015 Liabilities:US:BofA:CreditCard -44.12 USD Assets:Travel:Pending 44.12 USD You can conceptualize this as contributing money to a pending pool of cash which you will eventually receive expenses from. The same goes for cash that I brought for the trip: 2015-02-24 * \"Taking cash with me on the trip\" Assets:Cash -1200.00 USD Assets:Travel:Pending 1200.00 USD Note that absolutely all of the transactions related to the trip should be booked to that one account, including the inter-account transfers: 2015-03-06 * \"Final transfer from Mexico trip\" #trip-cozumel2015 Assets:Google:Wallet 1500 USD Assets:Travel:Pending -1500 USD Booking Expenses \uf0c1 After the trip is concluded, we want to convert the balance in the pending account to a list of Expenses. To find the list of expenses for yourself, you can issue a query like this: beancount> SELECT account, sum(position) WHERE account ~ '^Expenses:.*:Martin' GROUP BY 1 ORDER BY 1 account sum_positio ------------------------------- ----------- Expenses:Accommodation:Martin 735.45 USD Expenses:Alcohol:Martin 483 MXN Expenses:Bicycles:Martin 69.5 MXN Expenses:Flights:Martin 488.00 USD Expenses:Groceries:Martin 197.0 MXN Expenses:Museum:Martin 64 MXN Expenses:Restaurant:Martin 22.28 USD 1795.5 MXN Expenses:Scuba:Martin 506.14 USD Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Tips:Martin 225 MXN 189.16 USD Expenses:Transport:Bus:Martin 709 MXN Expenses:Transport:Taxi:Martin 53.90 USD 294 MXN Expenses:Transport:Train:Martin 5.00 USD I suppose you could script away this part to remove the member\u2019s name from the account names and have the script spit output already formatted as a nicely formatted Transaction, but because the account names will not match the personal ledger file\u2019s 1-to-1, you will have to perform a manual conversion anyway, so I did not bother automating this. Furthermore, you only have a single one of these to make after your trip, so it\u2019s not worth spending too much time making this part easier 2 . Here\u2019s what the final transaction looks like; I have a section in my input file with this: 2015-02-25 event \"location\" \"Cozumel, Mexico\" ;; (1) pushtag #trip-mexico-cozumel-2015 \u2026 other transactions related to the trip\u2026 2015-03-01 event \"location\" \"Tulum, Mexico\" ;; (1) 2015-03-07 * \"Final reconciliation - Booking pending expenses for trip\" Expenses:Travel:Accommodation 735.45 USD Expenses:Food:Alcohol 483.0 MXN Expenses:Sports:Velo 69.5 MXN Expenses:Transportation:Flights 488.00 USD Expenses:Food:Grocery 197.0 MXN Expenses:Fun:Museum 64.0 MXN Expenses:Food:Restaurant 22.28 USD Expenses:Food:Restaurant 1795.5 MXN Expenses:Scuba:Dives 506.14 USD Expenses:Scuba:Fees 7.50 USD Expenses:Scuba:Tips 225.0 MXN Expenses:Scuba:Tips 189.16 USD Expenses:Transportation:Bus 709.0 MXN Expenses:Transportation:Taxi 53.90 USD Expenses:Transportation:Taxi 294.0 MXN Expenses:Transportation:Train 5.00 USD Assets:Cash:Foreign 60.0 MXN ;; (2) Assets:Cash 330.00 USD ;; (3) Assets:Travel:Pending -2337.43 USD ;; (4) Assets:Travel:Pending -288.67 USD @@ 3897.0 MXN ;; (5) 2015-03-07 * \"Writing off difference as gift to Caroline\" ;; (6) Assets:Travel:Pending -43.18 USD Expenses:Gifts 2015-03-14 balance Assets:Travel:Pending 0 USD ;; (7) 2015-03-14 balance Assets:Travel:Pending 0 MXN poptag #trip-mexico-cozumel-2015 Observations: I used \u201cevent\u201d directives in order to track my location. I\u2019m doing this because I will eventually need it for immigration purposes (and just for fun, to track the days I spend in places). I moved the extra cash I kept to my \u201cforeign cash pocket\u201d account: Assets:Cash:Foreign I \u201cmoved\u201d back the cash I had with me after I returned from the trip. This leg removes the USD expenses from the Pending account. The leg removes the MXN expenses from the Pending account. I calculated its amount manually (3897 / 13.5 ~= 288.67 USD) and use the full amount as the exchange rate. I want to completely zero out the Pending account after the trip, and so I write off the excess amount we agreed not to pay (the difference between the imbalance and 1500.00 USD). Finally, I assert that the Pending account is empty of USD and of MXN. I found the missing amounts by running bean-check or using bean-doctor context on an incomplete transaction from Emacs. Generating Reports \uf0c1 If you want to automate the generation of reports for each of the participants in a trip, there is a script that generates text (and eventually CSV) reports for the queries mentioned in the previous sections. You can use this script to provide expenses status after or even during the trip or project, for each of the participants. The script lives in the split_expenses plugin itself, and you invoke it like this: python3 -m beancount.plugins.split_expenses --text= For each person, it will generate the following reports: A detailed journal of expenses A detailed journal of contributions A breakdown of expenses for each category (account type) Finally, it will also generate a final balance for each person, which you can use to send final reconciling transfers to each other. Try it now on one of the example files provided with Beancount. Other Examples \uf0c1 There is another example file that shows how to share expenses between three participants in duxbury2015.beancount . Look to more example files to be introduced over time. Conclusion \uf0c1 There is more than just one way to carry out the task we describe here. However, the method we present extends nicely to a larger group of participants, allows us to account for situations where particular expenses are incurred for one individual as part of a group, and finally, allows for a non-even split between the participants. This is pretty general. It\u2019s also a short step to accounting for an ongoing project with a longer term. The ideas presented in this document provide a nice use case for the usage of the double-entry method in a simple setting. I hope that working through this use case will help people develop the intuitions necessary in using the double-entry method. As our SQL-like query language matures, bean-web will eventually allow the user to create views from a set of transactions filtered from an expression. This will be implemented eventually. \u21a9 Note that we could implement a special \u201cbeancount\u201d supported format to the bean-query tool in order to write out balances in Transaction form. I\u2019m not sure how useful that would be but it\u2019s an idea worth considering for the future. \u21a9","title":"Sharing Expenses with Beancount"},{"location":"sharing_expenses_with_beancount.html#sharing-expenses-in-beancount","text":"Martin Blais , May 2015 http://furius.ca/beancount/doc/shared","title":"Sharing Expenses in Beancount"},{"location":"sharing_expenses_with_beancount.html#introduction","text":"This document presents a method for precisely and easily accounting for shared expenses in complicated group situations. For example, traveling with a group of friends where people pay for different things. We show it is possible to deal with expenses to be split evenly among the group as well as group expenses that should be allocated to specific persons. The method uses the double-entry accounting system. The essence of the method consists in separating the accounting of expenses and their associated payments. This makes it much easier to deal with shared costs, because using this method it doesn\u2019t matter who pays for what: we reconcile the precise amounts owed for each individual at the end and automatically compute final correcting transfers for each. This article is structured around a simple trip with expenses shared equally by two people. However, the same method generalizes to any type of project with incomes and expenses to be shared among multiple participants, and splitting expenses evenly or not.","title":"Introduction"},{"location":"sharing_expenses_with_beancount.html#a-travel-example","text":"Martin (the author) and Caroline (his girlfriend) visited Mexico in March 2015. We visited the island of Cozumel for a SCUBA diving trip for three days and then headed to Tulum for two days to relax and dive in the cenotes . Our assumptions are: Chaotic payments . We lead very busy lives\u2026 both of us are going to make payments ahead of time and during the trip, without any forethought regarding who will pay for what, though we will carefully record every payment. Each of us just pays for whatever preparation costs as needed to arrange this trip. For example, Caroline selected and booked flights early while I paid for the resort and booked the rental and activities at the local dive shop one week before departure. Use of shared and individual assets. Both of us are bringing cash, and are going to make payments during the trip from our respective wallets as well as from a shared pool of cash converted to local currency (Mexican pesos, for which I will use the \u201cMXN\u201d symbol), and we will use credit cards as necessary before and during the trip. Multiple currencies. Some of our expenses will be denominated in US dollars and some in Mexican pesos. For example, flights were paid in US dollars, local meals and the accommodation were paid in pesos, but the local dive shop charged us dollars. Converted amounts will come from both cash and credit cards sources. Individual expenses in a shared pool. While most of the expenses are to be shared equally, some of the expenses will apply to only one of us, and we want to account for those explicitly. For example, Caroline took a SCUBA certification course ( PADI Open Water ) and will pay for that on her own; similarly, she should not be paying for Martin\u2019s expensive boat diving costs. To complicate matters, the dive shop issued us a single joint bill for everything at the end of our stay.","title":"A Travel Example"},{"location":"sharing_expenses_with_beancount.html#a-note-about-sharing","text":"I feel that something should be said about the \u201csharing\u201d aspect of our expenses, as this topic has come up on previous discussions on the mailing-lists involving sharing examples. We are nitpicking on purpose. For the purpose of this exercise, we are accounting for every little penny spent in an incredibly detailed manner. The point of this document is specifically to show how a complex set of transactions well accounted for can be efficiently and simply disentangled to a precise accounting of expenses for each participant, regardless of who actually makes the payments. We are not cheapskates. We will assume that we\u2019ve decided to split expenses evenly. Our \u201cgenerosity\u201d to each other is not a topic relevant to this document. We\u2019re both well compensated working professionals and you can assume that we\u2019ve agreed to split the common costs for this trip evenly (50/50). One of the attributes of the method we show here is that the decision of how we choose to split expenses can be made separately from the actual payment of costs. For example, we may decide in the end that I pay for \u2154rd of the trip, but that can be decided precisely rather than in an ad-hoc \u201coh, I think I remember I paid for this\u201d manner. This is especially useful in a larger group of people because when expenses aren\u2019t tracked accurately, usually everyone is left with a feeling that they paid more than the others. The sum of the perceived fractions of expenses paid in a group is always greater than 100%...","title":"A Note About Sharing"},{"location":"sharing_expenses_with_beancount.html#overview-of-the-method","text":"In this section we present a brief illustrated overview of the method. A set of common Assets accounts that belong to the project, and book all our individual expenses and transfer for the trip as coming from external Income accounts: During the trip, we use the common Assets to make expenses. Most of the expenses are attributed to both of us (and to be shared eventually), but some of the expenses are intended to be attributed to each of us individually: After the trip, remaining Assets (like cash we walked home with) gets distributed back to ourselves to zero out the balances of the Assets accounts and we record this through contra postings to Income accounts: Finally, the list of shared Expenses are split between each other\u2014using a plugin that forks every posting that is intended to be a shared expense\u2014and the final amount is used to make a final transfer between each other so that we\u2019ve each paid for our respective expenses and we\u2019re square: Note that the final balance of expenses for each participant may differ, and these are due to particular expenses that were attributed separately, or if we decide to split the total unevenly.","title":"Overview of the Method"},{"location":"sharing_expenses_with_beancount.html#how-to-track-expenses","text":"In order to write down all expenses for this trip we will open a brand new Beancount input file. Despite the fact that the expenses will come from each person\u2019s personal accounts, it is useful to think of the trip as a special project, as if it were a separate entity, e.g., a company that exists only for the duration of the trip. The example file we wrote for our trip can be found here and should help you follow along this text.","title":"How to Track Expenses"},{"location":"sharing_expenses_with_beancount.html#accounts","text":"The set of accounts in the input file should not have to match your personal Beancount file\u2019s account names. The accounts we will use include accounts that correspond to Martin and Caroline\u2019s personal accounts but with generic names (e.g., Income:Martin:CreditCard instead of Liabilities:US:Chase ), and expense accounts may not match any regular expenses in my personal Beancount file\u2014it\u2019s not important. As a convention, any account that pertains specifically to one of the travelers will include that person\u2019s name in the account name. For example, Caroline\u2019s credit card will be named \u201c Income:Caroline:CreditCard \u201d. This is important, as we will use this later on to split contributions and expenses. Let\u2019s examine the different types of accounts we will need to carry this out.","title":"Accounts"},{"location":"sharing_expenses_with_beancount.html#external-income-accounts","text":"The \u201cproject\u201d will receive income in the form of transfers from personal accounts of either traveler. These are accounts we will consider external to the project and so will define them as Income accounts: ;; External accounts for Martin. 2015-02-01 open Income:Martin:Cash 2015-02-01 open Income:Martin:Cash:Foreign 2015-02-01 open Income:Martin:Wallet 2015-02-01 open Income:Martin:CreditCard ;; External accounts for Caroline. 2015-02-01 open Income:Caroline:Cash 2015-02-01 open Income:Caroline:Wallet 2015-02-01 open Income:Caroline:MetroCard 2015-02-01 open Income:Caroline:CreditCard Transactions carried out from these accounts must be copied from your personal Beancount file. Obviously, you must be careful to include all the transactions pertaining to the trip. I used a tag to do this in my personal file.","title":"External Income Accounts"},{"location":"sharing_expenses_with_beancount.html#assets-liabilities-accounts","text":"There will be a few Asset accounts that will be active and exist for the duration of the trip. These temporary accounts will be zero\u2019ed out at the end of it. One example is a pool of petty cash in local currency: 2015-02-01 open Assets:Cash:Pesos description: \"A shared account to contain our pocket of pesos\" We also carried cash in each of our pockets while traveling, so I created two separate accounts for that: 2015-02-01 open Assets:Cash:Martin description: \"Cash for the trip held by Martin\" 2015-02-01 open Assets:Cash:Caroline description: \"Cash for the trip held by Caroline\" Note however that despite their individual names, those accounts are considered as part of the project. It was just convenient to separately track balances for the cash we each held during the trip.","title":"Assets & Liabilities Accounts"},{"location":"sharing_expenses_with_beancount.html#expenses-accounts","text":"We will define various accounts to book our Expenses to. For example, \u201c Expenses:Flights \u201d will contain our costs associated with flight travel. For convenience, and because there are many types of expenses in this file, we chose to leverage the \u201cauto-accounts\u201d plugin and let Beancount automatically open these accounts: plugin \"beancount.ops.auto_accounts\" The great majority of these accounts are for shared expenses to be split between us. For example, shared SCUBA diving expenses will be booked to \u201c Expenses:Scuba \u201d. However, for expenses that are intended to be covered by one of us only, we simply include the name of the traveler in the account name. For example, Martin\u2019s extra costs for boat diving will be booked to the \u201c Expenses:Scuba:Martin \u201d account.","title":"Expenses Accounts"},{"location":"sharing_expenses_with_beancount.html#example-transactions","text":"Let\u2019s turn our attention to the different types of transactions present in the file. In this section I will walk you through some representative transactions (the names I give to these are arbitrary).","title":"Example Transactions"},{"location":"sharing_expenses_with_beancount.html#contributing-transactions","text":"Contributions to the project\u2019s costs are usually done in the form of expenses paid from external accounts. For example, Caroline paying for flights with her credit card looks like this: 2015-02-01 * \"Flights to Cancun\" Income:Caroline:CreditCard -976.00 USD Expenses:Flights Martin paying for the taxi to the airport looks like this: 2015-02-25 * \"Taxi to airport\" ^433f66ea0e4e Expenses:Transport:Taxi 62.80 USD Income:Martin:CreditCard","title":"Contributing Transactions"},{"location":"sharing_expenses_with_beancount.html#bringing-cash","text":"We both brought some cash on us, as we were warned it might be difficult to use credit cards in Mexico. In my personal Beancount file, the cash account is \u201c Assets:Cash \u201d but here it must get booked as an external contribution with my name on it: ;; Initial cash on us. 2015-02-24 * \"Getting cash for travel\" Income:Martin:Cash -1200 USD Assets:Cash:Martin 1200 USD Caroline\u2019s cash is similar: 2015-02-24 * \"Getting cash for travel\" Income:Caroline:Cash -300 USD Assets:Cash:Caroline 300 USD Once again, note that the Assets:Cash:Martin and Assets:Cash:Caroline accounts are considered a part of the project, in this case it just refers to who is carrying it. (These accounts are cleared at the end, so it does not matter that our names are in them.)","title":"Bringing Cash"},{"location":"sharing_expenses_with_beancount.html#transfers","text":"Before the trip, I was really busy. It looked like Caroline was going to make most of arrangements and upfront payments, so I made a transfer to her Google Wallet to help her cover for some expenses ahead of time: 2015-02-01 * \"Transfer Martin -> Caroline on Google Wallet\" Income:Martin:Wallet -1000 USD Income:Caroline:Wallet 1000 USD","title":"Transfers"},{"location":"sharing_expenses_with_beancount.html#cash-conversions","text":"Obtaining local currency was done by changing a small amount of cash at the airport (at a very bad rate): 2015-02-25 * \"Exchanged cash at XIC at CUN airport\" Assets:Cash:Caroline -100.00 USD @ 12.00 MXN Assets:Cash:Pesos 1200 MXN The \u201c Assets:Cash:Pesos \u201d account tracks our common pool of local currency that we use for various small expenses.","title":"Cash Conversions"},{"location":"sharing_expenses_with_beancount.html#cash-expenses-in-us-dollars","text":"Some local expenses will call for US money, which in this example I paid from my cash pocket: 2015-03-01 * \"Motmot Diving\" | \"Deposit for cenote diving\" Expenses:Scuba 50.00 USD Assets:Cash:Martin","title":"Cash Expenses in US Dollars"},{"location":"sharing_expenses_with_beancount.html#cash-expenses-in-local-currency","text":"Paying cash using pesos from our shared stash of pesos looks like this: 2015-02-25 * \"UltraMar Ferry across to Cozumel\" Expenses:Transport:Bus 326 MXN Assets:Cash:Pesos Sometimes we even had to pay with a mix of US dollars and pesos. In this example, we ran out of pesos, so we have to give them dollars and pesos (all the restaurants and hotels in the beach part of Tulum accept US currency): 2015-03-01 * \"Hartwood\" | \"Dinner - ran out of pesos\" Expenses:Restaurant 1880 MXN Assets:Cash:Pesos -1400 MXN Assets:Cash:Martin -40.00 USD @ 12.00 MXN I used the unfavorable rate the restaurant was offering to accept dollars at (the market rate was 14.5 at the time).","title":"Cash Expenses in Local Currency"},{"location":"sharing_expenses_with_beancount.html#individual-expenses","text":"Here is an example of booking individual expenses using shared money. In order for us to have access to the reef for diving, we had to pay a \u201cmarine park\u201d fee of $2.50 per day to the island. This was a short trip where I dove only three days there and Caroline\u2019s fee was included in her course except for one day: 2015-02-25 * \"Marine Park (3 days Martin, 1 day Caroline)\" Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Scuba:ParkFees:Caroline 2.50 USD Assets:Cash:Martin All that needs to be done is to book these to expense accounts with our names in them, which will get separated out at the end. Here is a more complicated example: the dive shop at Scuba Club Cozumel charged us a single bill at the end of our stay for all our rental gear and extra dives. All I did here was translate the itemized bill into a single transaction, booking to each other: 2015-03-01 * \"Scuba Club Cozumel\" | \"Dive shop bill\" ^69b409189b37 Income:Martin:CreditCard -381.64 USD Expenses:Scuba:Martin 27 USD ;; Regulator w/ Gauge Expenses:Scuba:Caroline 9 USD ;; Regulator w/ Gauge Expenses:Scuba:Martin 27 USD ;; BCD Expenses:Scuba:Caroline 9 USD ;; BCD Expenses:Scuba:Martin 6 USD ;; Fins Expenses:Scuba:Martin 24 USD ;; Wetsuit Expenses:Scuba:Caroline 8 USD ;; Wetsuit Expenses:Scuba:Caroline 9 USD ;; Dive computer Expenses:Scuba:Martin 5 USD ;; U/W Light Expenses:Scuba:Caroline 70 USD ;; Dive trip (2 tank) Expenses:Scuba:Martin 45 USD ;; Wreck Dive w/ Lite Expenses:Scuba:Martin 45 USD ;; Afternoon dive Expenses:Scuba:Caroline 45 USD ;; Afternoon dive Expenses:Scuba:Martin 28.64 USD ;; Taxes Expenses:Scuba:Caroline 24.00 USD ;; Taxes","title":"Individual Expenses"},{"location":"sharing_expenses_with_beancount.html#final-balances","text":"Of course you can use balance assertions at any time during the trip. For example, just before leaving at the Cancun airport we knew we wouldn\u2019t spend any more Mexican pesos for a while, so I counted what we had left after Caroline decided to splurge the remainder of them into overpriced chocolate sold at the airport shop: 2015-03-04 balance Assets:Cash:Pesos 65 MXN Ideally the bookkeeper should want to do this at a quiet moment at the end of every day or couple of days, which makes it easier to triangulate an expense you might have forgotten to enter (we are on vacation after all, in our relaxed state of mind we forget to write down stuff here and there).","title":"Final Balances"},{"location":"sharing_expenses_with_beancount.html#clearing-asset-accounts","text":"At the end of the trip, you should clear the final balances of all Assets and Liabilities accounts by transferring the remaining funds out to the participants, i.e., to the Income accounts. This should leave all the balances of the trip at zero and ensures all the cash put forward for trip expenses has been moved out to travelers. By the way, it doesn\u2019t matter much who keeps this money, because at the end we will end up making a single correcting transfer that should account for the balance required to create an even split. You can transfer it to anyone; the end result will be the same. Our clearing transaction looked like this: 2015-03-06 * \"Final transfer to clear internal balances to external ones\" Assets:Cash:Pesos -65 MXN Income:Martin:Cash:Foreign 60 MXN Income:Caroline:Cash 5 MXN Assets:Cash:Martin -330 USD Income:Martin:Cash 330 USD Assets:Cash:Caroline -140 USD Income:Caroline:Cash 140 USD 2015-03-07 balance Assets:Cash:Pesos 0 MXN 2015-03-07 balance Assets:Cash:Pesos 0 USD 2015-03-07 balance Assets:Cash:Martin 0 USD 2015-03-07 balance Assets:Cash:Caroline 0 USD We had three 20 peso bills, and I kept the bills for future travels. Caroline kept the 5 peso coin (forgot to hand it over as a tip). We transferred out the respective cash amounts we had been carrying together during the trip.","title":"Clearing Asset Accounts"},{"location":"sharing_expenses_with_beancount.html#how-to-take-notes","text":"During this trip I did not carry a laptop\u2014this was vacation after all. I like to disconnect. I did not carry a notebook either. Instead, I just took notes at the end of the day on a few sheets of paper at the hotel. This process took about 5-10 minutes each night, just recalling from memory and writing things down. These notes look like this: I made a paper spreadsheet where each line had The narration for the transaction (a description that would allow me to select an Expenses account later on) Who paid (which Assets or Income account the money came from) The amount (either in USD or MXN) After our trip, I sat down at the computer and typed the corresponding Beancount file . If I had a computer during my vacation I probably would have typed it as we went along. Of course, I had to do a few adjustments here and there because of mistakes. The bottom line is: if you\u2019re organized well, the overhead of doing this is minimal.","title":"How to Take Notes"},{"location":"sharing_expenses_with_beancount.html#reconciling-expenses","text":"Running bean-web on the trip\u2019s file: bean-web beancount/examples/sharing/cozumel2015.beancount You can view the balances in the \u201cAll Transactions\u201d view (click on \u201cAll Transactions\u201d). The Balance Sheet should show empty balances for Assets accounts: The balances of the equity accounts should reflect the total amount of currency conversions made during the trip. You can verify this by calculating the amount-weight average rate like this: 7539.00 / 559.88 ~= 13.465 USD/MXN (which is about right).","title":"Reconciling Expenses"},{"location":"sharing_expenses_with_beancount.html#reviewing-contributions","text":"The Income Statement should show a summary of all expenses and contributions to the project: The Income account balances show the total amounts of contributions for each person. Note that in creating the Income accounts, I went through the extra step of creating some specific accounts for each source of payment, like \u201cCaroline\u2019s Credit Card\u201d, etc. From this view, we can see that we contributed a total of 4254.28 USD (and were left with 65 MXN in hand) for this trip. The expenses side should match, considering the currency exchanges: 3694.40 + 7474 / 13.465 ~= 4249 USD which is approximately right (the small difference can be explained by the varying currency conversions). If you want to view the list of contribution payments and the final balance, click on a particular traveler\u2019s root account, e.g., \u201cIncome:Caroline\u201d (click on \u201cCaroline\u201d) which should take you to the Journal for that root account: This journal includes all the transactions in its sub-accounts. The final value at the bottom should show the total balance of those accounts, and thus, the amount of money Caroline contributed to this trip: 415 USD, and kept 5 MXN (in coin). We can do the same for Martin and find the final balance of 3839.28 USD and kept 60 MXN (in bills). You can also get the same amounts by using bean-query to achieve the same thing: ~/p/.../examples/sharing$ bean-query cozumel2015.beancount Input file: \"Mexico Trip: Cozumel & Tulum SCUBA Diving\" Ready with 105 directives (160 postings in 45 transactions). beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' sum_positio ----------- -415.00 USD 5 MXN","title":"Reviewing Contributions"},{"location":"sharing_expenses_with_beancount.html#splitting-expenses","text":"The expenses side of the Income Statement shows the breakdown of expenses. Note how some of the expense accounts are explicitly booked to each member separately by their account name, e.g., \u201c Expenses:Scuba:Martin \u201d. The other accounts, e.g. \u201c Expenses:Groceries \u201d are intended to be split. How we\u2019re going to carry this out is by adding a plugin that will transform all the transactions to actually split the postings which are intended to be shared, that is, postings without any member\u2019s name as a component. For example, the following input transaction: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation 1201.90 USD Will be transformed by the plugin into one like this: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation:Martin 600.95 USD Expenses:Accommodation:Caroline 600.95 USD Note that: Only Expenses accounts get split into multiple postings. Accounts with the name of a member are assumed by convention to be already attributed to that person. In order to achieve this, the plugin has to be provided with the names of the members. All the resulting Income and Expenses accounts include the name of a member. The plugin is enabled like this: plugin \"beancount.plugins.split_expenses\" \"Martin Caroline\" Reloading the web page after uncommenting this line from the input file and visiting the Income Statement page should show a long list of Expenses accounts split by person. Now, in order to generate the total amount of expenses incurred for each person, we have to produce the balance of all Expenses accounts with a member\u2019s name in it, accounts like \u201c Expenses:.*Martin \u201d. The web tool does not provide such a filtering capability at the moment 1 , but we can use the bean-query tool to produce the total of expenses for each person: beancount> SELECT sum(position) WHERE account ~ '^Expenses:.*Martin' sum_positio ----------- 2007.43 USD 3837.0 MXN beancount> SELECT sum(position) WHERE account '^Expenses:.*Caroline' sum_positio ----------- 1686.97 USD 3637.0 MXN This says \u201cMartin accrued expenses of 2007.43 USD and 3837.0 MXN.\u201d You can manually convert this to a dollar amount: yuzu:~$ dc -e '2007.43 3837.0 13.465 / +p' 2291.43 Or you can use the recently introduced \u201c CONVERT \u201d function of bean-query to do this: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Martin' convert_sum_position_c_ --------------------------------- 2288.528901098901098901098901 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Caroline' convert_sum_position_c_ --------------------------------- 1953.416886446886446886446886 USD (The difference between the 2291.43 and 2288.53 amounts can be attributed to the usage of slightly different exchange rates used in the converting transactions.) Similarly, you can generate the list of payments made by each person, e.g.: beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline'","title":"Splitting Expenses"},{"location":"sharing_expenses_with_beancount.html#final-transfer","text":"In order to figure out the total amount owed by each member, the process is similar: Simply sum up the balances of all the accounts attributed to that particular member: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ --------------------------------- 1538.783186813186813186813187 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ ---------------------------------- -1546.355494505494505494505494 USD Notice that this includes the Income and Expenses accounts for that person. It\u2019s as if we had two separate ledgers merged into one. (Once again, the small differences can be attributed to differences in exchange rate over time.) We can now make a final transfer amount in order to account for each of our expenses; we\u2019ve agreed to round this to 1500 USD: 2015-03-06 * \"Final transfer from Caroline to Martin to pay for difference\" Income:Caroline:Wallet -1500 USD Income:Martin:Wallet 1500 USD If you uncomment this transaction from the input file (near the end), you will find corrected balances: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ --------------------------------- -46.3554945054945054945054945 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ -------------------------------- 38.7831868131868131868131868 USD","title":"Final Transfer"},{"location":"sharing_expenses_with_beancount.html#updating-your-personal-ledger","text":"So great! Now we\u2019ve reconciled each other for the trip. But you still need to reflect these expenses in your personal ledger (if you have one). In this section, I will explain how you should reflect these transactions in that file.","title":"Updating your Personal Ledger"},{"location":"sharing_expenses_with_beancount.html#account-names","text":"First, let us take note that the accounts in your personal ledger do not have to match the account names used in the trip\u2019s ledger file. I never process those files together. (A good argument for not attempting this is that each trip will include account names from other people and I prefer not to have those leak into my main ledger file.)","title":"Account Names"},{"location":"sharing_expenses_with_beancount.html#using-a-tag","text":"I like to use a tag to report on the entire set of transactions related to the trip. In this example, I used the tag #trip-cozumel2015 .","title":"Using a Tag"},{"location":"sharing_expenses_with_beancount.html#booking-contributions","text":"Your contributions into the project should be booked to a temporary holding account. I call mine \u201c Assets:Travel:Pending \u201d. For example, this transaction from the trip\u2019s file: 2015-02-25 * \"Yellow Transfers\" | \"SuperShuttle to Playa del Carmen\" Expenses:Transport:Bus 656 MXN Income:Martin:CreditCard -44.12 USD @ 14.86854 MXN will eventually be imported in my personal file and categorized like this: 2015-02-25 * \"YELLOW TRANSFER MX CO\" #trip-cozumel2015 Liabilities:US:BofA:CreditCard -44.12 USD Assets:Travel:Pending 44.12 USD You can conceptualize this as contributing money to a pending pool of cash which you will eventually receive expenses from. The same goes for cash that I brought for the trip: 2015-02-24 * \"Taking cash with me on the trip\" Assets:Cash -1200.00 USD Assets:Travel:Pending 1200.00 USD Note that absolutely all of the transactions related to the trip should be booked to that one account, including the inter-account transfers: 2015-03-06 * \"Final transfer from Mexico trip\" #trip-cozumel2015 Assets:Google:Wallet 1500 USD Assets:Travel:Pending -1500 USD","title":"Booking Contributions"},{"location":"sharing_expenses_with_beancount.html#booking-expenses","text":"After the trip is concluded, we want to convert the balance in the pending account to a list of Expenses. To find the list of expenses for yourself, you can issue a query like this: beancount> SELECT account, sum(position) WHERE account ~ '^Expenses:.*:Martin' GROUP BY 1 ORDER BY 1 account sum_positio ------------------------------- ----------- Expenses:Accommodation:Martin 735.45 USD Expenses:Alcohol:Martin 483 MXN Expenses:Bicycles:Martin 69.5 MXN Expenses:Flights:Martin 488.00 USD Expenses:Groceries:Martin 197.0 MXN Expenses:Museum:Martin 64 MXN Expenses:Restaurant:Martin 22.28 USD 1795.5 MXN Expenses:Scuba:Martin 506.14 USD Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Tips:Martin 225 MXN 189.16 USD Expenses:Transport:Bus:Martin 709 MXN Expenses:Transport:Taxi:Martin 53.90 USD 294 MXN Expenses:Transport:Train:Martin 5.00 USD I suppose you could script away this part to remove the member\u2019s name from the account names and have the script spit output already formatted as a nicely formatted Transaction, but because the account names will not match the personal ledger file\u2019s 1-to-1, you will have to perform a manual conversion anyway, so I did not bother automating this. Furthermore, you only have a single one of these to make after your trip, so it\u2019s not worth spending too much time making this part easier 2 . Here\u2019s what the final transaction looks like; I have a section in my input file with this: 2015-02-25 event \"location\" \"Cozumel, Mexico\" ;; (1) pushtag #trip-mexico-cozumel-2015 \u2026 other transactions related to the trip\u2026 2015-03-01 event \"location\" \"Tulum, Mexico\" ;; (1) 2015-03-07 * \"Final reconciliation - Booking pending expenses for trip\" Expenses:Travel:Accommodation 735.45 USD Expenses:Food:Alcohol 483.0 MXN Expenses:Sports:Velo 69.5 MXN Expenses:Transportation:Flights 488.00 USD Expenses:Food:Grocery 197.0 MXN Expenses:Fun:Museum 64.0 MXN Expenses:Food:Restaurant 22.28 USD Expenses:Food:Restaurant 1795.5 MXN Expenses:Scuba:Dives 506.14 USD Expenses:Scuba:Fees 7.50 USD Expenses:Scuba:Tips 225.0 MXN Expenses:Scuba:Tips 189.16 USD Expenses:Transportation:Bus 709.0 MXN Expenses:Transportation:Taxi 53.90 USD Expenses:Transportation:Taxi 294.0 MXN Expenses:Transportation:Train 5.00 USD Assets:Cash:Foreign 60.0 MXN ;; (2) Assets:Cash 330.00 USD ;; (3) Assets:Travel:Pending -2337.43 USD ;; (4) Assets:Travel:Pending -288.67 USD @@ 3897.0 MXN ;; (5) 2015-03-07 * \"Writing off difference as gift to Caroline\" ;; (6) Assets:Travel:Pending -43.18 USD Expenses:Gifts 2015-03-14 balance Assets:Travel:Pending 0 USD ;; (7) 2015-03-14 balance Assets:Travel:Pending 0 MXN poptag #trip-mexico-cozumel-2015 Observations: I used \u201cevent\u201d directives in order to track my location. I\u2019m doing this because I will eventually need it for immigration purposes (and just for fun, to track the days I spend in places). I moved the extra cash I kept to my \u201cforeign cash pocket\u201d account: Assets:Cash:Foreign I \u201cmoved\u201d back the cash I had with me after I returned from the trip. This leg removes the USD expenses from the Pending account. The leg removes the MXN expenses from the Pending account. I calculated its amount manually (3897 / 13.5 ~= 288.67 USD) and use the full amount as the exchange rate. I want to completely zero out the Pending account after the trip, and so I write off the excess amount we agreed not to pay (the difference between the imbalance and 1500.00 USD). Finally, I assert that the Pending account is empty of USD and of MXN. I found the missing amounts by running bean-check or using bean-doctor context on an incomplete transaction from Emacs.","title":"Booking Expenses"},{"location":"sharing_expenses_with_beancount.html#generating-reports","text":"If you want to automate the generation of reports for each of the participants in a trip, there is a script that generates text (and eventually CSV) reports for the queries mentioned in the previous sections. You can use this script to provide expenses status after or even during the trip or project, for each of the participants. The script lives in the split_expenses plugin itself, and you invoke it like this: python3 -m beancount.plugins.split_expenses --text= For each person, it will generate the following reports: A detailed journal of expenses A detailed journal of contributions A breakdown of expenses for each category (account type) Finally, it will also generate a final balance for each person, which you can use to send final reconciling transfers to each other. Try it now on one of the example files provided with Beancount.","title":"Generating Reports"},{"location":"sharing_expenses_with_beancount.html#other-examples","text":"There is another example file that shows how to share expenses between three participants in duxbury2015.beancount . Look to more example files to be introduced over time.","title":"Other Examples"},{"location":"sharing_expenses_with_beancount.html#conclusion","text":"There is more than just one way to carry out the task we describe here. However, the method we present extends nicely to a larger group of participants, allows us to account for situations where particular expenses are incurred for one individual as part of a group, and finally, allows for a non-even split between the participants. This is pretty general. It\u2019s also a short step to accounting for an ongoing project with a longer term. The ideas presented in this document provide a nice use case for the usage of the double-entry method in a simple setting. I hope that working through this use case will help people develop the intuitions necessary in using the double-entry method. As our SQL-like query language matures, bean-web will eventually allow the user to create views from a set of transactions filtered from an expression. This will be implemented eventually. \u21a9 Note that we could implement a special \u201cbeancount\u201d supported format to the bean-query tool in order to write out balances in Transaction form. I\u2019m not sure how useful that would be but it\u2019s an idea worth considering for the future. \u21a9","title":"Conclusion"},{"location":"stock_vesting_in_beancount.html","text":"Stock Vesting in Beancount \uf0c1 Martin Blais , June 2015 http://furius.ca/beancount/doc/vesting Introduction \uf0c1 This document explains the vesting of restricted stock units in Beancount, by way of an example. This example may not exactly match your situation, but enough detail is provided that you should be able to adapt it for your own particular differences. A working example file can be found here to follow along with this text. Restricted Stock Compensation \uf0c1 Many technology companies offer their employees incentive compensation in the form of \u201cgrants\u201d (or \u201cawards\u201d) of \u201crestricted stock units\u201d (RSU), which is essentially a promise for the \u201crelease\u201d to you of actual shares in the future. The stock is \u201crestricted\u201d in the sense that you cannot access it\u2014you only receive it when it \u201cvests\u201d, and this happens based on a schedule. Typically, you are promised a fixed number of shares that vest every quarter or every month over a period of 3 or 4 years. If you leave the company, your remaining unvested shares are lost. One way you can view these RSUs is as an asset, a receivable that arrives regularly over time. These RSUs are essentially compensation denominated in the currency of the company\u2019s shares itself. We want to track the unraveling of these unvested units, and correctly account for their conversion to real stock with a cost basis and including whatever taxes were paid upon vesting. Tracking Awards \uf0c1 Commodities \uf0c1 First we want to define some commodities. In this example, I work for \u201cHooli Inc.\u201d and will eventually receive shares of that company (valued in US dollars): 1990-12-02 commodity HOOL name: \"Common shares of Hooli Inc.\" quote: USD We will also want to track the amount of unvested shares: 2013-01-28 commodity HOOL.UNVEST name: \"Unvested shares of Hooli from awards.\" Accounts for Awards \uf0c1 Grants received is income. I use \u201cIncome:US:Hooli\u201d as the root for all income accounts from Hooli, but in particular, I define an account for the awards, which contains units of unvested stock: 2013-01-28 open Income:US:Hooli:Awards HOOL.UNVEST When the stock vests, we will need to book the other side of this income somewhere, so we define an expenses account to count how much stock has been vested over a period of time: 2014-01-28 open Expenses:Hooli:Vested HOOL.UNVEST Receiving Awards \uf0c1 When you receive a new award (this may occur every year, for example, some people call this a \u201cstock refresh\u201d), you receive it as income and deposit it into a fresh new account, used to track this particular award: 2014-04-02 * \"Award S0012345\" Income:US:Hooli:Awards -1680 HOOL.UNVEST Assets:US:Hooli:Unvested:S0012345 1680 HOOL.UNVEST 2014-04-02 open Assets:US:Hooli:Unvested:S0012345 You may have multiple active awards at the same time. It\u2019s nice to have a separate account per award, as it offers a natural way to list their contents and when the award expires, you can close the account\u2014the list of open award accounts gives you the list of outstanding & actively vesting awards. In this example I used the number of the award (#S0012345) as the sub-account name. It\u2019s useful to use the number as the statements typically include it. I like to keep all the awards in a small dedicated section. Vesting Events \uf0c1 Then I have a different section that contains all the transactions that follow a vesting event. Accounts \uf0c1 First, when we vest stock, it\u2019s a taxable income event. The cash value for the stock needs an Income account: 2013-04-04 open Income:US:Hooli:RSU Taxes paid should be on the annual expenses accounts you should have defined somewhere else to account for that year\u2019s taxes (this is covered elsewhere in the cookbook): 2015-01-01 open Expenses:Taxes:TY2015:US:StateNY 2015-01-01 open Expenses:Taxes:TY2015:US:Federal 2015-01-01 open Expenses:Taxes:TY2015:US:SocSec 2015-01-01 open Expenses:Taxes:TY2015:US:SDI 2015-01-01 open Expenses:Taxes:TY2015:US:Medicare 2015-01-01 open Expenses:Taxes:TY2015:US:CityNYC After paying taxes on the received income, the remaining cash is deposited in a limbo account before getting converted: 2013-01-28 open Assets:US:Hooli:RSURefund Also, in another section we should have an account for the brokerage which holds and manages the shares for you: 2013-04-04 open Assets:US:Schwab:HOOL Generally you don\u2019t have a choice for this broker because the company you work normally makes an arrangement with an external firm in order to administer the restricted stock program. Typical firms doing this type of administration are Morgan Stanley, Salomon Smith Barney, Schwab, even E*Trade. In the example we\u2019ll use a Schwab account. And we also need some sort of checking account to receive cash in lieu of fractional shares. I\u2019ll assume a Bank of America account in the example: 2001-01-01 open Assets:US:BofA:Checking Vesting \uf0c1 First, the vesting events themselves: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD Assets:US:Hooli:RSURefund 2458.97 USD This corresponds line-by-line to a payroll stub that I receive each vesting event for each award. Since all the RSU awards at Hooli vest on the same day of the month, this means that I have clusters of these, one for each award (in the example file I show two awards). Some observations are in order: The value of the Income posting consists of the number of shares vested times the FMV of the stock. In this case, that is 35 shares \u2a09 $131.37/share = $4597.95. I just write the dollar amount because it is provided to me on the pay stub. Receiving vested stock is a taxable income event, and Hooli automatically withholds taxes and pays them to the government on its employees\u2019 behalf. These are the Expenses:Taxes accounts corresponding to your W-2. Finally, I don\u2019t directly deposit the converted shares to the brokerage account; this is because the remainder of cash after paying tax expenses is not necessarily a round number of shares. Therefore, I used a limbo account ( Assets:US:Hooli:RSURefund ) and decouple the receipt from the conversion. This way, each transaction corresponds exactly to one pay stub. It makes it easier to enter the data. Also note that I used a unique link ( ^392f97dd62d0 ) to group all the transactions for a particular vesting event. You could also use tags if you prefer. Conversion to Actual Stock \uf0c1 Now we\u2019re ready to convert the remaining cash to stock units. This happens in the brokerage firm and you should see the new shares in your brokerage account. The brokerage will typically issue a \u201cstock release report\u201d statement for each vesting event for each award, with the details necessary to make the conversion, namely, the actual number of shares converted from cash and the cost basis (the FMV on the vesting day): 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST The first two postings deposit the shares and subtract the dollar value from the limbo account where the vesting transaction left the remaining cash. You should make sure to use the specific FMV price provided by the statement and not an approximate price, because this has tax consequences later on. Note that you cannot buy fractional shares, so the cost of the rounded amount of shares (18) will leave some remaining cash in the limbo account. The last two postings deduct from the balance of unvested shares and I \u201creceive\u201d an expenses; that expenses account basically counts how many shares were vested over a particular time period. Here again you will probably have one of these conversions for each stock grant you have. I enter them separately, so that one statement matches one transaction. This is a good rule to follow. Refund for Fractions \uf0c1 After all the conversion events have moved cash out of the limbo account, it is left the fractional remainders from all the conversions. In my case, this remainder is refunded by Hooli 3-4 weeks after vesting as a single separate pay stub that includes all the remainders (it even lists each of them separately). I enter this as a transaction as well: 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.31 USD Assets:US:Hooli:RSURefund -2.88 USD ; (For second award in example) Assets:US:BofA:Checking 97.19 USD After the fractions have been paid the limbo account should be empty. I verify this claim using a balance assertion: 2015-06-14 balance Assets:US:Hooli:RSURefund 0 USD This provides me with some sense that the numbers are right. Organizing your Input \uf0c1 I like to put all the vesting events together in my input file; this makes them much easier to update and reconcile, especially with multiple awards. For example, with two awards I would have multiple chunks of transactions like this, separated with 4-5 empty lines to delineate them: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Assets:US:Hooli:RSURefund 2458.97 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD 2015-05-27 * \"Vesting Event - C123456 - HOOL\" #award-C123456 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -1970.55 USD Assets:US:Hooli:RSURefund 1053.84 USD Expenses:Taxes:TY2015:US:Medicare 28.58 USD Expenses:Taxes:TY2015:US:Federal 492.63 USD Expenses:Taxes:TY2015:US:CityNYC 83.75 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 189.57 USD Expenses:Taxes:TY2015:US:SocSec 122.18 USD 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 9 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:C123456 -15 HOOL.UNVEST Expenses:Hooli:Vested 15 HOOL.UNVEST 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.30 USD Assets:US:Hooli:RSURefund -2.88 USD Assets:US:BofA:Checking 97.18 USD 2015-02-14 balance Assets:US:Hooli:RSURefund 0 USD Unvested Shares \uf0c1 Asserting Unvested Balances \uf0c1 Finally, you may occasionally want to assert the number of unvested shares. I like to do this semi-annually, for example. The brokerage company that handles the RSUs for Hooli should be able to list how many unvested shares of each award remain, so it\u2019s as simple as looking it up on a website: 2015-06-04 balance Assets:US:Hooli:Unvested:S0012345 1645 HOOL.UNVEST 2015-06-04 balance Assets:US:Hooli:Unvested:C123456 705 HOOL.UNVEST Pricing Unvested Shares \uf0c1 You can also put a price on the unvested shares in order to estimate the unvested dollar amount. You should use a fictional currency for this, because we want to avoid a situation where a balance sheet is produced that includes these unvested assets as regular dollars: 2015-06-02 price HOOL.UNVEST 132.4300 USD.UNVEST At the time of this writing, the bean-web interface does not convert the units if they are not held at cost, but using the SQL query interface or writing a custom script you should be able to produce those numbers: $ bean-query examples/vesting/vesting.beancount beancount> select account, sum(convert(position, 'USD.UNVEST')) as unvested where account ~ 'Unvested' group by account; account unvested --------------------------------- ---------------------- Assets:US:Hooli:Unvested:S0012345 217847.3500 USD.UNVEST Assets:US:Hooli:Unvested:C123456 93363.1500 USD.UNVEST Selling Vested Stock \uf0c1 After each vesting event, the stock is left in your brokerage account. Selling this stock proceeds just as in any other trading transaction (see Trading with Beancount for full details). For example, selling the shares from the example would look something like this: 2015-09-10 * \"Selling shares\" Assets:US:Schwab:HOOL -26 HOOL {131.3700 USD} @ 138.23 USD Assets:US:Schwab:Cash 3593.98 USD Income:US:Schwab:Gains Here you can see why it matters that the cost basis you used on the conversion event is the correct one: You will have to pay taxes on the difference (in Income:US:Schwab:Gains ). In this example the taxable difference is (138.23 - 131.37) dollars per share. I like to keep all the brokerage transactions in a separate section of my document, where other transactions related to the brokerage occur, such as fees, dividends and transfers. Conclusion \uf0c1 This is a simple example that is modeled after how technology companies deal with this type of compensation. It is by no means comprehensive, and some of the details will necessarily vary in your situation. In particular, it does not explain how to deal with options (ISOs). My hope is that there is enough meat in this document to allow you to extrapolate and adapt to your particular situation. If you get stuck, please reach out on the mailing-list .","title":"Stock Vesting in Beancount"},{"location":"stock_vesting_in_beancount.html#stock-vesting-in-beancount","text":"Martin Blais , June 2015 http://furius.ca/beancount/doc/vesting","title":"Stock Vesting in Beancount"},{"location":"stock_vesting_in_beancount.html#introduction","text":"This document explains the vesting of restricted stock units in Beancount, by way of an example. This example may not exactly match your situation, but enough detail is provided that you should be able to adapt it for your own particular differences. A working example file can be found here to follow along with this text.","title":"Introduction"},{"location":"stock_vesting_in_beancount.html#restricted-stock-compensation","text":"Many technology companies offer their employees incentive compensation in the form of \u201cgrants\u201d (or \u201cawards\u201d) of \u201crestricted stock units\u201d (RSU), which is essentially a promise for the \u201crelease\u201d to you of actual shares in the future. The stock is \u201crestricted\u201d in the sense that you cannot access it\u2014you only receive it when it \u201cvests\u201d, and this happens based on a schedule. Typically, you are promised a fixed number of shares that vest every quarter or every month over a period of 3 or 4 years. If you leave the company, your remaining unvested shares are lost. One way you can view these RSUs is as an asset, a receivable that arrives regularly over time. These RSUs are essentially compensation denominated in the currency of the company\u2019s shares itself. We want to track the unraveling of these unvested units, and correctly account for their conversion to real stock with a cost basis and including whatever taxes were paid upon vesting.","title":"Restricted Stock Compensation"},{"location":"stock_vesting_in_beancount.html#tracking-awards","text":"","title":"Tracking Awards"},{"location":"stock_vesting_in_beancount.html#commodities","text":"First we want to define some commodities. In this example, I work for \u201cHooli Inc.\u201d and will eventually receive shares of that company (valued in US dollars): 1990-12-02 commodity HOOL name: \"Common shares of Hooli Inc.\" quote: USD We will also want to track the amount of unvested shares: 2013-01-28 commodity HOOL.UNVEST name: \"Unvested shares of Hooli from awards.\"","title":"Commodities"},{"location":"stock_vesting_in_beancount.html#accounts-for-awards","text":"Grants received is income. I use \u201cIncome:US:Hooli\u201d as the root for all income accounts from Hooli, but in particular, I define an account for the awards, which contains units of unvested stock: 2013-01-28 open Income:US:Hooli:Awards HOOL.UNVEST When the stock vests, we will need to book the other side of this income somewhere, so we define an expenses account to count how much stock has been vested over a period of time: 2014-01-28 open Expenses:Hooli:Vested HOOL.UNVEST","title":"Accounts for Awards"},{"location":"stock_vesting_in_beancount.html#receiving-awards","text":"When you receive a new award (this may occur every year, for example, some people call this a \u201cstock refresh\u201d), you receive it as income and deposit it into a fresh new account, used to track this particular award: 2014-04-02 * \"Award S0012345\" Income:US:Hooli:Awards -1680 HOOL.UNVEST Assets:US:Hooli:Unvested:S0012345 1680 HOOL.UNVEST 2014-04-02 open Assets:US:Hooli:Unvested:S0012345 You may have multiple active awards at the same time. It\u2019s nice to have a separate account per award, as it offers a natural way to list their contents and when the award expires, you can close the account\u2014the list of open award accounts gives you the list of outstanding & actively vesting awards. In this example I used the number of the award (#S0012345) as the sub-account name. It\u2019s useful to use the number as the statements typically include it. I like to keep all the awards in a small dedicated section.","title":"Receiving Awards"},{"location":"stock_vesting_in_beancount.html#vesting-events","text":"Then I have a different section that contains all the transactions that follow a vesting event.","title":"Vesting Events"},{"location":"stock_vesting_in_beancount.html#accounts","text":"First, when we vest stock, it\u2019s a taxable income event. The cash value for the stock needs an Income account: 2013-04-04 open Income:US:Hooli:RSU Taxes paid should be on the annual expenses accounts you should have defined somewhere else to account for that year\u2019s taxes (this is covered elsewhere in the cookbook): 2015-01-01 open Expenses:Taxes:TY2015:US:StateNY 2015-01-01 open Expenses:Taxes:TY2015:US:Federal 2015-01-01 open Expenses:Taxes:TY2015:US:SocSec 2015-01-01 open Expenses:Taxes:TY2015:US:SDI 2015-01-01 open Expenses:Taxes:TY2015:US:Medicare 2015-01-01 open Expenses:Taxes:TY2015:US:CityNYC After paying taxes on the received income, the remaining cash is deposited in a limbo account before getting converted: 2013-01-28 open Assets:US:Hooli:RSURefund Also, in another section we should have an account for the brokerage which holds and manages the shares for you: 2013-04-04 open Assets:US:Schwab:HOOL Generally you don\u2019t have a choice for this broker because the company you work normally makes an arrangement with an external firm in order to administer the restricted stock program. Typical firms doing this type of administration are Morgan Stanley, Salomon Smith Barney, Schwab, even E*Trade. In the example we\u2019ll use a Schwab account. And we also need some sort of checking account to receive cash in lieu of fractional shares. I\u2019ll assume a Bank of America account in the example: 2001-01-01 open Assets:US:BofA:Checking","title":"Accounts"},{"location":"stock_vesting_in_beancount.html#vesting","text":"First, the vesting events themselves: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD Assets:US:Hooli:RSURefund 2458.97 USD This corresponds line-by-line to a payroll stub that I receive each vesting event for each award. Since all the RSU awards at Hooli vest on the same day of the month, this means that I have clusters of these, one for each award (in the example file I show two awards). Some observations are in order: The value of the Income posting consists of the number of shares vested times the FMV of the stock. In this case, that is 35 shares \u2a09 $131.37/share = $4597.95. I just write the dollar amount because it is provided to me on the pay stub. Receiving vested stock is a taxable income event, and Hooli automatically withholds taxes and pays them to the government on its employees\u2019 behalf. These are the Expenses:Taxes accounts corresponding to your W-2. Finally, I don\u2019t directly deposit the converted shares to the brokerage account; this is because the remainder of cash after paying tax expenses is not necessarily a round number of shares. Therefore, I used a limbo account ( Assets:US:Hooli:RSURefund ) and decouple the receipt from the conversion. This way, each transaction corresponds exactly to one pay stub. It makes it easier to enter the data. Also note that I used a unique link ( ^392f97dd62d0 ) to group all the transactions for a particular vesting event. You could also use tags if you prefer.","title":"Vesting"},{"location":"stock_vesting_in_beancount.html#conversion-to-actual-stock","text":"Now we\u2019re ready to convert the remaining cash to stock units. This happens in the brokerage firm and you should see the new shares in your brokerage account. The brokerage will typically issue a \u201cstock release report\u201d statement for each vesting event for each award, with the details necessary to make the conversion, namely, the actual number of shares converted from cash and the cost basis (the FMV on the vesting day): 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST The first two postings deposit the shares and subtract the dollar value from the limbo account where the vesting transaction left the remaining cash. You should make sure to use the specific FMV price provided by the statement and not an approximate price, because this has tax consequences later on. Note that you cannot buy fractional shares, so the cost of the rounded amount of shares (18) will leave some remaining cash in the limbo account. The last two postings deduct from the balance of unvested shares and I \u201creceive\u201d an expenses; that expenses account basically counts how many shares were vested over a particular time period. Here again you will probably have one of these conversions for each stock grant you have. I enter them separately, so that one statement matches one transaction. This is a good rule to follow.","title":"Conversion to Actual Stock"},{"location":"stock_vesting_in_beancount.html#refund-for-fractions","text":"After all the conversion events have moved cash out of the limbo account, it is left the fractional remainders from all the conversions. In my case, this remainder is refunded by Hooli 3-4 weeks after vesting as a single separate pay stub that includes all the remainders (it even lists each of them separately). I enter this as a transaction as well: 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.31 USD Assets:US:Hooli:RSURefund -2.88 USD ; (For second award in example) Assets:US:BofA:Checking 97.19 USD After the fractions have been paid the limbo account should be empty. I verify this claim using a balance assertion: 2015-06-14 balance Assets:US:Hooli:RSURefund 0 USD This provides me with some sense that the numbers are right.","title":"Refund for Fractions"},{"location":"stock_vesting_in_beancount.html#organizing-your-input","text":"I like to put all the vesting events together in my input file; this makes them much easier to update and reconcile, especially with multiple awards. For example, with two awards I would have multiple chunks of transactions like this, separated with 4-5 empty lines to delineate them: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Assets:US:Hooli:RSURefund 2458.97 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD 2015-05-27 * \"Vesting Event - C123456 - HOOL\" #award-C123456 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -1970.55 USD Assets:US:Hooli:RSURefund 1053.84 USD Expenses:Taxes:TY2015:US:Medicare 28.58 USD Expenses:Taxes:TY2015:US:Federal 492.63 USD Expenses:Taxes:TY2015:US:CityNYC 83.75 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 189.57 USD Expenses:Taxes:TY2015:US:SocSec 122.18 USD 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 9 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:C123456 -15 HOOL.UNVEST Expenses:Hooli:Vested 15 HOOL.UNVEST 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.30 USD Assets:US:Hooli:RSURefund -2.88 USD Assets:US:BofA:Checking 97.18 USD 2015-02-14 balance Assets:US:Hooli:RSURefund 0 USD","title":"Organizing your Input"},{"location":"stock_vesting_in_beancount.html#unvested-shares","text":"","title":"Unvested Shares"},{"location":"stock_vesting_in_beancount.html#asserting-unvested-balances","text":"Finally, you may occasionally want to assert the number of unvested shares. I like to do this semi-annually, for example. The brokerage company that handles the RSUs for Hooli should be able to list how many unvested shares of each award remain, so it\u2019s as simple as looking it up on a website: 2015-06-04 balance Assets:US:Hooli:Unvested:S0012345 1645 HOOL.UNVEST 2015-06-04 balance Assets:US:Hooli:Unvested:C123456 705 HOOL.UNVEST","title":"Asserting Unvested Balances"},{"location":"stock_vesting_in_beancount.html#pricing-unvested-shares","text":"You can also put a price on the unvested shares in order to estimate the unvested dollar amount. You should use a fictional currency for this, because we want to avoid a situation where a balance sheet is produced that includes these unvested assets as regular dollars: 2015-06-02 price HOOL.UNVEST 132.4300 USD.UNVEST At the time of this writing, the bean-web interface does not convert the units if they are not held at cost, but using the SQL query interface or writing a custom script you should be able to produce those numbers: $ bean-query examples/vesting/vesting.beancount beancount> select account, sum(convert(position, 'USD.UNVEST')) as unvested where account ~ 'Unvested' group by account; account unvested --------------------------------- ---------------------- Assets:US:Hooli:Unvested:S0012345 217847.3500 USD.UNVEST Assets:US:Hooli:Unvested:C123456 93363.1500 USD.UNVEST","title":"Pricing Unvested Shares"},{"location":"stock_vesting_in_beancount.html#selling-vested-stock","text":"After each vesting event, the stock is left in your brokerage account. Selling this stock proceeds just as in any other trading transaction (see Trading with Beancount for full details). For example, selling the shares from the example would look something like this: 2015-09-10 * \"Selling shares\" Assets:US:Schwab:HOOL -26 HOOL {131.3700 USD} @ 138.23 USD Assets:US:Schwab:Cash 3593.98 USD Income:US:Schwab:Gains Here you can see why it matters that the cost basis you used on the conversion event is the correct one: You will have to pay taxes on the difference (in Income:US:Schwab:Gains ). In this example the taxable difference is (138.23 - 131.37) dollars per share. I like to keep all the brokerage transactions in a separate section of my document, where other transactions related to the brokerage occur, such as fees, dividends and transfers.","title":"Selling Vested Stock"},{"location":"stock_vesting_in_beancount.html#conclusion","text":"This is a simple example that is modeled after how technology companies deal with this type of compensation. It is by no means comprehensive, and some of the details will necessarily vary in your situation. In particular, it does not explain how to deal with options (ISOs). My hope is that there is enough meat in this document to allow you to extrapolate and adapt to your particular situation. If you get stuck, please reach out on the mailing-list .","title":"Conclusion"},{"location":"the_double_entry_counting_method.html","text":"The Double-Entry Counting Method \uf0c1 Martin Blais, December 2016 http://furius.ca/beancount/doc/double-entry Introduction Basics of Double-Entry Bookkeeping Statements Single-Entry Bookkeeping Double-Entry Bookkeeping Many Accounts Multiple Postings Types of Accounts Trial Balance Income Statement Clearing Income Equity Balance Sheet Summarizing Period Reporting Chart of Accounts Country-Institution Convention Credits & Debits Accounting Equations Plain-Text Accounting The Table Perspective Introduction \uf0c1 This document is a gentle introduction to the double-entry counting method, as written from the perspective of a computer scientist. It is an attempt to explain basic bookkeeping using as simple an approach as possible, doing away with some of the idiosyncrasies normally involved in accounting. It is also representative of how Beancount works, and it should be useful to all users of plain-text accounting . Note that I am not an accountant, and in the process of writing this document I may have used terminology that is slightly different or unusual to that which is taught in perhaps more traditional training in accounting. I granted myself license to create something new and perhaps even unusual in order to explain those ideas as simply and clearly as possible to someone unfamiliar with them. I believe that the method of double-entry counting should be taught to everyone at the high school level everywhere as it is a tremendously useful organizational skill, and I hope that this text can help spread its knowledge beyond professional circles. Basics of Double-Entry Bookkeeping \uf0c1 The double-entry system is just a simple method of counting , with some simple rules. Let\u2019s begin by defining the notion of an account . An account is something that can contain things, like a bag. It is used to count things, to accumulate things. Let\u2019s draw a horizontal arrow to visually represent the evolving contents of an account over time: On the left, we have the past, and to the right, increasing time: the present, the future, etc. For now, let\u2019s assume that accounts can contain only one kind of thing, for example, dollars . All accounts begin with an empty content of zero dollars. We will call the number of units in the account the balance of an account. Note that it represents its contents at a particular point in time. I will draw the balance using a number above the account\u2019s timeline: The contents of accounts can change over time. In order to change the content of an account, we have to add something to it. We will call this addition a posting to an account, and I will draw this change as a circled number on the account\u2019s timeline, for example, adding $100 to the account: Now, we can draw the updated balance of the account after the posting with another little number right after it: The account\u2019s balance, after adding $100, is now $100. We can also remove from the contents of an account. For example, we could remove $25, and the resulting account balance is now $75: Account balances can also become negative , if we remove more dollars than there are in the account. For example, if we remove $200 from this account, the balance now becomes $-125: It\u2019s perfectly fine for accounts to contain a negative balance number. Remember that all we\u2019re doing is counting things. As we will see shortly, some accounts will remain with a negative balance for most of their timeline. Statements \uf0c1 Something worthy of notice is how the timeline notation I\u2019ve written in the previous section is analogous to paper account statements institutions maintain for each client and which you typically receive through the mail: Date Description Amount Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... -25.00 1075.00 2016-10-06 ... -200.00 875.00 Final Balance 875.00 Sometimes the amount column is split into two, one showing the positive amounts and the other the negative ones: Date Description Debit Credit Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... 25.00 1075.00 2016-10-06 ... 200.00 875.00 Final Balance 875.00 Here, \u201cdebit\u201d means \u201cremoved from your account\u201d and \u201ccredit\u201d means \u201cdeposited in your account.\u201d Sometimes the words \u201cwithdrawals\u201d and \u201cdeposits\u201d will be used. It all depends on context: for checking and savings accounts it is usual to have both types of postings, but for a credit card account typically it shows only positive numbers and then the occasional monthly payment so the single column format is used. In any case, the \u201cbalance\u201d column always shows the resulting balance after the amount has been posted to the account. And sometimes the statements are rendered in decreasing order of time. Single-Entry Bookkeeping \uf0c1 In this story, this account belongs to someone. We\u2019ll call this person the owner of the account. The account can be used to represent a real world account, for example, imagine that we use it to represent the content of the owner\u2019s checking account at a bank. So we\u2019re going to label the account by giving it a name, in this case \u201cChecking\u201d: Imagine that at some point, this account has a balance of $1000, like I\u2019ve drawn on the picture. Now, if the owner spends $79 of this account, we would represent it like this: Furthermore, if the expense was for a meal at a restaurant, we could flag the posting with a category to indicate what the change was used for. Let\u2019s say, \u201cRestaurant\u201d, like this: Now, if we have a lot of these, we could write a computer program to accumulate all the changes for each category and calculate the sums for each of them. That would tell us how much we spent in restaurants in total, for example. This is called the single-entry method of accounting. But we\u2019re not going to do it this way; we have a better way. Bear with me for a few more sections. Double-Entry Bookkeeping \uf0c1 An owner may have multiple accounts. I will represent this by drawing many similar account timelines on the same graphic. As before, these are labeled with unique names. Let\u2019s assume that the owner has the same \u201cChecking\u201d account as previously, but now also a \u201c Restaurant \u201d account as well, which can be used to accumulate all food expenses at restaurants. It looks like this: Now, instead of categorizing the posting to a \u201crestaurant category\u201d as we did previously, we could create a matching posting on the \u201cRestaurant\u201d account to record how much we spent for food, with the amount spent ($79): The \u201cRestaurant\u201d account, like all other accounts, also has an accumulated balance, so we can find out how much we spent in \u201cRestaurant\u201d in total. This is entirely symmetrical to counting changes in a checking account. Now, we can associate the two postings together, by creating a kind of \u201cparent\u201d box that refers to both of them. We will call this object a transaction : Notice here that we\u2019ve also associated a description to this transaction: \u201cDinner at Uncle Boons\u201d. A transaction also has a date , and all of its postings are recorded to occur on that date. We call this the transaction date. We can now introduce the fundamental rule of double-entry bookkeeping system: The sum of all the postings of a transaction must equal zero. Remember this, as this is the foundation of the double-entry method, and its most important characteristic. It has important consequences which I will discuss later in this document. In our example, we remove $79 from the \u201cChecking\u201d account and \u201cgive it\u201d to the \u201cRestaurant\u201d account. ($79) + ($-79) = $0. To emphasize this, I could draw a little summation line under the postings of the transaction, like this: Many Accounts \uf0c1 There may be many such transactions, over many different accounts. For example, if the owner of the accounts had a lunch the next day which she paid using a credit card, it could be represented by creating a \u201cCredit Card\u201d account dedicated to tracking the real world credit card balance, and with a corresponding transaction: In this example, the owner spent $35 at a restaurant called \u201cEataly.\u201d The previous balance of the owner\u2019s credit card was $-450; after the expense, the new balance is $-485. For each real world account, the owner can create a bookkeeping account like we did. Also, for each category of expenditure, the owner also creates a bookkeeping account. In this system, there are no limits to how many accounts can be created. Note that the balance in the example is a negative number; this is not an error. Balances for credit card accounts are normally negative: they represent an amount you owe , that the bank is lending you on credit . When your credit card company keeps track of your expenses, they write out your statement from their perspective, as positive numbers. For you, those are amounts you need to eventually pay. But here, in our accounting system, we\u2019re representing numbers from the owner\u2019s point-of-view, and from her perspective, this is money she owes, not something she has. What we have is a meal sitting in our stomach (a positive number of $ of \u201cRestaurant\u201d). Multiple Postings \uf0c1 Finally, transactions may have more than two postings; in fact, they may have any number of postings. The only thing that matters is that the sum of their amounts is zero (from the rule of double-entry bookkeeping above). For example, let\u2019s look at what would happen if the owner gets her salary paid for December: Her gross salary received in this example is recorded as $-2,905 (I\u2019ll explain the sign in a moment). $905 is set aside for taxes. Her \u201cnet\u201d salary of $2,000, the remainder, is deposited in her \u201cChecking\u201d account and the resulting balance of that account is $2,921 (the previous balance of $921 + $2,000 = $2,921). This transaction has three postings: (+2,000) + (-2,905) + (+905) = 0. The double-entry rule is respected. Now, you may ask: Why is her salary recorded as a negative number? The reasoning here is similar to that of the credit card above, though perhaps a bit more subtle. These accounts exist to track all the amounts from the owner\u2019s point-of-view. The owner gives out work, and receives money and taxes in exchange for it (positive amounts). The work given away is denominated in dollar units. It \u201cleaves\u201d the owner (imagine that the owner has potential work stored in her pocket and as she goes into work every day sprinkles that work potential giving it to the company). The owner gave $2,905\u2019s worth of work away. We want to track how much work was given, and it\u2019s done with the \u201cSalary\u201d account. That\u2019s her gross salary. Note also that we\u2019ve simplified this paycheck transaction a bit, for the sake of keeping things simple. A more realistic recording of one\u2019s pay stub would have many more accounts; we would separately account for state and federal tax amounts, as well as social security and medicare payments, deductions, insurance paid through work, and vacation time accrued during the period. But it wouldn\u2019t be much more complicated: the owner would simply translate all the amounts available from her pay stub into a single transaction with more postings. The structure remains similar. Types of Accounts \uf0c1 Let\u2019s now turn our attention to the different types of accounts an owner can have. Balance or Delta. First, the most important distinction between accounts is about whether we care about the balance at a particular point in time, or whether it only makes sense to care about differences over a period of time. For example, the balance of someone\u2019s Checking or Savings accounts is a meaningful number that both the owner and its corresponding bank will care about. Similarly, the total amount owed on someone\u2019s Credit Card account is also meaningful. The same goes with someone\u2019s remaining Mortgage amount to pay on a house. On the other hand, the total amount of Restaurant expenses since the beginning of somebody\u2019s life on earth is not particularly interesting. What we might care about for this account is the amount of Restaurant expenses incurred over a particular period of time . For example, \u201chow much did you spend in restaurants last month?\u201d Or last quarter. Or last year. Similarly, the total amount of gross salary since the beginning of someone\u2019s employment at a company a few years ago is not very important. But we would care about the total amount earned during a tax year, that is, for that time period, because it is used for reporting one\u2019s income to the tax man. Accounts whose balance at a point in time is meaningful are called balance sheet accounts . There are two types of such accounts: \u201c Assets \u201d and \u201c Liabilities .\u201d The other accounts, that is, those whose balance is not particularly meaningful but for which we are interested in calculating changes over a period of time are called income statement accounts . Again, there are two kinds: \u201c Income \u201d and \u201c Expenses .\u201d Normal sign. Secondly, we consider the usual sign of an account\u2019s balance . The great majority of accounts in the double-entry system tend to have a balance with always a positive sign, or always a negative sign (though as we\u2019ve seen previously, it is not impossible that an account\u2019s balance could change signs). This is how we will distinguish between the pairs of accounts mentioned before: For a balance sheet account, Assets normally have positive balances, and Liabilities normally have negative balances. For income statement accounts, Expenses normally have a positive balance, and Income accounts normally have a negative balance. This situation is summarized in the following table: Balance: Positive (+) Balance: Negative (-) Balance matters at a point in time (Balance Sheet) Assets Liabilities Change in balance matters over a period of time (Income Statement) Expenses Income Let\u2019s discuss each type of account and provide some examples, so that it doesn\u2019t remain too abstract. Assets. (+) Asset accounts represent something the owner has . A canonical example is banking accounts. Another one is a \u201ccash\u201d account, which counts how much money is in your wallet. Investments are also assets (their units aren\u2019t dollars in this case, but rather some number of shares of some mutual fund or stock). Finally, if you own a home, the home itself is considered an asset (and its market value fluctuates over time). Liabilities. (-) A liability account represents something the owner owes . The most common example is a credit card. Again, the statement provided by your bank will show positive numbers, but from your own perspective, they are negative numbers. A loan is also a liability account. For example, if you take out a mortgage on a home, this is money you owe, and will be tracked by an account with a negative amount. As you pay off the mortgage every month the negative number goes up, that is, its absolute value gets smaller and smaller over time (e.g., -120,000 -> -117,345). Expenses. (+) An expense account represents something you\u2019ve received , perhaps by exchanging something else to purchase it. This type of account will seem pretty natural: food, drinks, clothing, rent, flights, hotels and most other categories of things you typically spend your disposable income on. However, taxes are also typically tracked by an expense account: when you receive some salary income, the amount of taxes withheld at the source is recorded immediately as an expense. Think of it as paying for government services you receive throughout the year. Income. (-) An income account is used to count something you\u2019ve given away in order to receive something else (typically assets or expenses). For most people with jobs, that is the value of their time (a salary income). Specifically, here we\u2019re talking about the gross income. For example, if you\u2019re earning a salary of $120,000/year, that number is $120,000, not whatever amount remains after paying for taxes. Other types of income includes dividends received from investments, or interest paid from bonds held. There are also a number of oddball things received you might record as income, such the value of rewards received, e.g., cash back from a credit card, or monetary gifts from someone. In Beancount, all account names, without exception, must be associated to one of the types of accounts described previously. Since the type of an account never changes during its lifetime, we will make its type a part of an account\u2019s name, as a prefix , by convention. For example, the qualified account name for restaurant will be \u201cExpenses:Restaurant\u201d. For the bank checking account, the qualified account name will be \u201cAssets:Checking\u201d. Other than that, you can select any name you like for your accounts. You can create as many accounts as you like, and as we will see later, you can organize them in a hierarchy. As of the writing of this document, I\u2019m using more than 700 accounts to track my personal affairs. Let us now revisit our example and add some more accounts: And let\u2019s imagine there are more transactions: \u2026 and even more of them: Finally, we can label each of those accounts with one of the four types of accounts by prepending the type to their account names: A realistic book from someone tracking all of their personal affairs might easily contain thousands of transactions per year. But the principles remain simple and they remain the same: postings are applied to accounts over time, and must be parented to a transaction, and within this transaction the sum of all the postings is zero. When you do bookkeeping for a set of accounts, you are essentially describing all the postings that happen on all the accounts over time, subject to the constraint of the rule. You are creating a database of those postings in a book . You are \u201ckeeping the book,\u201d that is, traditionally, the book which contains all those transactions. Some people call this \u201cmaintaining a journal.\u201d We will now turn our attention to obtaining useful information from this data, summarizing information from the book. Trial Balance \uf0c1 Take our last example: we can easily reorder all the accounts such that all the Asset accounts appear together at the top, then all the Liabilities accounts, then Income, and finally Expenses accounts. We are simply changing the order without modifying the structure of transactions, in order to group each type of accounts together: We\u2019ve reordered the accounts with Assets accounts grouped at the top, then Liabilities, then some Equity accounts (which we have just introduced, more about them is discussed later), then Income and finally Expenses at the bottom. If we sum up the postings on all of the accounts and render just the account name and its final balance on the right, we obtain a report we call the \u201ctrial balance.\u201d This simply reflects the balance of each account at a particular point in time. And because each of the accounts began with a zero balance, and each transaction has itself a zero balance, we know that the sum of all those balances must equal zero. 1 This is a consequence of our constraining that each of the postings be part of a transaction, and that each transaction have postings that balance each other out. Income Statement \uf0c1 One kind of common information that is useful to extract from the list of transactions is a summary of changes in income statement accounts during a particular period of time. This tells us how much money was earned and spent during this period, and the difference tells us how much profit (or loss) was incurred. We call this the \u201cnet income.\u201d In order to generate this summary, we simply turn our attention to the balances of the accounts of types Income and Expenses, summing up just the transactions for a particular period, and we draw the Income balances on the left, and Expenses balances on the right: It is important to take note of the signs here: Income numbers are negative, and Expenses numbers are positive. So if you earned more than you spent (a good outcome), the final sum of Income + Expenses balances will be a negative number. Like any other income, a net income that has a negative number means that there is a corresponding amount of Assets and/or Liabilities with positive numbers (this is good for you). An Income Statement tells us what changed during a particular period of time. Companies typically report this information quarterly to investors and perhaps the public (if they are a publicly traded company) in order to share how much profit they were able to make. Individuals typically report this information on their annual tax returns. Clearing Income \uf0c1 Notice how in the income statement only the transactions within a particular interval of time are summed up. This allows one, for instance, to compute the sum of all income earned during a year. If we were to sum up all of the transactions of this account since its inception we would obtain the total amount of income earned since the account was created. A better way to achieve the same thing is to zero out the balances of the Income and Expenses accounts. Beancount calls this basic transformation \u201cclearing 2 .\u201d It is carried out by: Computing the balances of those accounts from the beginning of time to the start of the reporting period. For example, if you created your accounts in year 2000 and you wanted to generate an income statement for year 2016, you would sum up the balances from 2000 to Jan 1, 2016. Inserting transactions to empty those balances and transfer them to some other account that isn\u2019t Income nor Expenses. For instance, if the restaurant expense account for those 16 years amounts to $85,321 on Jan 1, 2016, it would insert a transaction of $-85,321 to restaurants and $+85,321 to \u201cprevious earnings\u201d. The transactions would be dated Jan 1, 2016. Including this transaction, the sum of that account would zero on that date. This is what we want. Those transactions inserted for all income statement accounts are pictured in green below. Now summing the entire set of transactions through the end of the ledger would yield only the changes during year 2016 because the balances were zero on that date: This is the semantics of the \u201cCLEAR\u201d operation of the bean-query shell. (Note that another way to achieve the same thing for income statement accounts would be to segregate and count amounts only for the transactions after the clearing date; however, jointly reporting on income statement accounts and balance sheet accounts would have incorrect balances for the balance sheet accounts.) Equity \uf0c1 The account that receives those previously accumulated incomes is called \u201cPrevious Earnings\u201d. It lives in a fifth and final type of accounts: Equity . We did not talk about this type of accounts earlier because they are most often only used to transfer amounts to build up reports, and the owner usually doesn\u2019t post changes to those types of accounts; the software does that automatically, e.g., when clearing net income. The account type \u201cequity\u201d is used for accounts that hold a summary of the net income implied by all the past activity. The point is that if we now list together the Assets, Liabilities and Equity accounts, because the Income and Expenses accounts have been zero\u2019ed out, the sum total of all these balances should equal exactly zero. And summing up all the Equity accounts clearly tells us what\u2019s our stake in the entity, in other words, if you used the assets to pay off all the liabilities, how much is left in the business\u2026 how much it\u2019s worth. Note that the normal sign of the Equity accounts is negative . There is no particular meaning to that, just that they are used to counterbalance Assets and Liabilities and if the owner has any value, that number should be negative. (A negative Equity means some positive net worth.) There are a few different Equity accounts in use in Beancount: Previous Earnings or Retained Earnings. An account used to hold the sum total of Income & Expenses balances from the beginning of time until the beginning of a reporting period. This is the account we were referring to in the previous section. Current Earnings , also called Net Income. An account used to contain the sum of Income & Expenses balances incurred during the reporting period. They are filled in by \u201cclearing\u201d the Income & Expenses accounts at the end of the reporting period. Opening Balances. An equity account used to counterbalance deposits used to initialize accounts. This type of account is used when we truncate the past history of transactions, but we also need to ensure that an account\u2019s balance begins its history with a particular amount. Once again: you don\u2019t need to define nor use these accounts yourself, as these are created for the purpose of summarizing transactions. Generally, the accounts are filled in by the clearing process described above, or filled in by Pad directives to \u201copening balances\u201d equity accounts, to account for summarized balances from the past. They are created and filled in automatically by the software. We\u2019ll see how these get used in the following sections. Balance Sheet \uf0c1 Another kind of summary is a listing of the owner\u2019s assets and debts, for each of the accounts. This answers the question: \u201c Where\u2019s the money? \u201d In theory, we could just restrict our focus to the Assets and Liabilities accounts and draw those up in a report: However, in practice, there is another closely related question that comes up and which is usually answered at the same time: \u201c Once all debts are paid off, how much are we left with? \u201d This is called the net worth . If the Income & Expenses accounts have been cleared to zero and all their balances have been transferred to Equity accounts, the net worth should be equal to the sum of all the Equity accounts. So in building up the Balance Sheet, it it customary to clear the net income and then display the balances of the Equity accounts. The report looks like this: Note that the balance sheet can be drawn for any point in time , simply by truncating the list of transactions following a particular date. A balance sheet displays a snapshot of balances at one date; an income statement displays the difference of those balances between two dates. Summarizing \uf0c1 It is useful to summarize a history of past transactions into a single equivalent deposit. For example, if we\u2019re interested in transactions for year 2016 for an account which has a balance of $450 on Jan 1, 2016, we can delete all the previous transactions and replace them with a single one that deposits $450 on Dec 31, 2015 and that takes it from somewhere else. That somewhere else will be the Equity account Opening Balances . First, we can do this for all Assets and Liabilities accounts (see transactions in blue): Then we delete all the transactions that precede the opening date, to obtain a truncated list of transactions: This is a useful operation when we\u2019re focused on the transactions for a particular interval of time. (This is a bit of an implementation detail: these operations are related to how Beancount is designed. Instead of making all the reporting operations with parameters, all of its reporting routines are simplified and instead operate on the entire stream of transactions; in this way, we convert the list of transactions to include only the data we want to report on. In this case, summarization is just a transformation which accepts the full set of transactions and returns an equivalent truncated stream. Then, from this stream, a journal can be produced that excludes the transactions from the past. From a program design perspective, this is appealing because the only state of the program is a stream of transactions, and it is never modified directly. It\u2019s simple and robust.) Period Reporting \uf0c1 Now we know we can produce a statement of changes over a period of time, by \u201cclearing\u201d and looking at just the Income & Expenses accounts (the Income Statement). We also know we can clear to produce a snapshot of Assets, Liabilities & Equity at any point in time (the Balance Sheet). More generally, we\u2019re interested in inspecting a particular period of time. That implies an income statement, but also two balance sheet statements: the balance sheet at the beginning of the period, and the balance sheet at the end of the period. In order to do this, we apply the following transformations: Open. We first clear net income at the beginning of the period, to move all previous income balances to the Equity Previous Earnings account. We then summarize up to the beginning of the period. We call the combination of clearing + summarizing: \u201cOpening.\u201d Close. We also truncate all the transactions following the end of the reporting period. We call this operation \u201cClosing.\u201d These are the meaning of the \u201cOPEN\u201d and \u201cCLOSE\u201d operations of the bean-query shell 3 . The resulting set of transactions should look like this. \u201cClosing\u201d involves two steps. First, we remove all transactions following the closing date: We can process this stream of transactions to produce an Income Statement for the period. Then we clear again at the end date of the desired report, but this time we clear the net income to \u201cEquity:Earnings:Current\u201d: From these transactions, we produce the Balance Sheet at the end of the period. This sums up the operations involved in preparing the streams of transactions to produce reports with Beancount, as well as a basic introduction to those types of reports. Chart of Accounts \uf0c1 New users are often wondering how much detail they should use in their account names. For example, should one include the payee in the account name itself, such as in these examples? Expenses:Phone:Mobile:VerizonWireless Assets:AccountsReceivable:Clients:AcmeInc Or should one use simpler names like the following, relying instead on the \u201cpayee\u201d, \u201ctags\u201d, or perhaps some other metadata in order to group the postings? Expenses:Phone Assets:AccountsReceivable The answer is that it depends on you . This is an arbitrary choice to make. It\u2019s a matter of taste. Personally I like to abuse the account names a bit and create long descriptive ones, other people prefer to keep them simple and use tags to group their postings. Sometimes one doesn\u2019t even need to filter subgroups of postings. There\u2019s no right answer, it depends on what you\u2019d like to do. One consideration to keep in mind is that account names implicitly define a hierarchy. The \u201c:\u201d separator is interpreted by some reporting code to create an in-memory tree and can allow you to collapse a node\u2019s children subaccounts and compute aggregates on the parent. Think of this as an additional way to group postings. Country-Institution Convention \uf0c1 One convention I\u2019ve come up with that works well for my assets, liabilities and income accounts is to root the tree with a code for the country the account lives in, followed by a short string for the institution it corresponds to. Underneath that, a unique name for the particular account in that institution. Like this: : : : For example, a checking account could be chosen to be \u201c Assets:US:BofA:Checking \u201d, where \u201cBofA\u201d stands for \u201cBank of America.\u201d A credit card account could include the name of the particular type of card as the account name, like \u201c Liabilities:US:Amex:Platinum \u201d, which can be useful if you have multiple cards. I\u2019ve found it doesn\u2019t make sense for me to use this scheme for expense accounts, since those tend to represent generic categories. For those, it seems to make more sense to group them by category, as in using \u201c Expenses:Food:Restaurant \u201d instead of just \u201c Expenses:Restaurant \u201d. In any case, Beancount doesn\u2019t enforce anything other than the root accounts; this is just a suggestion and this convention is not coded anywhere in the software. You have great freedom to experiment, and you can easily change all the names later by processing the text file. See the Cookbook for more practical guidance. Credits & Debits \uf0c1 At this point, we haven\u2019t discussed the concepts of \u201ccredits\u201d and \u201cdebits.\u201d This is on purpose: Beancount largely does away with these concepts because it makes everything else simpler. I believe that it is simpler to just learn that the signs of Income, Liabilities and Equity accounts are normally negative and to treat all accounts the same way than to deal with the debits and credits terminology and to treat different account categories differently. In any case, this section explains what these are. As I have pointed out in previous sections, we consider \u201cIncome\u201d, \u201cLiabilities\u201d and \u201cEquity\u201d accounts to normally have a negative balance. This may sound odd; after all, nobody thinks of their gross salary as a negative amount, and certainly your credit-card bill or mortgage loan statements report positive numbers. This is because in our double-entry accounting system we consider all accounts to be held from the perspective of the owner of the account . We use signs consistent from this perspective, because it makes all operations on account contents straightforward: they\u2019re all just simple additions and all the accounts are treated the same. In contrast, accountants traditionally keep all the balances of their accounts as positive numbers and then handle postings to those accounts differently depending on the account type upon which they are applied. The sign to apply to each account is entirely dictated by its type: Assets and Expenses accounts are debit accounts and Liabilities, Equity and Income accounts are credit accounts and require a sign adjustment. Moreover, posting a positive amount on an account is called \u201cdebiting\u201d and removing from an account is called \u201ccrediting.\u201d See this external document , for example, which nearly makes my head explode, and this recent thread has more detail. This way of handling postings makes everything much more complicated than it needs to be. The problem with this approach is that summing of amounts over the postings of a transaction is not a straightforward sum anymore. For example, let\u2019s say you\u2019re creating a new transaction with postings to two Asset accounts, an Expenses account and an Income account and the system tells you there is a $9.95 imbalance error somewhere. You\u2019re staring at the entry intently; which of the postings is too small? Or is one of the postings too large? Also, maybe a new posting needs to be added, but is it to a debit account or to a credit account? The mental gymnastics required to do this are taxing. Some double-entry accounting software tries to deal with this by creating separate columns for debits and credits and allowing the user enter an amount only in the column that corresponds to each posting account\u2019s type. This helps visually, but why not just use signs instead? Moreover, when you look at the accounting equations, you have to consider their signs as well. This makes it awkward to do transformations on them and make what is essentially a simple summation over postings into a convoluted mess that is difficult to understand. In plain-text accounting, we would rather just do away with this inconvenient baggage. We just use additions everywhere and learn to keep in mind that Liabilities, Equity and Income accounts normally have a negative balance. While this is unconventional, it\u2019s much easier to grok. And If there is a need to view a conventional report with positive numbers only, we will be able to trigger that in reporting code 4 , inverting the signs just to render them in the output. Save yourself some pain: Flush your brain from the \"debit\" and \"credit\" terminology. Accounting Equations \uf0c1 In light of the previous sections, we can easily express the accounting equations in signed terms. If, A = the sum of all Assets postings L = the sum of all Liabilities postings X = the sum of all Expenses postings I = the sum of all Income postings E = the sum of all Equity postings We can say that: A + L + E + X + I = 0 This follows from the fact that sum(all postings) = 0 Which follows from the fact that each transaction is guaranteed to sum up to zero (which is enforced by Beancount): for all transactions t, sum(postings of t) = 0 Moreover, the sum of postings from Income and Expenses is the Net Income (NI): NI = X + I If we adjust the equity to reflect the total Net Income effect by clearing the income to the Equity retained earnings account, we get an updated Equity value (E\u2019): E\u2019 = E + NI = E + X + I And we have a simplified accounting equation: A + L + E\u2019 = 0 If we were to adjust the signs for credits and debits (see previous section) and have sums that are all positive number, this becomes the familiar accounting equation: Assets - Liabilities = Equity As you can see, it\u2019s much easier to just always add up the numbers. Plain-Text Accounting \uf0c1 Ok, so now we understand the method and what it can do for us, at least in theory. The purpose of a double-entry bookkeeping system is to allow you to replicate the transactions that occur in various real world accounts into a single, unified system, in a common representation, and to extract various views and reports from this data. Let us now turn our attention to how we record this data in practice. This document talks about Beancount, whose purpose is \u201cdouble-entry bookkeeping using text files.\u201d Beancount implements a parser for a simple syntax that allows you to record transactions and postings. The syntax for an example transaction looks something like this: 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants You write many of declarations like these in a file, and Beancount will read it and create the corresponding data structures in memory. Verification. After parsing the transactions, Beancount also verifies the rule of the double-entry method: it checks that the sum of the postings on all your transactions is zero. If you make a mistake and record a transaction with a non-zero balance, an error will be displayed. Balance Assertions. Beancount allows you to replicate balances declared from external accounts, for example, a balance written on a monthly statement. It processes those and checks that the balances resulting from your input transactions match those declared balances. This helps you detect and find mistakes easily. Plugins. Beancount allows you to build programs which can automate and/or process the streams of transactions in your input files. You can build custom functionality by writing code which directly processes the transaction stream. Querying & Reporting. It provides tools to then process this stream of transactions to produce the kinds of reports we discussed earlier in this document. There are a few more details, for example, Beancount allows you to track cost basis and make currency conversions, but that\u2019s the essence of it. The Table Perspective \uf0c1 Almost always, questions asked by users on the mailing-list about how to calculate or track some value or other can be resolved easily simply by thinking of the data as a long list of rows, some of which need to be filtered and aggregated. If you consider that all that we\u2019re doing in the end is deriving \u201csums\u201d of these postings, and that the attributes of transactions and postings are what allows us to filter subsets of postings, it always becomes very simple. In almost all the cases, the answer is to find some way to disambiguate postings to select them, e.g. by account name, by attaching some tag, by using some metadata, etc. It can be illuminating to consider how this data can be represented as a table. Imagine that you have two tables: a table containing the fields of each Transaction such as date and description, and a table for the fields of each Posting, such as account, amount and currency, as well as a reference to its parent transaction. The simplest way to represent the data is to join those two tables, replicating values of the parent transaction across each of the postings. For example, this Beancount input: 2016-12-04 * \"Christmas gift\" Liabilities:CreditCard -153.45 USD Expenses:Gifts 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants 2016-12-07 * \"Pouring Ribbons\" \"Drinks with friends\" Assets:Cash -25.00 USD Expenses:Tips 4.00 USD Expenses:Alcohol could be rendered as a table like this: Date Fl Payee Narration Account Number Ccy 2016-12-04 * Christmas gift Liabilities:CreditCard -153.45 USD 2016-12-04 * Christmas gift Expenses:Gifts 153.45 USD 2016-12-06 * Biang! Dinner Liabilities:CreditCard -47.23 USD 2016-12-06 * Biang! Dinner Expenses:Restaurants 47.23 USD 2016-12-07 * Pouring Ribbons Drinks with friends Assets:Cash -25.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Tips 4.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Alcohol 21.00 USD Notice how the values of Transaction fields are replicated for each posting. This is exactly like a regular database join operation. The posting fields begin at column \u201cAccount.\u201d (Also note that this example table is simplified; in practice there are many more fields.) If you had a joined table just like this you could filter it and sum up amounts for arbitrary groups of postings. This is exactly what the bean-query tool allows you to do: You can run an SQL query on the data equivalent to this in-memory table and list values like this: SELECT date, payee, number WHERE account = \"Liabilities:CreditCard\"; Or sum up positions like this: SELECT account, sum(position) GROUP BY account; This simple last command generates the trial balance report. Note that the table representation does not inherently constrain the postings to sum to zero. If your selection criteria for the rows (in the WHERE clause) always selects all the postings for each of the matching transactions, you are ensured that the final sum of all the postings is zero. If not, the sum may be anything else. Just something to keep in mind. If you\u2019re familiar with SQL databases, you might ask why Beancount doesn\u2019t simply process its data in order to fill up an existing database system, so that the user could then use those database\u2019s tools. There are two main reasons for this: Reporting Operations. In order to generate income statements and balance sheets, the list of transactions needs to be preprocessed using the clear, open and close operations described previously. These operations are not trivial to implement in database queries and are dependent on just the report and ideally don\u2019t need to modify the input data. We\u2019d have to load up the posting data into memory and then run some code. We\u2019re already doing that by parsing the input file; the database step would be superfluous. Aggregating Positions. Though we haven\u2019t discussed it in this document so far, the contents of accounts may contain different types of commodities, as well as positions with an attached cost basis. The way that these positions are aggregated together requires the implementation of a custom data type because it obeys some rules about how positions are able to cancel each other out (see How Inventories Work for details). It would be very difficult to build these operations with an SQL database beyond the context of using just a single currency and ignoring cost basis. This is why Beancount provides a custom tool to directly process and query its data: It provides its own implementation of an SQL client that lets you specify open and close dates and leverages a custom \u201cInventory\u201d data structure to create sums of the positions of postings. This tools supports columns of Beancount\u2019s core types: Amount, Position and Inventory objects. (In any case, if you\u2019re not convinced, Beancount provides a tool to export its contents to a regular SQL database system. Feel free to experiment with it if you like, knock yourself out.) Please don\u2019t pay attention to the numbers in these large figures, they were randomly generated and don\u2019t reflect this. We\u2019re just interested in showing the structure, in these figures. \u21a9 Note that this is unrelated to the term \u201cclearing transactions\u201d which means acknowledging or marking that some transactions have been eyeballed by the bookkeeper and checked for correctness. \u21a9 Note that operations have nothing to do with the Open and Close directives Beancount provides. \u21a9 This is not provided yet in Beancount, but would be trivial to implement. All we'd need to do is invert the signs of balances from Liabilities, Income and Equity accounts. It's on the roadmap to provide this eventually. \u21a9","title":"The Double Entry Counting Method"},{"location":"the_double_entry_counting_method.html#the-double-entry-counting-method","text":"Martin Blais, December 2016 http://furius.ca/beancount/doc/double-entry Introduction Basics of Double-Entry Bookkeeping Statements Single-Entry Bookkeeping Double-Entry Bookkeeping Many Accounts Multiple Postings Types of Accounts Trial Balance Income Statement Clearing Income Equity Balance Sheet Summarizing Period Reporting Chart of Accounts Country-Institution Convention Credits & Debits Accounting Equations Plain-Text Accounting The Table Perspective","title":"The Double-Entry Counting Method"},{"location":"the_double_entry_counting_method.html#introduction","text":"This document is a gentle introduction to the double-entry counting method, as written from the perspective of a computer scientist. It is an attempt to explain basic bookkeeping using as simple an approach as possible, doing away with some of the idiosyncrasies normally involved in accounting. It is also representative of how Beancount works, and it should be useful to all users of plain-text accounting . Note that I am not an accountant, and in the process of writing this document I may have used terminology that is slightly different or unusual to that which is taught in perhaps more traditional training in accounting. I granted myself license to create something new and perhaps even unusual in order to explain those ideas as simply and clearly as possible to someone unfamiliar with them. I believe that the method of double-entry counting should be taught to everyone at the high school level everywhere as it is a tremendously useful organizational skill, and I hope that this text can help spread its knowledge beyond professional circles.","title":"Introduction"},{"location":"the_double_entry_counting_method.html#basics-of-double-entry-bookkeeping","text":"The double-entry system is just a simple method of counting , with some simple rules. Let\u2019s begin by defining the notion of an account . An account is something that can contain things, like a bag. It is used to count things, to accumulate things. Let\u2019s draw a horizontal arrow to visually represent the evolving contents of an account over time: On the left, we have the past, and to the right, increasing time: the present, the future, etc. For now, let\u2019s assume that accounts can contain only one kind of thing, for example, dollars . All accounts begin with an empty content of zero dollars. We will call the number of units in the account the balance of an account. Note that it represents its contents at a particular point in time. I will draw the balance using a number above the account\u2019s timeline: The contents of accounts can change over time. In order to change the content of an account, we have to add something to it. We will call this addition a posting to an account, and I will draw this change as a circled number on the account\u2019s timeline, for example, adding $100 to the account: Now, we can draw the updated balance of the account after the posting with another little number right after it: The account\u2019s balance, after adding $100, is now $100. We can also remove from the contents of an account. For example, we could remove $25, and the resulting account balance is now $75: Account balances can also become negative , if we remove more dollars than there are in the account. For example, if we remove $200 from this account, the balance now becomes $-125: It\u2019s perfectly fine for accounts to contain a negative balance number. Remember that all we\u2019re doing is counting things. As we will see shortly, some accounts will remain with a negative balance for most of their timeline.","title":"Basics of Double-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#statements","text":"Something worthy of notice is how the timeline notation I\u2019ve written in the previous section is analogous to paper account statements institutions maintain for each client and which you typically receive through the mail: Date Description Amount Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... -25.00 1075.00 2016-10-06 ... -200.00 875.00 Final Balance 875.00 Sometimes the amount column is split into two, one showing the positive amounts and the other the negative ones: Date Description Debit Credit Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... 25.00 1075.00 2016-10-06 ... 200.00 875.00 Final Balance 875.00 Here, \u201cdebit\u201d means \u201cremoved from your account\u201d and \u201ccredit\u201d means \u201cdeposited in your account.\u201d Sometimes the words \u201cwithdrawals\u201d and \u201cdeposits\u201d will be used. It all depends on context: for checking and savings accounts it is usual to have both types of postings, but for a credit card account typically it shows only positive numbers and then the occasional monthly payment so the single column format is used. In any case, the \u201cbalance\u201d column always shows the resulting balance after the amount has been posted to the account. And sometimes the statements are rendered in decreasing order of time.","title":"Statements"},{"location":"the_double_entry_counting_method.html#single-entry-bookkeeping","text":"In this story, this account belongs to someone. We\u2019ll call this person the owner of the account. The account can be used to represent a real world account, for example, imagine that we use it to represent the content of the owner\u2019s checking account at a bank. So we\u2019re going to label the account by giving it a name, in this case \u201cChecking\u201d: Imagine that at some point, this account has a balance of $1000, like I\u2019ve drawn on the picture. Now, if the owner spends $79 of this account, we would represent it like this: Furthermore, if the expense was for a meal at a restaurant, we could flag the posting with a category to indicate what the change was used for. Let\u2019s say, \u201cRestaurant\u201d, like this: Now, if we have a lot of these, we could write a computer program to accumulate all the changes for each category and calculate the sums for each of them. That would tell us how much we spent in restaurants in total, for example. This is called the single-entry method of accounting. But we\u2019re not going to do it this way; we have a better way. Bear with me for a few more sections.","title":"Single-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#double-entry-bookkeeping","text":"An owner may have multiple accounts. I will represent this by drawing many similar account timelines on the same graphic. As before, these are labeled with unique names. Let\u2019s assume that the owner has the same \u201cChecking\u201d account as previously, but now also a \u201c Restaurant \u201d account as well, which can be used to accumulate all food expenses at restaurants. It looks like this: Now, instead of categorizing the posting to a \u201crestaurant category\u201d as we did previously, we could create a matching posting on the \u201cRestaurant\u201d account to record how much we spent for food, with the amount spent ($79): The \u201cRestaurant\u201d account, like all other accounts, also has an accumulated balance, so we can find out how much we spent in \u201cRestaurant\u201d in total. This is entirely symmetrical to counting changes in a checking account. Now, we can associate the two postings together, by creating a kind of \u201cparent\u201d box that refers to both of them. We will call this object a transaction : Notice here that we\u2019ve also associated a description to this transaction: \u201cDinner at Uncle Boons\u201d. A transaction also has a date , and all of its postings are recorded to occur on that date. We call this the transaction date. We can now introduce the fundamental rule of double-entry bookkeeping system: The sum of all the postings of a transaction must equal zero. Remember this, as this is the foundation of the double-entry method, and its most important characteristic. It has important consequences which I will discuss later in this document. In our example, we remove $79 from the \u201cChecking\u201d account and \u201cgive it\u201d to the \u201cRestaurant\u201d account. ($79) + ($-79) = $0. To emphasize this, I could draw a little summation line under the postings of the transaction, like this:","title":"Double-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#many-accounts","text":"There may be many such transactions, over many different accounts. For example, if the owner of the accounts had a lunch the next day which she paid using a credit card, it could be represented by creating a \u201cCredit Card\u201d account dedicated to tracking the real world credit card balance, and with a corresponding transaction: In this example, the owner spent $35 at a restaurant called \u201cEataly.\u201d The previous balance of the owner\u2019s credit card was $-450; after the expense, the new balance is $-485. For each real world account, the owner can create a bookkeeping account like we did. Also, for each category of expenditure, the owner also creates a bookkeeping account. In this system, there are no limits to how many accounts can be created. Note that the balance in the example is a negative number; this is not an error. Balances for credit card accounts are normally negative: they represent an amount you owe , that the bank is lending you on credit . When your credit card company keeps track of your expenses, they write out your statement from their perspective, as positive numbers. For you, those are amounts you need to eventually pay. But here, in our accounting system, we\u2019re representing numbers from the owner\u2019s point-of-view, and from her perspective, this is money she owes, not something she has. What we have is a meal sitting in our stomach (a positive number of $ of \u201cRestaurant\u201d).","title":"Many Accounts"},{"location":"the_double_entry_counting_method.html#multiple-postings","text":"Finally, transactions may have more than two postings; in fact, they may have any number of postings. The only thing that matters is that the sum of their amounts is zero (from the rule of double-entry bookkeeping above). For example, let\u2019s look at what would happen if the owner gets her salary paid for December: Her gross salary received in this example is recorded as $-2,905 (I\u2019ll explain the sign in a moment). $905 is set aside for taxes. Her \u201cnet\u201d salary of $2,000, the remainder, is deposited in her \u201cChecking\u201d account and the resulting balance of that account is $2,921 (the previous balance of $921 + $2,000 = $2,921). This transaction has three postings: (+2,000) + (-2,905) + (+905) = 0. The double-entry rule is respected. Now, you may ask: Why is her salary recorded as a negative number? The reasoning here is similar to that of the credit card above, though perhaps a bit more subtle. These accounts exist to track all the amounts from the owner\u2019s point-of-view. The owner gives out work, and receives money and taxes in exchange for it (positive amounts). The work given away is denominated in dollar units. It \u201cleaves\u201d the owner (imagine that the owner has potential work stored in her pocket and as she goes into work every day sprinkles that work potential giving it to the company). The owner gave $2,905\u2019s worth of work away. We want to track how much work was given, and it\u2019s done with the \u201cSalary\u201d account. That\u2019s her gross salary. Note also that we\u2019ve simplified this paycheck transaction a bit, for the sake of keeping things simple. A more realistic recording of one\u2019s pay stub would have many more accounts; we would separately account for state and federal tax amounts, as well as social security and medicare payments, deductions, insurance paid through work, and vacation time accrued during the period. But it wouldn\u2019t be much more complicated: the owner would simply translate all the amounts available from her pay stub into a single transaction with more postings. The structure remains similar.","title":"Multiple Postings"},{"location":"the_double_entry_counting_method.html#types-of-accounts","text":"Let\u2019s now turn our attention to the different types of accounts an owner can have. Balance or Delta. First, the most important distinction between accounts is about whether we care about the balance at a particular point in time, or whether it only makes sense to care about differences over a period of time. For example, the balance of someone\u2019s Checking or Savings accounts is a meaningful number that both the owner and its corresponding bank will care about. Similarly, the total amount owed on someone\u2019s Credit Card account is also meaningful. The same goes with someone\u2019s remaining Mortgage amount to pay on a house. On the other hand, the total amount of Restaurant expenses since the beginning of somebody\u2019s life on earth is not particularly interesting. What we might care about for this account is the amount of Restaurant expenses incurred over a particular period of time . For example, \u201chow much did you spend in restaurants last month?\u201d Or last quarter. Or last year. Similarly, the total amount of gross salary since the beginning of someone\u2019s employment at a company a few years ago is not very important. But we would care about the total amount earned during a tax year, that is, for that time period, because it is used for reporting one\u2019s income to the tax man. Accounts whose balance at a point in time is meaningful are called balance sheet accounts . There are two types of such accounts: \u201c Assets \u201d and \u201c Liabilities .\u201d The other accounts, that is, those whose balance is not particularly meaningful but for which we are interested in calculating changes over a period of time are called income statement accounts . Again, there are two kinds: \u201c Income \u201d and \u201c Expenses .\u201d Normal sign. Secondly, we consider the usual sign of an account\u2019s balance . The great majority of accounts in the double-entry system tend to have a balance with always a positive sign, or always a negative sign (though as we\u2019ve seen previously, it is not impossible that an account\u2019s balance could change signs). This is how we will distinguish between the pairs of accounts mentioned before: For a balance sheet account, Assets normally have positive balances, and Liabilities normally have negative balances. For income statement accounts, Expenses normally have a positive balance, and Income accounts normally have a negative balance. This situation is summarized in the following table: Balance: Positive (+) Balance: Negative (-) Balance matters at a point in time (Balance Sheet) Assets Liabilities Change in balance matters over a period of time (Income Statement) Expenses Income Let\u2019s discuss each type of account and provide some examples, so that it doesn\u2019t remain too abstract. Assets. (+) Asset accounts represent something the owner has . A canonical example is banking accounts. Another one is a \u201ccash\u201d account, which counts how much money is in your wallet. Investments are also assets (their units aren\u2019t dollars in this case, but rather some number of shares of some mutual fund or stock). Finally, if you own a home, the home itself is considered an asset (and its market value fluctuates over time). Liabilities. (-) A liability account represents something the owner owes . The most common example is a credit card. Again, the statement provided by your bank will show positive numbers, but from your own perspective, they are negative numbers. A loan is also a liability account. For example, if you take out a mortgage on a home, this is money you owe, and will be tracked by an account with a negative amount. As you pay off the mortgage every month the negative number goes up, that is, its absolute value gets smaller and smaller over time (e.g., -120,000 -> -117,345). Expenses. (+) An expense account represents something you\u2019ve received , perhaps by exchanging something else to purchase it. This type of account will seem pretty natural: food, drinks, clothing, rent, flights, hotels and most other categories of things you typically spend your disposable income on. However, taxes are also typically tracked by an expense account: when you receive some salary income, the amount of taxes withheld at the source is recorded immediately as an expense. Think of it as paying for government services you receive throughout the year. Income. (-) An income account is used to count something you\u2019ve given away in order to receive something else (typically assets or expenses). For most people with jobs, that is the value of their time (a salary income). Specifically, here we\u2019re talking about the gross income. For example, if you\u2019re earning a salary of $120,000/year, that number is $120,000, not whatever amount remains after paying for taxes. Other types of income includes dividends received from investments, or interest paid from bonds held. There are also a number of oddball things received you might record as income, such the value of rewards received, e.g., cash back from a credit card, or monetary gifts from someone. In Beancount, all account names, without exception, must be associated to one of the types of accounts described previously. Since the type of an account never changes during its lifetime, we will make its type a part of an account\u2019s name, as a prefix , by convention. For example, the qualified account name for restaurant will be \u201cExpenses:Restaurant\u201d. For the bank checking account, the qualified account name will be \u201cAssets:Checking\u201d. Other than that, you can select any name you like for your accounts. You can create as many accounts as you like, and as we will see later, you can organize them in a hierarchy. As of the writing of this document, I\u2019m using more than 700 accounts to track my personal affairs. Let us now revisit our example and add some more accounts: And let\u2019s imagine there are more transactions: \u2026 and even more of them: Finally, we can label each of those accounts with one of the four types of accounts by prepending the type to their account names: A realistic book from someone tracking all of their personal affairs might easily contain thousands of transactions per year. But the principles remain simple and they remain the same: postings are applied to accounts over time, and must be parented to a transaction, and within this transaction the sum of all the postings is zero. When you do bookkeeping for a set of accounts, you are essentially describing all the postings that happen on all the accounts over time, subject to the constraint of the rule. You are creating a database of those postings in a book . You are \u201ckeeping the book,\u201d that is, traditionally, the book which contains all those transactions. Some people call this \u201cmaintaining a journal.\u201d We will now turn our attention to obtaining useful information from this data, summarizing information from the book.","title":"Types of Accounts"},{"location":"the_double_entry_counting_method.html#trial-balance","text":"Take our last example: we can easily reorder all the accounts such that all the Asset accounts appear together at the top, then all the Liabilities accounts, then Income, and finally Expenses accounts. We are simply changing the order without modifying the structure of transactions, in order to group each type of accounts together: We\u2019ve reordered the accounts with Assets accounts grouped at the top, then Liabilities, then some Equity accounts (which we have just introduced, more about them is discussed later), then Income and finally Expenses at the bottom. If we sum up the postings on all of the accounts and render just the account name and its final balance on the right, we obtain a report we call the \u201ctrial balance.\u201d This simply reflects the balance of each account at a particular point in time. And because each of the accounts began with a zero balance, and each transaction has itself a zero balance, we know that the sum of all those balances must equal zero. 1 This is a consequence of our constraining that each of the postings be part of a transaction, and that each transaction have postings that balance each other out.","title":"Trial Balance"},{"location":"the_double_entry_counting_method.html#income-statement","text":"One kind of common information that is useful to extract from the list of transactions is a summary of changes in income statement accounts during a particular period of time. This tells us how much money was earned and spent during this period, and the difference tells us how much profit (or loss) was incurred. We call this the \u201cnet income.\u201d In order to generate this summary, we simply turn our attention to the balances of the accounts of types Income and Expenses, summing up just the transactions for a particular period, and we draw the Income balances on the left, and Expenses balances on the right: It is important to take note of the signs here: Income numbers are negative, and Expenses numbers are positive. So if you earned more than you spent (a good outcome), the final sum of Income + Expenses balances will be a negative number. Like any other income, a net income that has a negative number means that there is a corresponding amount of Assets and/or Liabilities with positive numbers (this is good for you). An Income Statement tells us what changed during a particular period of time. Companies typically report this information quarterly to investors and perhaps the public (if they are a publicly traded company) in order to share how much profit they were able to make. Individuals typically report this information on their annual tax returns.","title":"Income Statement"},{"location":"the_double_entry_counting_method.html#clearing-income","text":"Notice how in the income statement only the transactions within a particular interval of time are summed up. This allows one, for instance, to compute the sum of all income earned during a year. If we were to sum up all of the transactions of this account since its inception we would obtain the total amount of income earned since the account was created. A better way to achieve the same thing is to zero out the balances of the Income and Expenses accounts. Beancount calls this basic transformation \u201cclearing 2 .\u201d It is carried out by: Computing the balances of those accounts from the beginning of time to the start of the reporting period. For example, if you created your accounts in year 2000 and you wanted to generate an income statement for year 2016, you would sum up the balances from 2000 to Jan 1, 2016. Inserting transactions to empty those balances and transfer them to some other account that isn\u2019t Income nor Expenses. For instance, if the restaurant expense account for those 16 years amounts to $85,321 on Jan 1, 2016, it would insert a transaction of $-85,321 to restaurants and $+85,321 to \u201cprevious earnings\u201d. The transactions would be dated Jan 1, 2016. Including this transaction, the sum of that account would zero on that date. This is what we want. Those transactions inserted for all income statement accounts are pictured in green below. Now summing the entire set of transactions through the end of the ledger would yield only the changes during year 2016 because the balances were zero on that date: This is the semantics of the \u201cCLEAR\u201d operation of the bean-query shell. (Note that another way to achieve the same thing for income statement accounts would be to segregate and count amounts only for the transactions after the clearing date; however, jointly reporting on income statement accounts and balance sheet accounts would have incorrect balances for the balance sheet accounts.)","title":"Clearing Income"},{"location":"the_double_entry_counting_method.html#equity","text":"The account that receives those previously accumulated incomes is called \u201cPrevious Earnings\u201d. It lives in a fifth and final type of accounts: Equity . We did not talk about this type of accounts earlier because they are most often only used to transfer amounts to build up reports, and the owner usually doesn\u2019t post changes to those types of accounts; the software does that automatically, e.g., when clearing net income. The account type \u201cequity\u201d is used for accounts that hold a summary of the net income implied by all the past activity. The point is that if we now list together the Assets, Liabilities and Equity accounts, because the Income and Expenses accounts have been zero\u2019ed out, the sum total of all these balances should equal exactly zero. And summing up all the Equity accounts clearly tells us what\u2019s our stake in the entity, in other words, if you used the assets to pay off all the liabilities, how much is left in the business\u2026 how much it\u2019s worth. Note that the normal sign of the Equity accounts is negative . There is no particular meaning to that, just that they are used to counterbalance Assets and Liabilities and if the owner has any value, that number should be negative. (A negative Equity means some positive net worth.) There are a few different Equity accounts in use in Beancount: Previous Earnings or Retained Earnings. An account used to hold the sum total of Income & Expenses balances from the beginning of time until the beginning of a reporting period. This is the account we were referring to in the previous section. Current Earnings , also called Net Income. An account used to contain the sum of Income & Expenses balances incurred during the reporting period. They are filled in by \u201cclearing\u201d the Income & Expenses accounts at the end of the reporting period. Opening Balances. An equity account used to counterbalance deposits used to initialize accounts. This type of account is used when we truncate the past history of transactions, but we also need to ensure that an account\u2019s balance begins its history with a particular amount. Once again: you don\u2019t need to define nor use these accounts yourself, as these are created for the purpose of summarizing transactions. Generally, the accounts are filled in by the clearing process described above, or filled in by Pad directives to \u201copening balances\u201d equity accounts, to account for summarized balances from the past. They are created and filled in automatically by the software. We\u2019ll see how these get used in the following sections.","title":"Equity"},{"location":"the_double_entry_counting_method.html#balance-sheet","text":"Another kind of summary is a listing of the owner\u2019s assets and debts, for each of the accounts. This answers the question: \u201c Where\u2019s the money? \u201d In theory, we could just restrict our focus to the Assets and Liabilities accounts and draw those up in a report: However, in practice, there is another closely related question that comes up and which is usually answered at the same time: \u201c Once all debts are paid off, how much are we left with? \u201d This is called the net worth . If the Income & Expenses accounts have been cleared to zero and all their balances have been transferred to Equity accounts, the net worth should be equal to the sum of all the Equity accounts. So in building up the Balance Sheet, it it customary to clear the net income and then display the balances of the Equity accounts. The report looks like this: Note that the balance sheet can be drawn for any point in time , simply by truncating the list of transactions following a particular date. A balance sheet displays a snapshot of balances at one date; an income statement displays the difference of those balances between two dates.","title":"Balance Sheet"},{"location":"the_double_entry_counting_method.html#summarizing","text":"It is useful to summarize a history of past transactions into a single equivalent deposit. For example, if we\u2019re interested in transactions for year 2016 for an account which has a balance of $450 on Jan 1, 2016, we can delete all the previous transactions and replace them with a single one that deposits $450 on Dec 31, 2015 and that takes it from somewhere else. That somewhere else will be the Equity account Opening Balances . First, we can do this for all Assets and Liabilities accounts (see transactions in blue): Then we delete all the transactions that precede the opening date, to obtain a truncated list of transactions: This is a useful operation when we\u2019re focused on the transactions for a particular interval of time. (This is a bit of an implementation detail: these operations are related to how Beancount is designed. Instead of making all the reporting operations with parameters, all of its reporting routines are simplified and instead operate on the entire stream of transactions; in this way, we convert the list of transactions to include only the data we want to report on. In this case, summarization is just a transformation which accepts the full set of transactions and returns an equivalent truncated stream. Then, from this stream, a journal can be produced that excludes the transactions from the past. From a program design perspective, this is appealing because the only state of the program is a stream of transactions, and it is never modified directly. It\u2019s simple and robust.)","title":"Summarizing"},{"location":"the_double_entry_counting_method.html#period-reporting","text":"Now we know we can produce a statement of changes over a period of time, by \u201cclearing\u201d and looking at just the Income & Expenses accounts (the Income Statement). We also know we can clear to produce a snapshot of Assets, Liabilities & Equity at any point in time (the Balance Sheet). More generally, we\u2019re interested in inspecting a particular period of time. That implies an income statement, but also two balance sheet statements: the balance sheet at the beginning of the period, and the balance sheet at the end of the period. In order to do this, we apply the following transformations: Open. We first clear net income at the beginning of the period, to move all previous income balances to the Equity Previous Earnings account. We then summarize up to the beginning of the period. We call the combination of clearing + summarizing: \u201cOpening.\u201d Close. We also truncate all the transactions following the end of the reporting period. We call this operation \u201cClosing.\u201d These are the meaning of the \u201cOPEN\u201d and \u201cCLOSE\u201d operations of the bean-query shell 3 . The resulting set of transactions should look like this. \u201cClosing\u201d involves two steps. First, we remove all transactions following the closing date: We can process this stream of transactions to produce an Income Statement for the period. Then we clear again at the end date of the desired report, but this time we clear the net income to \u201cEquity:Earnings:Current\u201d: From these transactions, we produce the Balance Sheet at the end of the period. This sums up the operations involved in preparing the streams of transactions to produce reports with Beancount, as well as a basic introduction to those types of reports.","title":"Period Reporting"},{"location":"the_double_entry_counting_method.html#chart-of-accounts","text":"New users are often wondering how much detail they should use in their account names. For example, should one include the payee in the account name itself, such as in these examples? Expenses:Phone:Mobile:VerizonWireless Assets:AccountsReceivable:Clients:AcmeInc Or should one use simpler names like the following, relying instead on the \u201cpayee\u201d, \u201ctags\u201d, or perhaps some other metadata in order to group the postings? Expenses:Phone Assets:AccountsReceivable The answer is that it depends on you . This is an arbitrary choice to make. It\u2019s a matter of taste. Personally I like to abuse the account names a bit and create long descriptive ones, other people prefer to keep them simple and use tags to group their postings. Sometimes one doesn\u2019t even need to filter subgroups of postings. There\u2019s no right answer, it depends on what you\u2019d like to do. One consideration to keep in mind is that account names implicitly define a hierarchy. The \u201c:\u201d separator is interpreted by some reporting code to create an in-memory tree and can allow you to collapse a node\u2019s children subaccounts and compute aggregates on the parent. Think of this as an additional way to group postings.","title":"Chart of Accounts"},{"location":"the_double_entry_counting_method.html#country-institution-convention","text":"One convention I\u2019ve come up with that works well for my assets, liabilities and income accounts is to root the tree with a code for the country the account lives in, followed by a short string for the institution it corresponds to. Underneath that, a unique name for the particular account in that institution. Like this: : : : For example, a checking account could be chosen to be \u201c Assets:US:BofA:Checking \u201d, where \u201cBofA\u201d stands for \u201cBank of America.\u201d A credit card account could include the name of the particular type of card as the account name, like \u201c Liabilities:US:Amex:Platinum \u201d, which can be useful if you have multiple cards. I\u2019ve found it doesn\u2019t make sense for me to use this scheme for expense accounts, since those tend to represent generic categories. For those, it seems to make more sense to group them by category, as in using \u201c Expenses:Food:Restaurant \u201d instead of just \u201c Expenses:Restaurant \u201d. In any case, Beancount doesn\u2019t enforce anything other than the root accounts; this is just a suggestion and this convention is not coded anywhere in the software. You have great freedom to experiment, and you can easily change all the names later by processing the text file. See the Cookbook for more practical guidance.","title":"Country-Institution Convention"},{"location":"the_double_entry_counting_method.html#credits-debits","text":"At this point, we haven\u2019t discussed the concepts of \u201ccredits\u201d and \u201cdebits.\u201d This is on purpose: Beancount largely does away with these concepts because it makes everything else simpler. I believe that it is simpler to just learn that the signs of Income, Liabilities and Equity accounts are normally negative and to treat all accounts the same way than to deal with the debits and credits terminology and to treat different account categories differently. In any case, this section explains what these are. As I have pointed out in previous sections, we consider \u201cIncome\u201d, \u201cLiabilities\u201d and \u201cEquity\u201d accounts to normally have a negative balance. This may sound odd; after all, nobody thinks of their gross salary as a negative amount, and certainly your credit-card bill or mortgage loan statements report positive numbers. This is because in our double-entry accounting system we consider all accounts to be held from the perspective of the owner of the account . We use signs consistent from this perspective, because it makes all operations on account contents straightforward: they\u2019re all just simple additions and all the accounts are treated the same. In contrast, accountants traditionally keep all the balances of their accounts as positive numbers and then handle postings to those accounts differently depending on the account type upon which they are applied. The sign to apply to each account is entirely dictated by its type: Assets and Expenses accounts are debit accounts and Liabilities, Equity and Income accounts are credit accounts and require a sign adjustment. Moreover, posting a positive amount on an account is called \u201cdebiting\u201d and removing from an account is called \u201ccrediting.\u201d See this external document , for example, which nearly makes my head explode, and this recent thread has more detail. This way of handling postings makes everything much more complicated than it needs to be. The problem with this approach is that summing of amounts over the postings of a transaction is not a straightforward sum anymore. For example, let\u2019s say you\u2019re creating a new transaction with postings to two Asset accounts, an Expenses account and an Income account and the system tells you there is a $9.95 imbalance error somewhere. You\u2019re staring at the entry intently; which of the postings is too small? Or is one of the postings too large? Also, maybe a new posting needs to be added, but is it to a debit account or to a credit account? The mental gymnastics required to do this are taxing. Some double-entry accounting software tries to deal with this by creating separate columns for debits and credits and allowing the user enter an amount only in the column that corresponds to each posting account\u2019s type. This helps visually, but why not just use signs instead? Moreover, when you look at the accounting equations, you have to consider their signs as well. This makes it awkward to do transformations on them and make what is essentially a simple summation over postings into a convoluted mess that is difficult to understand. In plain-text accounting, we would rather just do away with this inconvenient baggage. We just use additions everywhere and learn to keep in mind that Liabilities, Equity and Income accounts normally have a negative balance. While this is unconventional, it\u2019s much easier to grok. And If there is a need to view a conventional report with positive numbers only, we will be able to trigger that in reporting code 4 , inverting the signs just to render them in the output. Save yourself some pain: Flush your brain from the \"debit\" and \"credit\" terminology.","title":"Credits & Debits"},{"location":"the_double_entry_counting_method.html#accounting-equations","text":"In light of the previous sections, we can easily express the accounting equations in signed terms. If, A = the sum of all Assets postings L = the sum of all Liabilities postings X = the sum of all Expenses postings I = the sum of all Income postings E = the sum of all Equity postings We can say that: A + L + E + X + I = 0 This follows from the fact that sum(all postings) = 0 Which follows from the fact that each transaction is guaranteed to sum up to zero (which is enforced by Beancount): for all transactions t, sum(postings of t) = 0 Moreover, the sum of postings from Income and Expenses is the Net Income (NI): NI = X + I If we adjust the equity to reflect the total Net Income effect by clearing the income to the Equity retained earnings account, we get an updated Equity value (E\u2019): E\u2019 = E + NI = E + X + I And we have a simplified accounting equation: A + L + E\u2019 = 0 If we were to adjust the signs for credits and debits (see previous section) and have sums that are all positive number, this becomes the familiar accounting equation: Assets - Liabilities = Equity As you can see, it\u2019s much easier to just always add up the numbers.","title":"Accounting Equations"},{"location":"the_double_entry_counting_method.html#plain-text-accounting","text":"Ok, so now we understand the method and what it can do for us, at least in theory. The purpose of a double-entry bookkeeping system is to allow you to replicate the transactions that occur in various real world accounts into a single, unified system, in a common representation, and to extract various views and reports from this data. Let us now turn our attention to how we record this data in practice. This document talks about Beancount, whose purpose is \u201cdouble-entry bookkeeping using text files.\u201d Beancount implements a parser for a simple syntax that allows you to record transactions and postings. The syntax for an example transaction looks something like this: 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants You write many of declarations like these in a file, and Beancount will read it and create the corresponding data structures in memory. Verification. After parsing the transactions, Beancount also verifies the rule of the double-entry method: it checks that the sum of the postings on all your transactions is zero. If you make a mistake and record a transaction with a non-zero balance, an error will be displayed. Balance Assertions. Beancount allows you to replicate balances declared from external accounts, for example, a balance written on a monthly statement. It processes those and checks that the balances resulting from your input transactions match those declared balances. This helps you detect and find mistakes easily. Plugins. Beancount allows you to build programs which can automate and/or process the streams of transactions in your input files. You can build custom functionality by writing code which directly processes the transaction stream. Querying & Reporting. It provides tools to then process this stream of transactions to produce the kinds of reports we discussed earlier in this document. There are a few more details, for example, Beancount allows you to track cost basis and make currency conversions, but that\u2019s the essence of it.","title":"Plain-Text Accounting"},{"location":"the_double_entry_counting_method.html#the-table-perspective","text":"Almost always, questions asked by users on the mailing-list about how to calculate or track some value or other can be resolved easily simply by thinking of the data as a long list of rows, some of which need to be filtered and aggregated. If you consider that all that we\u2019re doing in the end is deriving \u201csums\u201d of these postings, and that the attributes of transactions and postings are what allows us to filter subsets of postings, it always becomes very simple. In almost all the cases, the answer is to find some way to disambiguate postings to select them, e.g. by account name, by attaching some tag, by using some metadata, etc. It can be illuminating to consider how this data can be represented as a table. Imagine that you have two tables: a table containing the fields of each Transaction such as date and description, and a table for the fields of each Posting, such as account, amount and currency, as well as a reference to its parent transaction. The simplest way to represent the data is to join those two tables, replicating values of the parent transaction across each of the postings. For example, this Beancount input: 2016-12-04 * \"Christmas gift\" Liabilities:CreditCard -153.45 USD Expenses:Gifts 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants 2016-12-07 * \"Pouring Ribbons\" \"Drinks with friends\" Assets:Cash -25.00 USD Expenses:Tips 4.00 USD Expenses:Alcohol could be rendered as a table like this: Date Fl Payee Narration Account Number Ccy 2016-12-04 * Christmas gift Liabilities:CreditCard -153.45 USD 2016-12-04 * Christmas gift Expenses:Gifts 153.45 USD 2016-12-06 * Biang! Dinner Liabilities:CreditCard -47.23 USD 2016-12-06 * Biang! Dinner Expenses:Restaurants 47.23 USD 2016-12-07 * Pouring Ribbons Drinks with friends Assets:Cash -25.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Tips 4.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Alcohol 21.00 USD Notice how the values of Transaction fields are replicated for each posting. This is exactly like a regular database join operation. The posting fields begin at column \u201cAccount.\u201d (Also note that this example table is simplified; in practice there are many more fields.) If you had a joined table just like this you could filter it and sum up amounts for arbitrary groups of postings. This is exactly what the bean-query tool allows you to do: You can run an SQL query on the data equivalent to this in-memory table and list values like this: SELECT date, payee, number WHERE account = \"Liabilities:CreditCard\"; Or sum up positions like this: SELECT account, sum(position) GROUP BY account; This simple last command generates the trial balance report. Note that the table representation does not inherently constrain the postings to sum to zero. If your selection criteria for the rows (in the WHERE clause) always selects all the postings for each of the matching transactions, you are ensured that the final sum of all the postings is zero. If not, the sum may be anything else. Just something to keep in mind. If you\u2019re familiar with SQL databases, you might ask why Beancount doesn\u2019t simply process its data in order to fill up an existing database system, so that the user could then use those database\u2019s tools. There are two main reasons for this: Reporting Operations. In order to generate income statements and balance sheets, the list of transactions needs to be preprocessed using the clear, open and close operations described previously. These operations are not trivial to implement in database queries and are dependent on just the report and ideally don\u2019t need to modify the input data. We\u2019d have to load up the posting data into memory and then run some code. We\u2019re already doing that by parsing the input file; the database step would be superfluous. Aggregating Positions. Though we haven\u2019t discussed it in this document so far, the contents of accounts may contain different types of commodities, as well as positions with an attached cost basis. The way that these positions are aggregated together requires the implementation of a custom data type because it obeys some rules about how positions are able to cancel each other out (see How Inventories Work for details). It would be very difficult to build these operations with an SQL database beyond the context of using just a single currency and ignoring cost basis. This is why Beancount provides a custom tool to directly process and query its data: It provides its own implementation of an SQL client that lets you specify open and close dates and leverages a custom \u201cInventory\u201d data structure to create sums of the positions of postings. This tools supports columns of Beancount\u2019s core types: Amount, Position and Inventory objects. (In any case, if you\u2019re not convinced, Beancount provides a tool to export its contents to a regular SQL database system. Feel free to experiment with it if you like, knock yourself out.) Please don\u2019t pay attention to the numbers in these large figures, they were randomly generated and don\u2019t reflect this. We\u2019re just interested in showing the structure, in these figures. \u21a9 Note that this is unrelated to the term \u201cclearing transactions\u201d which means acknowledging or marking that some transactions have been eyeballed by the bookkeeper and checked for correctness. \u21a9 Note that operations have nothing to do with the Open and Close directives Beancount provides. \u21a9 This is not provided yet in Beancount, but would be trivial to implement. All we'd need to do is invert the signs of balances from Liabilities, Income and Equity accounts. It's on the roadmap to provide this eventually. \u21a9","title":"The Table Perspective"},{"location":"tracking_medical_claims.html","text":"Tracking Out-of-Network Medical Claims in Beancount \uf0c1 Martin Blais - Updated: November 2023 Let's illustrate how one might handle dealing with medical treatment with insurance and HSA claims. Let's use an example of psychotherapy sessions, received out-of-network and paid upfront out of pocket and reimbursed later. When a session is received, it is booked to receivables and payables: 2023-09-06 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-11 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD Later on, some payments are made for it, clearing the payables: 2023-09-12 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -520.00 USD Liabilities:AccountsPayable:Psychotherapy 520.00 USD And so on, for the entire month: 2023-09-20 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-23 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD 2023-09-27 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-28 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD At the end of the month, a claim form is produced by the therapist. We file the claim with the insurance company, clearing the receivable and shifting the remaining portion to a insurance company check to be issued: 2023-09-28 * \"Claim for September filed with insurance\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy -1040.00 USD Expenses:Mental:Psychotherapy 312.00 USD Assets:AccountsReceivable:Anthem 728.00 USD Eventually, an EOB is produced to confirm how much is covered (in this example, 70%, which was known from the terms) and a check is received and deposited at the bank: 2023-10-24 * \"ATM CHECK DEPOSIT\" #freud-2023-09 Assets:US:BofA:Checking 728.00 USD Assets:AccountsReceivable:Anthem -728.00 USD At this stage we know the amount of the remaining portion eligible to be paid from the HSA, so we file a claim to the HSA company, once again booking them to a receivable and a payable: 2023-10-25 * \"HealthEquity\" \"Filed for reimbursement\" #freud-2023-09 Liabilities:AccountsPayable:HealthEquity -312.00 USD Assets:AccountsReceivable:HealthEquity 312.00 USD The HSA company makes a direct deposit to our checking account: 2023-11-01 * \"BofA bank (Claim ID:1234567-890); EFT to bank\" #freud-2023-09 Assets:US:HealthEquity:Cash -312.00 USD Liabilities:AccountsPayable:HealthEquity 312.00 USD And on the bank side when we import this transaction we book it against the receivable: 2023-11-01 * \"HEALTHEQUITY INC\" #freud-2023-09 Assets:US:BofA:Checking 312.00 USD Assets:AccountsReceivable:HealthEquity -312.00 USD The final result is that the HSA was used to cover the uninsured portion of the cost. |-- Assets | |-- AccountsReceivable | | |-- Anthem | | |-- HealthEquity | | `-- Psychotherapy | `-- US | |-- HealthEquity | | `-- Cash -312.00 USD | `-- BofA | `-- Checking |-- Expenses | `-- Mental | `-- Psychotherapy 312.00 USD `-- Liabilities `-- AccountsPayable |-- HealthEquity `-- Psychotherapy Net Income: (-312.00 USD) There are some flaws with the approach above: The amount covered by insurance is known ahead of time; in many cases the amount is not known before the EOB is issued by the insurance (after all that's what it's for, it's the \"Explanation of Benefits\"). This would require modifying the above.","title":"Tracking Out-of-Network Medical Claims in Beancount"},{"location":"tracking_medical_claims.html#tracking-out-of-network-medical-claims-in-beancount","text":"Martin Blais - Updated: November 2023 Let's illustrate how one might handle dealing with medical treatment with insurance and HSA claims. Let's use an example of psychotherapy sessions, received out-of-network and paid upfront out of pocket and reimbursed later. When a session is received, it is booked to receivables and payables: 2023-09-06 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-11 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD Later on, some payments are made for it, clearing the payables: 2023-09-12 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -520.00 USD Liabilities:AccountsPayable:Psychotherapy 520.00 USD And so on, for the entire month: 2023-09-20 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-23 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD 2023-09-27 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-28 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD At the end of the month, a claim form is produced by the therapist. We file the claim with the insurance company, clearing the receivable and shifting the remaining portion to a insurance company check to be issued: 2023-09-28 * \"Claim for September filed with insurance\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy -1040.00 USD Expenses:Mental:Psychotherapy 312.00 USD Assets:AccountsReceivable:Anthem 728.00 USD Eventually, an EOB is produced to confirm how much is covered (in this example, 70%, which was known from the terms) and a check is received and deposited at the bank: 2023-10-24 * \"ATM CHECK DEPOSIT\" #freud-2023-09 Assets:US:BofA:Checking 728.00 USD Assets:AccountsReceivable:Anthem -728.00 USD At this stage we know the amount of the remaining portion eligible to be paid from the HSA, so we file a claim to the HSA company, once again booking them to a receivable and a payable: 2023-10-25 * \"HealthEquity\" \"Filed for reimbursement\" #freud-2023-09 Liabilities:AccountsPayable:HealthEquity -312.00 USD Assets:AccountsReceivable:HealthEquity 312.00 USD The HSA company makes a direct deposit to our checking account: 2023-11-01 * \"BofA bank (Claim ID:1234567-890); EFT to bank\" #freud-2023-09 Assets:US:HealthEquity:Cash -312.00 USD Liabilities:AccountsPayable:HealthEquity 312.00 USD And on the bank side when we import this transaction we book it against the receivable: 2023-11-01 * \"HEALTHEQUITY INC\" #freud-2023-09 Assets:US:BofA:Checking 312.00 USD Assets:AccountsReceivable:HealthEquity -312.00 USD The final result is that the HSA was used to cover the uninsured portion of the cost. |-- Assets | |-- AccountsReceivable | | |-- Anthem | | |-- HealthEquity | | `-- Psychotherapy | `-- US | |-- HealthEquity | | `-- Cash -312.00 USD | `-- BofA | `-- Checking |-- Expenses | `-- Mental | `-- Psychotherapy 312.00 USD `-- Liabilities `-- AccountsPayable |-- HealthEquity `-- Psychotherapy Net Income: (-312.00 USD) There are some flaws with the approach above: The amount covered by insurance is known ahead of time; in many cases the amount is not known before the EOB is issued by the insurance (after all that's what it's for, it's the \"Explanation of Benefits\"). This would require modifying the above.","title":"Tracking Out-of-Network Medical Claims in Beancount"},{"location":"trading_with_beancount.html","text":"Trading with Beancount \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/trading Introduction What is Profit and Loss? Realized and Unrealized P/L Trade Lots Booking Methods Dated lots Reporting Unrealized P/L Commissions Stock Splits Cost Basis Adjustments Dividends Average Cost Booking Future Topics Introduction \uf0c1 This is a companion document for the Command-Line Accounting Cookbook that deals exclusively with the subject of trading and investments in Beancount. You probably should have read an introduction to the double-entry method before reading this document. The subject of stock trading needs to be preceded by a discussion of \u201cprofit and loss,\u201d or P/L, for short (pronounce: \u201cP and L\u201d), also called capital gains or losses. The notion of P/L against multiple trades can be difficult for a novice to understand, and I\u2019ve even seen professional traders lack sophistication in their understanding of P/L over varying time periods. It is worth spending a bit of time to explain this, and necessary to understand how to book your trades in a double-entry system. This discussion will be weaved with detailed examples of how to book these trades in Beancount, wherever possible. There is a related, active proposal for improving the booking methods in Beancount that you might also be interested in. Discussions of basis for tax-deferred accounts will not be treated here, but in the more general cookbook. What is Profit and Loss? \uf0c1 Let\u2019s imagine you have an account at the E*Trade discount broker and you buy some shares of a company, say IBM. If you buy 10 shares of IBM when its price is 160$/share, it will cost you 1600$. That value is what we will call the \u201cbook value\u201d, or equivalently, \u201cthe cost.\u201d This is how much money you had to spend in order to acquire the shares, also called \u201cthe position.\u201d This is how you would enter this transaction in Beancount: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1600.00 USD In practice you will probably pay some commission to E*Trade for this service, so let\u2019s put that in for completeness: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1609.95 USD Expenses:Financial:Commissions 9.95 USD This is how you tell Beancount to deposit some units \u201cat cost\u201d, in this case, units of \u201cIBM at 160 USD/share\u201d cost. This transaction balances because the sum of its legs is zero: 10 x 160 + -1609.95 + 9.95 = 0. Also note that we\u2019re choosing to use a subaccount dedicated to the shares of IBM; this is not strictly necessary but it is convenient for reporting in, for example, a balance sheet, because it will naturally aggregate all of your shares of each of your positions on their own line. Having a \u201ccash\u201d subaccount also emphasizes that uninvested funds you have there are not providing any return. The next day, the market opens and IBM shares are going for 170$/share. In this context, we will call this \u201cthe price.\u201d 1 The \u201cmarket value\u201d of your position, your shares, is the number of them x the market price, that is, 10 shares x 170$/share = 1700$. The difference between these two amounts is what we will call the P/L: market value - book value = P/L 10 x 170$ - 10 x 160$ = 1700$ - 1600$ = 100$ (profit) We will call a positive amount \u201ca profit\u201d and if the amount is negative, \u201ca loss.\u201d Realized and Unrealized P/L \uf0c1 The profit from the previous section is called an \u201cunrealized profit.\u201d That is because the shares have not actually been sold yet - this is a hypothetical profit: if I can sell those shares at the market value, this is how much I would pocket. The 100$ I mentioned in the previous section is actually an \u201cunrealized P/L.\u201d So let\u2019s say you like this unrealized profit and you feel that it\u2019s temporary luck that IBM went up. You decide to sell 3 of these 10 shares to the market at 170$/share. The profit on these share will now be \u201crealized\u201d: market value - book value = P/L 3 x 170$ - 3 x 160$ = 3 x (170 - 160) = 30$ (profit) This 30$ is a \u201crealized P/L.\u201d The remaining portion of your position is still showing an unrealized profit, that is, the price could fluctuate some more until you sell it: market value - book value = P/L 7 x 170$ - 7 x 160$ = 70$ This is how you would book this partial sale of your position in Beancount (again including a commission): 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Do you notice something funny going on here? -3 x 160 = -480, -480 + 500.05 + 9.95 = 30\u2026 This transaction does not balance to zero! The problem is that we received 510$ in cash in exchange for the 3 shares we sold. This is because the actual price we sold them at was 170$: 3 x 170 = 510$. This is where we need to account for the profit, by adding another leg which will absorb this profit, and conveniently enough, automatically calculate and track our profits for us: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL The last leg will be automatically filled in by Beancount to -30 USD , as we\u2019re allowed one posting without an amount (and remember that in the double-entry system without credits and debits, a profit is a negative number for \u201cIncome\u201d accounts). This is the number the government is interested in for your taxes. In summary, you now have: A position of 7 \u201cshares at book value of 160$\u201d = 1120$ (its book value) A realized P/L of 30$ An unrealized P/L of 70$ Now at this point, some of you will jump up and down and say: \u201cBut wait, waiiit! I sold at 170$/share, not 160$/share, why do you put 160$ here?\u201d The answer is that you did not have shares held at 170$ to sell. In order to explain this, I need to make a little detour to explain how we keep track of things in accounts... So how do we keep track of these shares? It\u2019s actually easy: when Beancount stores things in accounts, we use something called \u201can inventory.\u201d Imagine that an \u201cinventory\u201d is a bag with the name of that account on it. Each account has one such bag to hold the things in the account at a particular point in time, the \u201cbalance\u201d of this account at that time. Imagine that the things it contains have a little label attached to each of them, with their cost, that is, the price that was paid to acquire them. Whenever you put a thing in the bag, you attach a new label to the thing. For things to work right, all things need to be labeled 2 . In our example, the bag contained 10 items of \u201cshares of IBM bought at 160$/share\u201d. The syntax we used to put the IBM in the account can seem a little misleading; we wrote: Assets:US:ETrade:IBM 10 IBM {160.00 USD} but really, this is understood by Beancount closer to the following syntax: Assets:US:ETrade:IBM 10 {IBM 160.00 USD} But \u2026 it would be annoying to write this, so we use a syntax more intuitive to humans. So the thing is, you can\u2019t subtract units of {IBM at 170.00 USD} ... because there just aren\u2019t any in that bag. What you have in the bag are units of {IBM at 160.00 USD} . You can only take out these ones. Now that being said, do you see how it\u2019s the amount that was exchanged to us for the shares that really helps us track the P/L? Nowhere did we actually need to indicate the price at which we sold the shares. It\u2019s the fact that we received a certain amount of cash that is different than the cost of the position we\u2019re selling that triggers the imbalance, which we book to a capital gain. Hmmm\u2026 Beancount maintains a price database, wouldn\u2019t it be nice to at least record and attach that price to the transaction for documentation purposes? Indeed. Beancount allows you to also attach a price to that posting, but for the purpose of balancing the transaction, it ignores it completely. It is mainly there for documentation, and you can use it if you write scripts. And if you use the beancount.plugins.implicit_prices plugin, it will be used to automatically synthesize a price entry that will enrich our historical price database, which may be used in reporting the market value of the account contents (more details on this follow). So the complete and final transaction for selling those shares should be: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} @ 170.00 USD Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Trade Lots \uf0c1 In practice, the reality of trading gets a tiny bit more complicated than this. You might decide to buy some IBM multiple times, and each time, it is likely that you would buy them at a different price. Let\u2019s see how this works with another example trade. Given your previous position of 7 shares held at 160$ cost, the following day you see that the price went up some more, you change your mind on IBM and decide to \u201cgo long\u201d and buy 5 more shares. The price you get is 180$/share this time: 2014-02-18 * \"I put my chips on big blue!\" Assets:US:ETrade:IBM 5 IBM {180.00 USD} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD Now, what do we have in the bag for Assets:US:ETrade:IBM ? We have two kinds of things: 7 shares of \u201cIBM held at 160 USD/share\u201d, from the first trade 5 shares of \u201cIBM held at 180 USD/share\u201d, from this last trade We will call these \u201clots,\u201d or \u201ctrade lots.\u201d In fact, if you were to sell this entire position, say, a month later, the way to legally sell it in Beancount (that is, without issuing an error), is by specifying both legs. Say the price is 172$/share at that moment: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -7 IBM {160.00 USD} @ 172.00 USD Assets:US:ETrade:IBM -5 IBM {180.00 USD} Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Now your final position of IBM would be 0 shares. Alternatively, since you\u2019re selling the entire position, Beancount should be able to unambiguously match all the lots against an unspecified cost. This is equivalent: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -12 IBM {} @ 172.00 USD Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that this won\u2019t work if the total amount of shares doesn\u2019t match all the lots (this would be ambiguous\u2026 which subset of the lots should be chosen isn\u2019t obvious). Booking Methods \uf0c1 But what if you decided to sell only some of those shares? Say you need some cash to buy a gift to your loved one and you want to sell 4 shares this time. Say the price is now 175$/share. Now you have a choice to make. You can choose to sell the older shares and realize a larger profit: 2014-03-18 * \"Selling my older blue chips.\" Assets:US:ETrade:IBM -4 IBM {160.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; -60.00 USD (profit) Or you may choose to sell the most recently acquired ones and realize a loss: 2014-03-18 * \"Selling my most recent blue chips.\" Assets:US:ETrade:IBM -4 IBM {180.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; 20.00 USD (loss) Or you can choose to sell a mix of both: just use two legs. Note that in practice this choice will depend on a number of factors: The tax law of the jurisdiction where you trade the shares may have a defined method for how to book the shares and you may not actually have a choice. For example, they may state that you must trade the oldest lot you bought, a method called \u201cfirst-in-first out.\u201d If you have a choice, the various lots you\u2019re holding may have different taxation characteristics because you\u2019ve held them for a different period of time. In the USA, for example, positions held for more than one year benefit from a lower taxation rate (the \u201clong-term\u201d capital gains rate). You may have other gains or losses that you want to offset in order to minimize your cash flow requirements on your tax liability. This is sometimes called \u201c tax loss harvesting .\u201d There are more\u2026 but I\u2019m not going to elaborate on them here. My goal is to show you how to book these things with the double-entry method. Dated lots \uf0c1 We\u2019ve almost completed the whole picture of how this works. There is one more rather technical detail to add and it begins with a question: What if I bought multiple lots of share at the same price? As we alluded to in the previous section, the duration for which you held a position may have an impact on your taxation, even if the P/L ends up being the same. How do we differentiate between these lots? Well\u2026 I had simplified things a tiny bit earlier, just to make it simpler to understand. When we put positions in an inventory, on the label that we attach to the things we put in it, we also mark down the date that lot was acquired if you supply it. This is how you would book entering the position this way: 2014-05-20 * \"First trade\" Assets:US:ETrade:IBM 5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD 2014-05-21 * \"Second trade\" Assets:US:ETrade:IBM 3 IBM {180.00 USD, 2014-05-21} Assets:US:ETrade:Cash -549.95 USD Expenses:Financial:Commissions 9.95 USD Now when you sell, you can do the same thing to disambiguate which lot\u2019s position you want to reduce: 2014-08-04 * \"Selling off first trade\" Assets:US:ETrade:IBM -5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash 815.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that it\u2019s really unlikely that your broker will provide the information in the downloadable CSV or OFX files from their website\u2026 you probably won\u2019t be able to automate the lot detail of this transaction, you might have to pick up the PDF trade confirmations your broker provides to enter this manually, if it ever happens. But how often does it happen that you buy two lots at the same price? I trade relatively frequently - about every two weeks - and in 8 years worth of data I don\u2019t have a single occurrence of it. In practice, unless you do thousands of trades per day- and Beancount isn\u2019t really designed to handle that kind of activity, at least not in the most efficient way - it just won\u2019t happen very much. ( Technical Detail : that we\u2019re working on bettering the mechanism for lot selection so that you never have to insert the lot-date yourself, and so that you could disambiguate lot selection by supplying a name instead. See upcoming changes.) Reporting Unrealized P/L \uf0c1 Okay, so our account balances are holding the cost of each unit, and that provides us with the book value of these positions. Nice. But what about viewing the market value? The market value of the positions is simply the number of units of these instruments x the market price at the time we\u2019re interested in. This price fluctuates. So we need the price. Beancount supports a type of entry called a price entry that allows you to tell it what the price of an instrument was at a particular point in time, e.g. 2014-05-25 price IBM 182.27 USD In order to keep Beancount simple and with few dependencies, the software does not automatically fetch these prices (you can check out LedgerHub for this purpose, or write your own script that will insert the latest prices in your input file if so desired\u2026 there are many libraries to fetch prices from the internet online). It only knows about market prices from all these price entries. Using these, it builds an in-memory historical database of prices over time and can query it to obtain the most current values. Instead of supporting different reporting modes with options, you can trigger the insertion of unrealized gains by enabling a plugin: plugin \"beancount.plugins.unrealized\" \"Unrealized\" This will create a synthetic transaction at the date of the last of directives, that reflects the unrealized P/L. It books one side as Income and the other side as a change in Asset: 2014-05-25 U \"Unrealized gain for 7 units of IBM (price: 182.2700 USD as of 2014-05-25, average cost: 160.0000 USD)\" Assets:US:ETrade:IBM:Unrealized 155.89 USD Income:US:ETrade:IBM:Unrealized -155.89 USD Note that I used an option in this example to specify a sub-account to book the unrealized gains to. The unrealized P/L shows up on a separate line in the balance sheet and the parent account should show the market value on its balance (which includes that of its sub-accounts). Commissions \uf0c1 So far we have not discussed trading commissions. Depending on the tax law that applies to you, the costs associated with trading may be deductible from the raw capital gain as we\u2019ve calculated it in the previous examples. These are considered expenses by the government, and it is often the case that you can deduct those trading commissions (it\u2019s entirely reasonable from their part, you did not pocket that money after all). In the examples above, the capital gains and commission expenses get tracked into two separate accounts. For example, you could end up with reported balances that look like this: Income:US:ETrade:PnL -645.02 USD Expenses:Financial:Commissions 39.80 USD (Just to be clear, this is to be interpreted as a profit of $645.02 and an expense of $39.80.) You could subtract these numbers to obtain an approximation of the P/L without costs: 645.02 - 39.80 = $605.22. However, this is only an approximation of the correct P/L value. To understand why, we need to look at an example where a partial number of shares are sold across a reporting period. Imagine that we have an account with a commission rate of $10 per trade, 100 shares of ITOT were bought in 2013, 40 of those shares were later sold in that same year, and the remaining 60 were sold the year after, a scenario that looks like this: 2013-09-01 Buy 100 ITOT at $80, commission = 10$ 2013-11-01 Sell 40 ITOT at $82, commission = 10$ 2014-02-01 Sell 60 ITOT at $84, commission = 10$ If you computed the sum of commissions paid at the end of 2013, you would have $20, and using the approximate method outlined previously, for so 2013 and 2014 you would declare 2013: P/L of 40 x ($82 - $80) - ($10 + $10) = $60 2014: P/L of 60 x ($84 - $80) - $10 = $230 However, strictly speaking, this is incorrect. The $10 commission paid on acquiring the 100 shares has to be pro-rated with respect to the number of shares sold. This means that on that first sale of 40 shares only 4$ of the commission is deductible: $10 x (40 shares / 100 shares), and so we obtain: 2013: P/L of 40 x ($82 - $80) - $(4 + 10) = $66 2014: P/L of 60 x ($84 - $80) - $(6 + 10) = $224 As you can see, the P/L declared for each year differs, even if the sum of the P/L for both years is the same ($290). A convenient method to automatically allocate the acquisition costs to the pro-rata value of the number of shares sold is to add the acquisition trading cost to the total book value of the position. In this example, you would say that the position of 100 shares has a book value $8010 instead of $8000: 100 share x $80/share + $10, or equivalently, that the individual shares have a book value of $80.10 each. This would result in the following calculation: 2013: P/L of 40 x ($82 - $80.10) - $10 = $66 2014: P/L of 60 x ($84 - $80.10) - $10 = $224 You could even go one step further and fold the commission on sale into the price of each share sold as well: 2013: P/L of 40 x ($81.75 - $80.10) = $66 2014: P/L of 60 x ($83.8333 - $80.10) = $224 This may seem overkill, but imagine that those costs were much higher, as is the case on large commercial transactions; the details do begin to matter to the tax man. Accurate accounting is important, and we need to develop a method to do this more precisely. We don\u2019t currently have a good method of doing this with our input syntax. A suitable method is currently being developed and a proposal is on the table. Also see mailing-list for details. [June 2014] Stock Splits \uf0c1 Stock splits are currently dealt with by emptying an account\u2019s positions and recreating the positions at a different price: 2004-12-21 * \"Autodesk stock splits\" Assets:US:MSSB:ADSK -100 ADSK {66.30 USD} Assets:US:MSSB:ADSK 200 ADSK {33.15 USD} The postings balance each other, so the rule is respected. As you can see, this requires no special syntax feature. It also handles more general scenarios, such as the odd split of the Google company that occurred on the NASDAQ exchange in April 2014, into two different classes of stock (voting and non-voting shares, at 50.08% and 49.92%, respectively): 2014-04-07 * \"Stock splits into voting and non-voting shares\" Assets:US:MSSB:GOOG -25 GOOG {1212.51 USD} ; Old GOOG Assets:US:MSSB:GOOG 25 GOOG { 605.2850 USD} ; New GOOG Assets:US:MSSB:GOOGL 25 GOOG { 607.2250 USD} Ultimately, maybe a plug-in module should be provided to more easily create such stock split transactions, as there is some amount of redundancy involved. We need to figure out the most general way to do this. But the above will work for now. One problem with this approach is that the continuity of the trade lots is lost, that is, the purchase date of each lot has now been reset as a result of the transaction above, and it becomes impossible to automatically figure out the duration of the trade and its associated impact on taxation, i.e. long-term vs. short-term trade. Even without this the profit is still calculated correctly, but it is an annoying detail nonetheless. One way to handle this is by using the Dated Lots (see the appropriate section of this doc). That way, the original trade date can be preserved on the new lots. This provides accurate timing information in addition to the capital gain/loss based on the price. Another method for solving this and for easily propagating the lot trade date has been proposed and will be implemented in Beancount later on. A more important problem with the current implementation is that the meaning of a unit of ADSK before and after the stock split is different. The price graph for this commodity unit will show a radical discontinuity! This is a more general problem that has yet to be addressed in both Beancount and Ledger. The Commodity Definition Changes document has a discussion to address this topic. Cost Basis Adjustment and Return of Capital \uf0c1 Readjustment in cost basis may occur in managed funds, due to the fund\u2019s internal trading activities. This will typically occur in tax-sheltered accounts where the gain that occurs from such an adjustment has no impact on taxes, and where the cost basis is held at the average cost of all shares in each position. If we have the specific lot prices being adjusted, it is doable to book these in the same manner as we dealt with stock splits: 2014-04-07 * \"Cost basis adjustment for XSP\" Assets:CA:RRSP:XSP -100 ADSK {21.10 CAD} Assets:CA:RRSP:XSP 100 ADSK {23.40 CAD} Income:CA:RRSP:Gains -230.00 CAD However, this is really uncommon. The more common case of this is of an account using the average cost booking method, we don\u2019t currently have a way to deal with this. There is an active proposal in place to make this possible. The cost basis adjustment is commonly found in Return of Capital events. These happen, for example, when funds are returning capital to the shareholders. This can be caused by winding down the operation. From the taxation point of view, these are non-taxable events and affect the cost basis of the equity in the fund. The number of shares might stay the same, but their cost basis needs to be adjusted for potential Gain/Loss calculation at the point of sale in the future. Dividends \uf0c1 Dividends don\u2019t pose a particular problem. They are just income. They can be received as cash: 2014-02-01 * \"Cash dividends received from mutual fund RBF1005\" Assets:Investments:Cash 171.02 CAD Income:Investments:Dividends Or they can be received as stock itself: 2014-02-01 * \"Stock dividends received in shares\" Assets:Investments:RBF1005 7.234 RBF1005 {23.64 CAD} Income:Investments:Dividends In the case of dividends received as stock, as for stock purchases, you provide the cost basis at which the dividend was received (this should be available in your statements). If the account is held at average cost, this posting will simply merge with the other legs at the time an average cost booking is needed to be performed. Average Cost Booking \uf0c1 At the moment, the only way to perform booking at average cost is painful: you would have to use the method outlined in the Stock Split section in order to revalue your inventory. This is impractical, however. There is an active proposal with an associated syntax to fully solve this problem. Once the proposal is implemented, it will look like this: 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 GOOG {*} Assets:Investments:Cash 2740.05 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains Any posting with a cost of \u201c*\u201d acting on an inventory will select all the shares of that currency (GOOG), merge them into a single one at the average cost, and then reduce that position at this new average cost. Future Topics \uf0c1 I\u2019ll be handling the following topics later on: Mark-to-Market : Handling end-of-year mark-to-market for Section 1256 instruments (i.e., futures and options), by re-evaluating the cost basis. This is similar to a cost basis readjustment applied at the end of each year for all of these types of instruments. Short Sales : these require little changes. We just have to allow negative numbers of units held at cost. At the moment we spit a warning when units held at cost go negative in order to detect data entry errors, but it would be easy to extend the Open directive syntax to allow this to occur on specific accounts which can hold short sales, which should just show as negative shares. All the arithmetic should otherwise just work naturally. Interest payments on margins would show up as distinct transactions. Also, when you short the stock, you don\u2019t receive dividends for those positions, but rather you have to pay them out. You would have expense account for this, e.g., Expenses:StockLoans:Dividends . Trading Options : I have no idea how to do this at the moment, but I imagine these could be held like shares of stock, with no distinctions. I don\u2019t foresee any difficulty. Currency Trading : At the moment, I\u2019m not accounting for the positions in my FOREX accounts, just their P/L and interest payments. This poses interesting problems: Positions held in a FOREX account aren\u2019t just long or short the way that stocks are: they are actually offsetting two commodities at the same time. For example, a long position in USD/CAD should increase the exposure of USD and decrease the exposure in CAD, it can be seen as holding a long asset of USD and a short asset in CAD, at the same time. While it is possible to hold these positions as if they were distinct instruments (e.g., units of \u201cUSDCAD\u201d with disregard for its components) but for large positions, especially if held over long periods of time for hedging purposes, it is important to deal with this and somehow allow the user to reflect the net currency exposures of multiple currency positions against the rest of their assets and liabilities. We also need to deal with the gains generated by the closing of these positions: those generate a gain in the currency of the account, after conversion to this currency. For example, if you hold a currency account denominated in USD, and you go long EUR/JPY, when you close the position you will obtain a gain in EUR, and after conversion of the P/L from the EUR into the equivalent number of USD (via EUR/USD) the USD gain will be deposited in your account. This means that two rates are being used to estimate the current market value of any position: the differential between the current rate and the rate at the time of buying, and the rate of the base currency (e.g., EUR) in the account currency (e.g., USD). Some of these involve new features in Beancount, but some not. Ideas welcome. This is a misleading notion, however. In reality, there is no price , there exist only markets where you get a \u201chint\u201d of how much someone else might be willing to exchange your shares for (for different amounts to buy or to sell them, and for some limited number of them, we call this \u201ca market\u201d), but until you\u2019ve actually completed selling your shares, you don\u2019t really know precisely how much you will be able to execute that trade at, only an estimate. If you\u2019re not intimately familiar with trading, this should give you pause, and hopefully a big \u201cah-ha! \u201cmoment about how the world works - there really does not exist a price for anything in the world - but in the context of this discussion, let\u2019s make abstraction of this and assume that you can buy or sell as many shares as you have instantly on the markets at the middle price, such as Google Finance or Yahoo Finance or your broker would report it. The process of deciding that we would be able to sell the shares at 170$ each is called \u201cmarking\u201d, that is, under reasonable assumptions, we believe that we would be able to actually sell those shares at that price (the term of art \u201cmarking to market\u201d refers to the fact that we use the market price as the best indicator to our knowledge of our capability to realize the trade). For an individual buying and selling small amounts of shares that don\u2019t move the market and with an honest broker, this is mostly true in practice. \u21a9 As an aside, putting regular currencies in an account is just the degenerate case of a thing with an empty label. This is an implementation detail that works great in practice. \u21a9","title":"Trading with Beancount"},{"location":"trading_with_beancount.html#trading-with-beancount","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/trading Introduction What is Profit and Loss? Realized and Unrealized P/L Trade Lots Booking Methods Dated lots Reporting Unrealized P/L Commissions Stock Splits Cost Basis Adjustments Dividends Average Cost Booking Future Topics","title":"Trading with Beancount"},{"location":"trading_with_beancount.html#introduction","text":"This is a companion document for the Command-Line Accounting Cookbook that deals exclusively with the subject of trading and investments in Beancount. You probably should have read an introduction to the double-entry method before reading this document. The subject of stock trading needs to be preceded by a discussion of \u201cprofit and loss,\u201d or P/L, for short (pronounce: \u201cP and L\u201d), also called capital gains or losses. The notion of P/L against multiple trades can be difficult for a novice to understand, and I\u2019ve even seen professional traders lack sophistication in their understanding of P/L over varying time periods. It is worth spending a bit of time to explain this, and necessary to understand how to book your trades in a double-entry system. This discussion will be weaved with detailed examples of how to book these trades in Beancount, wherever possible. There is a related, active proposal for improving the booking methods in Beancount that you might also be interested in. Discussions of basis for tax-deferred accounts will not be treated here, but in the more general cookbook.","title":"Introduction"},{"location":"trading_with_beancount.html#what-is-profit-and-loss","text":"Let\u2019s imagine you have an account at the E*Trade discount broker and you buy some shares of a company, say IBM. If you buy 10 shares of IBM when its price is 160$/share, it will cost you 1600$. That value is what we will call the \u201cbook value\u201d, or equivalently, \u201cthe cost.\u201d This is how much money you had to spend in order to acquire the shares, also called \u201cthe position.\u201d This is how you would enter this transaction in Beancount: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1600.00 USD In practice you will probably pay some commission to E*Trade for this service, so let\u2019s put that in for completeness: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1609.95 USD Expenses:Financial:Commissions 9.95 USD This is how you tell Beancount to deposit some units \u201cat cost\u201d, in this case, units of \u201cIBM at 160 USD/share\u201d cost. This transaction balances because the sum of its legs is zero: 10 x 160 + -1609.95 + 9.95 = 0. Also note that we\u2019re choosing to use a subaccount dedicated to the shares of IBM; this is not strictly necessary but it is convenient for reporting in, for example, a balance sheet, because it will naturally aggregate all of your shares of each of your positions on their own line. Having a \u201ccash\u201d subaccount also emphasizes that uninvested funds you have there are not providing any return. The next day, the market opens and IBM shares are going for 170$/share. In this context, we will call this \u201cthe price.\u201d 1 The \u201cmarket value\u201d of your position, your shares, is the number of them x the market price, that is, 10 shares x 170$/share = 1700$. The difference between these two amounts is what we will call the P/L: market value - book value = P/L 10 x 170$ - 10 x 160$ = 1700$ - 1600$ = 100$ (profit) We will call a positive amount \u201ca profit\u201d and if the amount is negative, \u201ca loss.\u201d","title":"What is Profit and Loss?"},{"location":"trading_with_beancount.html#realized-and-unrealized-pl","text":"The profit from the previous section is called an \u201cunrealized profit.\u201d That is because the shares have not actually been sold yet - this is a hypothetical profit: if I can sell those shares at the market value, this is how much I would pocket. The 100$ I mentioned in the previous section is actually an \u201cunrealized P/L.\u201d So let\u2019s say you like this unrealized profit and you feel that it\u2019s temporary luck that IBM went up. You decide to sell 3 of these 10 shares to the market at 170$/share. The profit on these share will now be \u201crealized\u201d: market value - book value = P/L 3 x 170$ - 3 x 160$ = 3 x (170 - 160) = 30$ (profit) This 30$ is a \u201crealized P/L.\u201d The remaining portion of your position is still showing an unrealized profit, that is, the price could fluctuate some more until you sell it: market value - book value = P/L 7 x 170$ - 7 x 160$ = 70$ This is how you would book this partial sale of your position in Beancount (again including a commission): 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Do you notice something funny going on here? -3 x 160 = -480, -480 + 500.05 + 9.95 = 30\u2026 This transaction does not balance to zero! The problem is that we received 510$ in cash in exchange for the 3 shares we sold. This is because the actual price we sold them at was 170$: 3 x 170 = 510$. This is where we need to account for the profit, by adding another leg which will absorb this profit, and conveniently enough, automatically calculate and track our profits for us: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL The last leg will be automatically filled in by Beancount to -30 USD , as we\u2019re allowed one posting without an amount (and remember that in the double-entry system without credits and debits, a profit is a negative number for \u201cIncome\u201d accounts). This is the number the government is interested in for your taxes. In summary, you now have: A position of 7 \u201cshares at book value of 160$\u201d = 1120$ (its book value) A realized P/L of 30$ An unrealized P/L of 70$ Now at this point, some of you will jump up and down and say: \u201cBut wait, waiiit! I sold at 170$/share, not 160$/share, why do you put 160$ here?\u201d The answer is that you did not have shares held at 170$ to sell. In order to explain this, I need to make a little detour to explain how we keep track of things in accounts... So how do we keep track of these shares? It\u2019s actually easy: when Beancount stores things in accounts, we use something called \u201can inventory.\u201d Imagine that an \u201cinventory\u201d is a bag with the name of that account on it. Each account has one such bag to hold the things in the account at a particular point in time, the \u201cbalance\u201d of this account at that time. Imagine that the things it contains have a little label attached to each of them, with their cost, that is, the price that was paid to acquire them. Whenever you put a thing in the bag, you attach a new label to the thing. For things to work right, all things need to be labeled 2 . In our example, the bag contained 10 items of \u201cshares of IBM bought at 160$/share\u201d. The syntax we used to put the IBM in the account can seem a little misleading; we wrote: Assets:US:ETrade:IBM 10 IBM {160.00 USD} but really, this is understood by Beancount closer to the following syntax: Assets:US:ETrade:IBM 10 {IBM 160.00 USD} But \u2026 it would be annoying to write this, so we use a syntax more intuitive to humans. So the thing is, you can\u2019t subtract units of {IBM at 170.00 USD} ... because there just aren\u2019t any in that bag. What you have in the bag are units of {IBM at 160.00 USD} . You can only take out these ones. Now that being said, do you see how it\u2019s the amount that was exchanged to us for the shares that really helps us track the P/L? Nowhere did we actually need to indicate the price at which we sold the shares. It\u2019s the fact that we received a certain amount of cash that is different than the cost of the position we\u2019re selling that triggers the imbalance, which we book to a capital gain. Hmmm\u2026 Beancount maintains a price database, wouldn\u2019t it be nice to at least record and attach that price to the transaction for documentation purposes? Indeed. Beancount allows you to also attach a price to that posting, but for the purpose of balancing the transaction, it ignores it completely. It is mainly there for documentation, and you can use it if you write scripts. And if you use the beancount.plugins.implicit_prices plugin, it will be used to automatically synthesize a price entry that will enrich our historical price database, which may be used in reporting the market value of the account contents (more details on this follow). So the complete and final transaction for selling those shares should be: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} @ 170.00 USD Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL","title":"Realized and Unrealized P/L"},{"location":"trading_with_beancount.html#trade-lots","text":"In practice, the reality of trading gets a tiny bit more complicated than this. You might decide to buy some IBM multiple times, and each time, it is likely that you would buy them at a different price. Let\u2019s see how this works with another example trade. Given your previous position of 7 shares held at 160$ cost, the following day you see that the price went up some more, you change your mind on IBM and decide to \u201cgo long\u201d and buy 5 more shares. The price you get is 180$/share this time: 2014-02-18 * \"I put my chips on big blue!\" Assets:US:ETrade:IBM 5 IBM {180.00 USD} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD Now, what do we have in the bag for Assets:US:ETrade:IBM ? We have two kinds of things: 7 shares of \u201cIBM held at 160 USD/share\u201d, from the first trade 5 shares of \u201cIBM held at 180 USD/share\u201d, from this last trade We will call these \u201clots,\u201d or \u201ctrade lots.\u201d In fact, if you were to sell this entire position, say, a month later, the way to legally sell it in Beancount (that is, without issuing an error), is by specifying both legs. Say the price is 172$/share at that moment: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -7 IBM {160.00 USD} @ 172.00 USD Assets:US:ETrade:IBM -5 IBM {180.00 USD} Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Now your final position of IBM would be 0 shares. Alternatively, since you\u2019re selling the entire position, Beancount should be able to unambiguously match all the lots against an unspecified cost. This is equivalent: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -12 IBM {} @ 172.00 USD Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that this won\u2019t work if the total amount of shares doesn\u2019t match all the lots (this would be ambiguous\u2026 which subset of the lots should be chosen isn\u2019t obvious).","title":"Trade Lots"},{"location":"trading_with_beancount.html#booking-methods","text":"But what if you decided to sell only some of those shares? Say you need some cash to buy a gift to your loved one and you want to sell 4 shares this time. Say the price is now 175$/share. Now you have a choice to make. You can choose to sell the older shares and realize a larger profit: 2014-03-18 * \"Selling my older blue chips.\" Assets:US:ETrade:IBM -4 IBM {160.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; -60.00 USD (profit) Or you may choose to sell the most recently acquired ones and realize a loss: 2014-03-18 * \"Selling my most recent blue chips.\" Assets:US:ETrade:IBM -4 IBM {180.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; 20.00 USD (loss) Or you can choose to sell a mix of both: just use two legs. Note that in practice this choice will depend on a number of factors: The tax law of the jurisdiction where you trade the shares may have a defined method for how to book the shares and you may not actually have a choice. For example, they may state that you must trade the oldest lot you bought, a method called \u201cfirst-in-first out.\u201d If you have a choice, the various lots you\u2019re holding may have different taxation characteristics because you\u2019ve held them for a different period of time. In the USA, for example, positions held for more than one year benefit from a lower taxation rate (the \u201clong-term\u201d capital gains rate). You may have other gains or losses that you want to offset in order to minimize your cash flow requirements on your tax liability. This is sometimes called \u201c tax loss harvesting .\u201d There are more\u2026 but I\u2019m not going to elaborate on them here. My goal is to show you how to book these things with the double-entry method.","title":"Booking Methods"},{"location":"trading_with_beancount.html#dated-lots","text":"We\u2019ve almost completed the whole picture of how this works. There is one more rather technical detail to add and it begins with a question: What if I bought multiple lots of share at the same price? As we alluded to in the previous section, the duration for which you held a position may have an impact on your taxation, even if the P/L ends up being the same. How do we differentiate between these lots? Well\u2026 I had simplified things a tiny bit earlier, just to make it simpler to understand. When we put positions in an inventory, on the label that we attach to the things we put in it, we also mark down the date that lot was acquired if you supply it. This is how you would book entering the position this way: 2014-05-20 * \"First trade\" Assets:US:ETrade:IBM 5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD 2014-05-21 * \"Second trade\" Assets:US:ETrade:IBM 3 IBM {180.00 USD, 2014-05-21} Assets:US:ETrade:Cash -549.95 USD Expenses:Financial:Commissions 9.95 USD Now when you sell, you can do the same thing to disambiguate which lot\u2019s position you want to reduce: 2014-08-04 * \"Selling off first trade\" Assets:US:ETrade:IBM -5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash 815.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that it\u2019s really unlikely that your broker will provide the information in the downloadable CSV or OFX files from their website\u2026 you probably won\u2019t be able to automate the lot detail of this transaction, you might have to pick up the PDF trade confirmations your broker provides to enter this manually, if it ever happens. But how often does it happen that you buy two lots at the same price? I trade relatively frequently - about every two weeks - and in 8 years worth of data I don\u2019t have a single occurrence of it. In practice, unless you do thousands of trades per day- and Beancount isn\u2019t really designed to handle that kind of activity, at least not in the most efficient way - it just won\u2019t happen very much. ( Technical Detail : that we\u2019re working on bettering the mechanism for lot selection so that you never have to insert the lot-date yourself, and so that you could disambiguate lot selection by supplying a name instead. See upcoming changes.)","title":"Dated lots"},{"location":"trading_with_beancount.html#reporting-unrealized-pl","text":"Okay, so our account balances are holding the cost of each unit, and that provides us with the book value of these positions. Nice. But what about viewing the market value? The market value of the positions is simply the number of units of these instruments x the market price at the time we\u2019re interested in. This price fluctuates. So we need the price. Beancount supports a type of entry called a price entry that allows you to tell it what the price of an instrument was at a particular point in time, e.g. 2014-05-25 price IBM 182.27 USD In order to keep Beancount simple and with few dependencies, the software does not automatically fetch these prices (you can check out LedgerHub for this purpose, or write your own script that will insert the latest prices in your input file if so desired\u2026 there are many libraries to fetch prices from the internet online). It only knows about market prices from all these price entries. Using these, it builds an in-memory historical database of prices over time and can query it to obtain the most current values. Instead of supporting different reporting modes with options, you can trigger the insertion of unrealized gains by enabling a plugin: plugin \"beancount.plugins.unrealized\" \"Unrealized\" This will create a synthetic transaction at the date of the last of directives, that reflects the unrealized P/L. It books one side as Income and the other side as a change in Asset: 2014-05-25 U \"Unrealized gain for 7 units of IBM (price: 182.2700 USD as of 2014-05-25, average cost: 160.0000 USD)\" Assets:US:ETrade:IBM:Unrealized 155.89 USD Income:US:ETrade:IBM:Unrealized -155.89 USD Note that I used an option in this example to specify a sub-account to book the unrealized gains to. The unrealized P/L shows up on a separate line in the balance sheet and the parent account should show the market value on its balance (which includes that of its sub-accounts).","title":"Reporting Unrealized P/L"},{"location":"trading_with_beancount.html#commissions","text":"So far we have not discussed trading commissions. Depending on the tax law that applies to you, the costs associated with trading may be deductible from the raw capital gain as we\u2019ve calculated it in the previous examples. These are considered expenses by the government, and it is often the case that you can deduct those trading commissions (it\u2019s entirely reasonable from their part, you did not pocket that money after all). In the examples above, the capital gains and commission expenses get tracked into two separate accounts. For example, you could end up with reported balances that look like this: Income:US:ETrade:PnL -645.02 USD Expenses:Financial:Commissions 39.80 USD (Just to be clear, this is to be interpreted as a profit of $645.02 and an expense of $39.80.) You could subtract these numbers to obtain an approximation of the P/L without costs: 645.02 - 39.80 = $605.22. However, this is only an approximation of the correct P/L value. To understand why, we need to look at an example where a partial number of shares are sold across a reporting period. Imagine that we have an account with a commission rate of $10 per trade, 100 shares of ITOT were bought in 2013, 40 of those shares were later sold in that same year, and the remaining 60 were sold the year after, a scenario that looks like this: 2013-09-01 Buy 100 ITOT at $80, commission = 10$ 2013-11-01 Sell 40 ITOT at $82, commission = 10$ 2014-02-01 Sell 60 ITOT at $84, commission = 10$ If you computed the sum of commissions paid at the end of 2013, you would have $20, and using the approximate method outlined previously, for so 2013 and 2014 you would declare 2013: P/L of 40 x ($82 - $80) - ($10 + $10) = $60 2014: P/L of 60 x ($84 - $80) - $10 = $230 However, strictly speaking, this is incorrect. The $10 commission paid on acquiring the 100 shares has to be pro-rated with respect to the number of shares sold. This means that on that first sale of 40 shares only 4$ of the commission is deductible: $10 x (40 shares / 100 shares), and so we obtain: 2013: P/L of 40 x ($82 - $80) - $(4 + 10) = $66 2014: P/L of 60 x ($84 - $80) - $(6 + 10) = $224 As you can see, the P/L declared for each year differs, even if the sum of the P/L for both years is the same ($290). A convenient method to automatically allocate the acquisition costs to the pro-rata value of the number of shares sold is to add the acquisition trading cost to the total book value of the position. In this example, you would say that the position of 100 shares has a book value $8010 instead of $8000: 100 share x $80/share + $10, or equivalently, that the individual shares have a book value of $80.10 each. This would result in the following calculation: 2013: P/L of 40 x ($82 - $80.10) - $10 = $66 2014: P/L of 60 x ($84 - $80.10) - $10 = $224 You could even go one step further and fold the commission on sale into the price of each share sold as well: 2013: P/L of 40 x ($81.75 - $80.10) = $66 2014: P/L of 60 x ($83.8333 - $80.10) = $224 This may seem overkill, but imagine that those costs were much higher, as is the case on large commercial transactions; the details do begin to matter to the tax man. Accurate accounting is important, and we need to develop a method to do this more precisely. We don\u2019t currently have a good method of doing this with our input syntax. A suitable method is currently being developed and a proposal is on the table. Also see mailing-list for details. [June 2014]","title":"Commissions"},{"location":"trading_with_beancount.html#stock-splits","text":"Stock splits are currently dealt with by emptying an account\u2019s positions and recreating the positions at a different price: 2004-12-21 * \"Autodesk stock splits\" Assets:US:MSSB:ADSK -100 ADSK {66.30 USD} Assets:US:MSSB:ADSK 200 ADSK {33.15 USD} The postings balance each other, so the rule is respected. As you can see, this requires no special syntax feature. It also handles more general scenarios, such as the odd split of the Google company that occurred on the NASDAQ exchange in April 2014, into two different classes of stock (voting and non-voting shares, at 50.08% and 49.92%, respectively): 2014-04-07 * \"Stock splits into voting and non-voting shares\" Assets:US:MSSB:GOOG -25 GOOG {1212.51 USD} ; Old GOOG Assets:US:MSSB:GOOG 25 GOOG { 605.2850 USD} ; New GOOG Assets:US:MSSB:GOOGL 25 GOOG { 607.2250 USD} Ultimately, maybe a plug-in module should be provided to more easily create such stock split transactions, as there is some amount of redundancy involved. We need to figure out the most general way to do this. But the above will work for now. One problem with this approach is that the continuity of the trade lots is lost, that is, the purchase date of each lot has now been reset as a result of the transaction above, and it becomes impossible to automatically figure out the duration of the trade and its associated impact on taxation, i.e. long-term vs. short-term trade. Even without this the profit is still calculated correctly, but it is an annoying detail nonetheless. One way to handle this is by using the Dated Lots (see the appropriate section of this doc). That way, the original trade date can be preserved on the new lots. This provides accurate timing information in addition to the capital gain/loss based on the price. Another method for solving this and for easily propagating the lot trade date has been proposed and will be implemented in Beancount later on. A more important problem with the current implementation is that the meaning of a unit of ADSK before and after the stock split is different. The price graph for this commodity unit will show a radical discontinuity! This is a more general problem that has yet to be addressed in both Beancount and Ledger. The Commodity Definition Changes document has a discussion to address this topic.","title":"Stock Splits"},{"location":"trading_with_beancount.html#cost-basis-adjustment-and-return-of-capital","text":"Readjustment in cost basis may occur in managed funds, due to the fund\u2019s internal trading activities. This will typically occur in tax-sheltered accounts where the gain that occurs from such an adjustment has no impact on taxes, and where the cost basis is held at the average cost of all shares in each position. If we have the specific lot prices being adjusted, it is doable to book these in the same manner as we dealt with stock splits: 2014-04-07 * \"Cost basis adjustment for XSP\" Assets:CA:RRSP:XSP -100 ADSK {21.10 CAD} Assets:CA:RRSP:XSP 100 ADSK {23.40 CAD} Income:CA:RRSP:Gains -230.00 CAD However, this is really uncommon. The more common case of this is of an account using the average cost booking method, we don\u2019t currently have a way to deal with this. There is an active proposal in place to make this possible. The cost basis adjustment is commonly found in Return of Capital events. These happen, for example, when funds are returning capital to the shareholders. This can be caused by winding down the operation. From the taxation point of view, these are non-taxable events and affect the cost basis of the equity in the fund. The number of shares might stay the same, but their cost basis needs to be adjusted for potential Gain/Loss calculation at the point of sale in the future.","title":"Cost Basis Adjustment and Return of Capital"},{"location":"trading_with_beancount.html#dividends","text":"Dividends don\u2019t pose a particular problem. They are just income. They can be received as cash: 2014-02-01 * \"Cash dividends received from mutual fund RBF1005\" Assets:Investments:Cash 171.02 CAD Income:Investments:Dividends Or they can be received as stock itself: 2014-02-01 * \"Stock dividends received in shares\" Assets:Investments:RBF1005 7.234 RBF1005 {23.64 CAD} Income:Investments:Dividends In the case of dividends received as stock, as for stock purchases, you provide the cost basis at which the dividend was received (this should be available in your statements). If the account is held at average cost, this posting will simply merge with the other legs at the time an average cost booking is needed to be performed.","title":"Dividends"},{"location":"trading_with_beancount.html#average-cost-booking","text":"At the moment, the only way to perform booking at average cost is painful: you would have to use the method outlined in the Stock Split section in order to revalue your inventory. This is impractical, however. There is an active proposal with an associated syntax to fully solve this problem. Once the proposal is implemented, it will look like this: 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 GOOG {*} Assets:Investments:Cash 2740.05 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains Any posting with a cost of \u201c*\u201d acting on an inventory will select all the shares of that currency (GOOG), merge them into a single one at the average cost, and then reduce that position at this new average cost.","title":"Average Cost Booking"},{"location":"trading_with_beancount.html#future-topics","text":"I\u2019ll be handling the following topics later on: Mark-to-Market : Handling end-of-year mark-to-market for Section 1256 instruments (i.e., futures and options), by re-evaluating the cost basis. This is similar to a cost basis readjustment applied at the end of each year for all of these types of instruments. Short Sales : these require little changes. We just have to allow negative numbers of units held at cost. At the moment we spit a warning when units held at cost go negative in order to detect data entry errors, but it would be easy to extend the Open directive syntax to allow this to occur on specific accounts which can hold short sales, which should just show as negative shares. All the arithmetic should otherwise just work naturally. Interest payments on margins would show up as distinct transactions. Also, when you short the stock, you don\u2019t receive dividends for those positions, but rather you have to pay them out. You would have expense account for this, e.g., Expenses:StockLoans:Dividends . Trading Options : I have no idea how to do this at the moment, but I imagine these could be held like shares of stock, with no distinctions. I don\u2019t foresee any difficulty. Currency Trading : At the moment, I\u2019m not accounting for the positions in my FOREX accounts, just their P/L and interest payments. This poses interesting problems: Positions held in a FOREX account aren\u2019t just long or short the way that stocks are: they are actually offsetting two commodities at the same time. For example, a long position in USD/CAD should increase the exposure of USD and decrease the exposure in CAD, it can be seen as holding a long asset of USD and a short asset in CAD, at the same time. While it is possible to hold these positions as if they were distinct instruments (e.g., units of \u201cUSDCAD\u201d with disregard for its components) but for large positions, especially if held over long periods of time for hedging purposes, it is important to deal with this and somehow allow the user to reflect the net currency exposures of multiple currency positions against the rest of their assets and liabilities. We also need to deal with the gains generated by the closing of these positions: those generate a gain in the currency of the account, after conversion to this currency. For example, if you hold a currency account denominated in USD, and you go long EUR/JPY, when you close the position you will obtain a gain in EUR, and after conversion of the P/L from the EUR into the equivalent number of USD (via EUR/USD) the USD gain will be deposited in your account. This means that two rates are being used to estimate the current market value of any position: the differential between the current rate and the rate at the time of buying, and the rate of the base currency (e.g., EUR) in the account currency (e.g., USD). Some of these involve new features in Beancount, but some not. Ideas welcome. This is a misleading notion, however. In reality, there is no price , there exist only markets where you get a \u201chint\u201d of how much someone else might be willing to exchange your shares for (for different amounts to buy or to sell them, and for some limited number of them, we call this \u201ca market\u201d), but until you\u2019ve actually completed selling your shares, you don\u2019t really know precisely how much you will be able to execute that trade at, only an estimate. If you\u2019re not intimately familiar with trading, this should give you pause, and hopefully a big \u201cah-ha! \u201cmoment about how the world works - there really does not exist a price for anything in the world - but in the context of this discussion, let\u2019s make abstraction of this and assume that you can buy or sell as many shares as you have instantly on the markets at the middle price, such as Google Finance or Yahoo Finance or your broker would report it. The process of deciding that we would be able to sell the shares at 170$ each is called \u201cmarking\u201d, that is, under reasonable assumptions, we believe that we would be able to actually sell those shares at that price (the term of art \u201cmarking to market\u201d refers to the fact that we use the market price as the best indicator to our knowledge of our capability to realize the trade). For an individual buying and selling small amounts of shares that don\u2019t move the market and with an honest broker, this is mostly true in practice. \u21a9 As an aside, putting regular currencies in an account is just the degenerate case of a thing with an empty label. This is an implementation detail that works great in practice. \u21a9","title":"Future Topics"},{"location":"tutorial_example.html","text":"Beancount Example & Tutorial \uf0c1 Martin Blais , October 2014 http://furius.ca/beancount/doc/example Example File Generator Converting to Ledger Input Profile of Example User Future Additions Tutorial Generate an Example File Generating Reports Generating Balances Generating a Balance Sheet and Income Statement Journals Holdings Other Reports Other Formats Viewing Reports through the Web Interface The Future of Beancount Reports Example File Generator \uf0c1 Beancount has a command to generate a few years of a realistic user\u2019s historical entries: bean-example > example.beancount The script checks if the output fails to process cleanly in Beancount (if this occurs, try rerunning or contact the author at blais@furius.ca ). Note that there is an option to fix the random generator\u2019s seed to be used to generate the entries. You can generate multiple years of ledger data for this user; see available options with bean-example --help The purpose of the script is: To provide a realistic corpus of anonymous data for users to experiment with, to kick the tires of Beancount, generate reports, etc. To compare reports with those generated by Ledger. As an example file to be used in this tutorial. As input for stress testing Beancount. Converting to Ledger Input \uf0c1 It should be possible to convert example files generated from the script to Ledger syntax, like this: bean-report example.beancount ledger > example.lgr If you encounter any problems with the conversion, please let me know. While I have an automated test to check that the conversion succeeds, I\u2019m focusing my efforts on Beancount itself and I\u2019m not using Ledger much other than for the occasional comparison or for illustrating something on the mailing-list. Profile of Example User \uf0c1 Here\u2019s a high-level profile of this user. In the text that follows, I\u2019ll use the masculine \u201che\u201d for simplicity\u2019s sake. He lives somewhere in the USA and uses a single operating currency: \u201cUSD\u201d. He rents an apartment. Income : He is a software engineer (sorry I could not resist), receives a modest salary income from a software company. He is paid bi-weekly. He maximizes contributions to his company\u2019s 401k plan. His company offers a match to his contributions. His salary is paid as a direct deposit to his checking account. He benefits from and pays premiums for a health insurance plan provided by his employer. He tracks his vacation hours explicitly. Expenses : Checking account : He pays his apartment\u2019s rent by check. He also pays his electricity, internet ISP and mobile phone bills directly from his checking account. His bank also takes a monthly bank fee. Credit cards : He also has other regular expenses, such as groceries and restaurant outings with friends, which he pays from his credit card. Assets : Banking : He has an account at a major US bank. Retirement account : His 401k account is held at Fidelity or Vanguard and is allocated in its entirety to two funds, a mix of stocks and bonds. There are never any sales in this account. This account is held at average cost. Other investments : Extra money that he is able to save is transferred from his checking account towards a taxable investment account in which he purchases a mix of stocks and ETFs, and occasionally makes sales and realizes profits. Liabilities : Credit cards : He has one or two credit cards issued by another bank. Events/Tags : He takes a short vacation or two per year, where he travels somewhere fun. Future Additions \uf0c1 Note that as I beef up the example generator script, this profile will increase in complexity. If there is a particular situation from the cookbook or otherwise that you\u2019d like to see in the example file, please let me know (a patch is the best way to let me know). Generate fake PDF files for documents (from an option) along with suitable document directives for them, most of them implicit but some explicit. Generate more expenses: Some amount of realistic cash expenses. Add a vehicle: car repairs, gas, etc. Remove tram example, it is too urban-specific. Add donations for charity. Add a second credit card and vary sources of expenses. Add some real uses for links, for the 401k match, for instance, the employer contributions should be linked with the employee contributions. Or other ones. Generate a loan and payments for a student loan. Having a large liability is a common case, I\u2019d like to represent that, even if it\u2019s not a mortgage. Convert retirement accounts to book using the average cost method, and add corresponding fees which would otherwise be impossible to track with explicit lots. Add tracking of health care expenses as per the cookbook. Add foreign accounts in another currency and transactions and transfers in that currency. Tutorial \uf0c1 This section provides basic examples of generating reports with Beancount, on the typical example file that would be output by it. The list of reports here is not meant to be exhaustive. You may follow the tutorial and generate your own example file, or look at the files from beancount/examples/tutorial if you prefer. Generate an Example File \uf0c1 Begin by generating the example file: bean-example > example.beancount Open example.beancount and examine it. Next, before we begin generating reports, verify that the file loads without any errors: bean-check example.beancount It should return quietly, without outputting anything (bean-check only writes errors when there are some, otherwise on success it writes nothing). Generating Reports \uf0c1 Let\u2019s generate a report of the final balances of all accounts [output] : bean-report example.beancount balances As you can see, the bean-report script has subcommands for the various reports it generates. To list the available reports, use --help-reports [output] : bean-report --help-reports To list the options available for a particular report, use --help on it [output] : bean-report example.beancount balances --help The report-specific options vary for each report type. You can also list the global options for the script [output] : bean-report --help The global options allow you to specify the desired output format for the reports. To see which formats are available for which report, use --help-formats [output] : bean-report --help-formats Generating Balances \uf0c1 Good, so we know how to generate a report of balances for all accounts. This is a pretty detailed list of accounts though. Let\u2019s just restrict the output to the accounts that we\u2019re interested in [output] : bean-report example.beancount balances -e ETrade Here you can view the number of units held in each of the investment subaccounts. To render the cost, use the --cost option [output] : bean-report example.beancount balances -e ETrade --cost Formatting Tools \uf0c1 Sometimes it\u2019s nice to render a hierarchical list of accounts as a tree. You can use the \u201ctreeify\u201d tool provided by Beancount to do this: bean-report example.beancount balances | treeify This tool will work on any column of data that looks like a column of account names (you can also configure it work with filenames as well, or other patterns). Generating a Balance Sheet and Income Statement \uf0c1 Let us generate a balance sheet: bean-report example.beancount balsheet Unfortunately, the only output format supported for it at this point is HTML. Also, filtering balance sheet entries from the command-line is not supported. Generate this to a file and open a browser to it [output] : bean-report example.beancount balsheet > /tmp/balsheet.html You can to the same for income statements: bean-report example.beancount income > /tmp/income.html Journals \uf0c1 You can also generate journals (in Ledger parlance, these are \u201cregisters\u201d). Let\u2019s look at a checking account postings, for instance [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking To render a column of running balances, add the --balance option [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking --balance The inventories are rendered in the number of units they\u2019re holding [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD If you want to render the values at cost, use the --cost option [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD --cost To render journals, you will typically restrict the set of postings that you want to view. If you did not, the postings would render no change, because all the legs of the transactions would be applied to the running balance and the total would be zero. But try it, nonetheless [output] : bean-report example.beancount journal --balance Holdings \uf0c1 There are a variety of ways to obtain aggregations for the total list of holdings. List the detailed holdings [output] : bean-report example.beancount holdings To aggregate the holdings by account, do this [output] : bean-report example.beancount holdings --by=account Because it\u2019s common practice to create and use a dedicated Beancount subaccount name for each commodity held in a real-world account, we can refine this further to aggregate to the parent (\u201croot\u201d) accounts of those subaccounts [output] : bean-report example.beancount holdings --by=root-account We can aggregate by commodity [output] : bean-report example.beancount holdings --by=commodity Or by the currency in which the commodities are quoted, which gives us a glimpse into our currency exposure [output] : bean-report example.beancount holdings --by=currency Finally, we can aggregate all the holdings to obtain net worth [output] : bean-report example.beancount networth There are a few more options for converting amounts to a common currency. See help for details. Other Reports \uf0c1 There are many other miscellaneous reports available. Try a few of those. Listing all accounts [output] : bean-report example.beancount accounts Listing events [output] : bean-report example.beancount events Number of directives by type [output] : bean-report example.beancount stats-directives Number of postings by type [output] : bean-report example.beancount stats-postings Other Formats \uf0c1 Reports may support various output formats. You can change the output format with the --format (-f) global option [output] : bean-report -f csv example.beancount holdings Note that \u201cbeancount\u201d and \u201cledger\u201d are valid output formats: they refer to the Beancount and Ledger input syntaxes. Viewing Reports through the Web Interface \uf0c1 The original way to access reports in Beancount is via its web interface that serves to a local web server on your machine. Serve the example file like this: bean-web example.beancount Then navigate with a web browser to http://localhost:8080 . From there, you can click on any number of filtered views and access some of the reports previously demonstrated. For example, click on a year view; that will provide balance sheets and income statements and various other reports for this subset of transactions. The bean-web tool has many options for restricting what is being served. (Note that by default the server listens only for connections from your computer; if you want to host this on a web server and accept connections from anywhere, use --public ). Note: There exists a separate project which provides a better web interface than the one which comes with Beancount: Fava . You might want to check it out. The Future of Beancount Reports \uf0c1 I find my command-line reports above to be rather unsatisfying, from the point-of-view of customizability. They bother me a lot. This is largely because I\u2019ve been happy and satisfied with the capabilities of the web interface to Beancount. As I\u2019m beginning to use the command-line interface more and more, I\u2019m finding myself desiring for more explicit and well-defined way to apply all the filters I have available from the web interface, and more. For this reason, I\u2019m currently implementing a query language that looks similar to SQL . This syntax will allow me to remove all the custom reports above and to replace them by a single consistent query syntax. This work is underway. All the holdings reports and even their internal objects will be replaced by the SQL-like query syntax as well. Holdings need not be treated separately: they are simply the list of positions available at a particular point in time, and along with suitable accessors from the query syntax and support for aggregations over their many columns, we should be able to generate all of those reports equivalently and simplify the code. \u2014 Martin Blais, October 2014","title":"Tutorial & Example"},{"location":"tutorial_example.html#beancount-example-tutorial","text":"Martin Blais , October 2014 http://furius.ca/beancount/doc/example Example File Generator Converting to Ledger Input Profile of Example User Future Additions Tutorial Generate an Example File Generating Reports Generating Balances Generating a Balance Sheet and Income Statement Journals Holdings Other Reports Other Formats Viewing Reports through the Web Interface The Future of Beancount Reports","title":"Beancount Example & Tutorial"},{"location":"tutorial_example.html#example-file-generator","text":"Beancount has a command to generate a few years of a realistic user\u2019s historical entries: bean-example > example.beancount The script checks if the output fails to process cleanly in Beancount (if this occurs, try rerunning or contact the author at blais@furius.ca ). Note that there is an option to fix the random generator\u2019s seed to be used to generate the entries. You can generate multiple years of ledger data for this user; see available options with bean-example --help The purpose of the script is: To provide a realistic corpus of anonymous data for users to experiment with, to kick the tires of Beancount, generate reports, etc. To compare reports with those generated by Ledger. As an example file to be used in this tutorial. As input for stress testing Beancount.","title":"Example File Generator"},{"location":"tutorial_example.html#converting-to-ledger-input","text":"It should be possible to convert example files generated from the script to Ledger syntax, like this: bean-report example.beancount ledger > example.lgr If you encounter any problems with the conversion, please let me know. While I have an automated test to check that the conversion succeeds, I\u2019m focusing my efforts on Beancount itself and I\u2019m not using Ledger much other than for the occasional comparison or for illustrating something on the mailing-list.","title":"Converting to Ledger Input"},{"location":"tutorial_example.html#profile-of-example-user","text":"Here\u2019s a high-level profile of this user. In the text that follows, I\u2019ll use the masculine \u201che\u201d for simplicity\u2019s sake. He lives somewhere in the USA and uses a single operating currency: \u201cUSD\u201d. He rents an apartment. Income : He is a software engineer (sorry I could not resist), receives a modest salary income from a software company. He is paid bi-weekly. He maximizes contributions to his company\u2019s 401k plan. His company offers a match to his contributions. His salary is paid as a direct deposit to his checking account. He benefits from and pays premiums for a health insurance plan provided by his employer. He tracks his vacation hours explicitly. Expenses : Checking account : He pays his apartment\u2019s rent by check. He also pays his electricity, internet ISP and mobile phone bills directly from his checking account. His bank also takes a monthly bank fee. Credit cards : He also has other regular expenses, such as groceries and restaurant outings with friends, which he pays from his credit card. Assets : Banking : He has an account at a major US bank. Retirement account : His 401k account is held at Fidelity or Vanguard and is allocated in its entirety to two funds, a mix of stocks and bonds. There are never any sales in this account. This account is held at average cost. Other investments : Extra money that he is able to save is transferred from his checking account towards a taxable investment account in which he purchases a mix of stocks and ETFs, and occasionally makes sales and realizes profits. Liabilities : Credit cards : He has one or two credit cards issued by another bank. Events/Tags : He takes a short vacation or two per year, where he travels somewhere fun.","title":"Profile of Example User"},{"location":"tutorial_example.html#future-additions","text":"Note that as I beef up the example generator script, this profile will increase in complexity. If there is a particular situation from the cookbook or otherwise that you\u2019d like to see in the example file, please let me know (a patch is the best way to let me know). Generate fake PDF files for documents (from an option) along with suitable document directives for them, most of them implicit but some explicit. Generate more expenses: Some amount of realistic cash expenses. Add a vehicle: car repairs, gas, etc. Remove tram example, it is too urban-specific. Add donations for charity. Add a second credit card and vary sources of expenses. Add some real uses for links, for the 401k match, for instance, the employer contributions should be linked with the employee contributions. Or other ones. Generate a loan and payments for a student loan. Having a large liability is a common case, I\u2019d like to represent that, even if it\u2019s not a mortgage. Convert retirement accounts to book using the average cost method, and add corresponding fees which would otherwise be impossible to track with explicit lots. Add tracking of health care expenses as per the cookbook. Add foreign accounts in another currency and transactions and transfers in that currency.","title":"Future Additions"},{"location":"tutorial_example.html#tutorial","text":"This section provides basic examples of generating reports with Beancount, on the typical example file that would be output by it. The list of reports here is not meant to be exhaustive. You may follow the tutorial and generate your own example file, or look at the files from beancount/examples/tutorial if you prefer.","title":"Tutorial"},{"location":"tutorial_example.html#generate-an-example-file","text":"Begin by generating the example file: bean-example > example.beancount Open example.beancount and examine it. Next, before we begin generating reports, verify that the file loads without any errors: bean-check example.beancount It should return quietly, without outputting anything (bean-check only writes errors when there are some, otherwise on success it writes nothing).","title":"Generate an Example File"},{"location":"tutorial_example.html#generating-reports","text":"Let\u2019s generate a report of the final balances of all accounts [output] : bean-report example.beancount balances As you can see, the bean-report script has subcommands for the various reports it generates. To list the available reports, use --help-reports [output] : bean-report --help-reports To list the options available for a particular report, use --help on it [output] : bean-report example.beancount balances --help The report-specific options vary for each report type. You can also list the global options for the script [output] : bean-report --help The global options allow you to specify the desired output format for the reports. To see which formats are available for which report, use --help-formats [output] : bean-report --help-formats","title":"Generating Reports"},{"location":"tutorial_example.html#generating-balances","text":"Good, so we know how to generate a report of balances for all accounts. This is a pretty detailed list of accounts though. Let\u2019s just restrict the output to the accounts that we\u2019re interested in [output] : bean-report example.beancount balances -e ETrade Here you can view the number of units held in each of the investment subaccounts. To render the cost, use the --cost option [output] : bean-report example.beancount balances -e ETrade --cost","title":"Generating Balances"},{"location":"tutorial_example.html#formatting-tools","text":"Sometimes it\u2019s nice to render a hierarchical list of accounts as a tree. You can use the \u201ctreeify\u201d tool provided by Beancount to do this: bean-report example.beancount balances | treeify This tool will work on any column of data that looks like a column of account names (you can also configure it work with filenames as well, or other patterns).","title":"Formatting Tools"},{"location":"tutorial_example.html#generating-a-balance-sheet-and-income-statement","text":"Let us generate a balance sheet: bean-report example.beancount balsheet Unfortunately, the only output format supported for it at this point is HTML. Also, filtering balance sheet entries from the command-line is not supported. Generate this to a file and open a browser to it [output] : bean-report example.beancount balsheet > /tmp/balsheet.html You can to the same for income statements: bean-report example.beancount income > /tmp/income.html","title":"Generating a Balance Sheet and Income Statement"},{"location":"tutorial_example.html#journals","text":"You can also generate journals (in Ledger parlance, these are \u201cregisters\u201d). Let\u2019s look at a checking account postings, for instance [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking To render a column of running balances, add the --balance option [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking --balance The inventories are rendered in the number of units they\u2019re holding [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD If you want to render the values at cost, use the --cost option [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD --cost To render journals, you will typically restrict the set of postings that you want to view. If you did not, the postings would render no change, because all the legs of the transactions would be applied to the running balance and the total would be zero. But try it, nonetheless [output] : bean-report example.beancount journal --balance","title":"Journals"},{"location":"tutorial_example.html#holdings","text":"There are a variety of ways to obtain aggregations for the total list of holdings. List the detailed holdings [output] : bean-report example.beancount holdings To aggregate the holdings by account, do this [output] : bean-report example.beancount holdings --by=account Because it\u2019s common practice to create and use a dedicated Beancount subaccount name for each commodity held in a real-world account, we can refine this further to aggregate to the parent (\u201croot\u201d) accounts of those subaccounts [output] : bean-report example.beancount holdings --by=root-account We can aggregate by commodity [output] : bean-report example.beancount holdings --by=commodity Or by the currency in which the commodities are quoted, which gives us a glimpse into our currency exposure [output] : bean-report example.beancount holdings --by=currency Finally, we can aggregate all the holdings to obtain net worth [output] : bean-report example.beancount networth There are a few more options for converting amounts to a common currency. See help for details.","title":"Holdings"},{"location":"tutorial_example.html#other-reports","text":"There are many other miscellaneous reports available. Try a few of those. Listing all accounts [output] : bean-report example.beancount accounts Listing events [output] : bean-report example.beancount events Number of directives by type [output] : bean-report example.beancount stats-directives Number of postings by type [output] : bean-report example.beancount stats-postings","title":"Other Reports"},{"location":"tutorial_example.html#other-formats","text":"Reports may support various output formats. You can change the output format with the --format (-f) global option [output] : bean-report -f csv example.beancount holdings Note that \u201cbeancount\u201d and \u201cledger\u201d are valid output formats: they refer to the Beancount and Ledger input syntaxes.","title":"Other Formats"},{"location":"tutorial_example.html#viewing-reports-through-the-web-interface","text":"The original way to access reports in Beancount is via its web interface that serves to a local web server on your machine. Serve the example file like this: bean-web example.beancount Then navigate with a web browser to http://localhost:8080 . From there, you can click on any number of filtered views and access some of the reports previously demonstrated. For example, click on a year view; that will provide balance sheets and income statements and various other reports for this subset of transactions. The bean-web tool has many options for restricting what is being served. (Note that by default the server listens only for connections from your computer; if you want to host this on a web server and accept connections from anywhere, use --public ). Note: There exists a separate project which provides a better web interface than the one which comes with Beancount: Fava . You might want to check it out.","title":"Viewing Reports through the Web Interface"},{"location":"tutorial_example.html#the-future-of-beancount-reports","text":"I find my command-line reports above to be rather unsatisfying, from the point-of-view of customizability. They bother me a lot. This is largely because I\u2019ve been happy and satisfied with the capabilities of the web interface to Beancount. As I\u2019m beginning to use the command-line interface more and more, I\u2019m finding myself desiring for more explicit and well-defined way to apply all the filters I have available from the web interface, and more. For this reason, I\u2019m currently implementing a query language that looks similar to SQL . This syntax will allow me to remove all the custom reports above and to replace them by a single consistent query syntax. This work is underway. All the holdings reports and even their internal objects will be replaced by the SQL-like query syntax as well. Holdings need not be treated separately: they are simply the list of positions available at a particular point in time, and along with suitable accessors from the query syntax and support for aggregations over their many columns, we should be able to generate all of those reports equivalently and simplify the code. \u2014 Martin Blais, October 2014","title":"The Future of Beancount Reports"},{"location":"api_reference/index.html","text":"beancount \uf0c1 beancount.core beancount.loader beancount.ops beancount.parser beancount.plugins beancount.scripts beancount.tools beancount.utils","title":"beancount"},{"location":"api_reference/index.html#beancount","text":"beancount.core beancount.loader beancount.ops beancount.parser beancount.plugins beancount.scripts beancount.tools beancount.utils","title":"beancount"},{"location":"api_reference/beancount.core.html","text":"beancount.core \uf0c1 Core basic objects and data structures to represent a list of entries. beancount.core.account \uf0c1 Functions that operate on account strings. These account objects are rather simple and dumb; they do not contain the list of their associated postings. This is achieved by building a realization; see realization.py for details. beancount.core.account.AccountTransformer \uf0c1 Account name transformer. This is used to support Win... huh, filesystems and platforms which do not support colon characters. Attributes: Name Type Description rsep A character string, the new separator to use in link names. beancount.core.account.AccountTransformer.parse(self, transformed_name) \uf0c1 Convert the transform account name to an account name. Source code in beancount/core/account.py def parse(self, transformed_name): \"Convert the transform account name to an account name.\" return (transformed_name if self.rsep is None else transformed_name.replace(self.rsep, sep)) beancount.core.account.AccountTransformer.render(self, account_name) \uf0c1 Convert the account name to a transformed account name. Source code in beancount/core/account.py def render(self, account_name): \"Convert the account name to a transformed account name.\" return (account_name if self.rsep is None else account_name.replace(sep, self.rsep)) beancount.core.account.commonprefix(accounts) \uf0c1 Return the common prefix of a list of account names. Parameters: accounts \u2013 A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. Source code in beancount/core/account.py def commonprefix(accounts): \"\"\"Return the common prefix of a list of account names. Args: accounts: A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. \"\"\" accounts_lists = [account_.split(sep) for account_ in accounts] # Note: the os.path.commonprefix() function just happens to work here. # Inspect its code, and even the special case of no common prefix # works well with str.join() below. common_list = path.commonprefix(accounts_lists) return sep.join(common_list) beancount.core.account.has_component(account_name, component) \uf0c1 Return true if one of the account contains a given component. Parameters: account_name \u2013 A string, an account name. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/account.py def has_component(account_name, component): \"\"\"Return true if one of the account contains a given component. Args: account_name: A string, an account name. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return bool(re.search('(^|:){}(:|$)'.format(component), account_name)) beancount.core.account.is_valid(string) \uf0c1 Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Parameters: string \u2013 A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. Source code in beancount/core/account.py def is_valid(string): \"\"\"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Args: string: A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. \"\"\" return (isinstance(string, str) and bool(re.match('{}$'.format(ACCOUNT_RE), string))) beancount.core.account.join(*components) \uf0c1 Join the names with the account separator. Parameters: *components \u2013 Strings, the components of an account name. Returns: A string, joined in a single account name. Source code in beancount/core/account.py def join(*components): \"\"\"Join the names with the account separator. Args: *components: Strings, the components of an account name. Returns: A string, joined in a single account name. \"\"\" return sep.join(components) beancount.core.account.leaf(account_name) \uf0c1 Get the name of the leaf of this account. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. Source code in beancount/core/account.py def leaf(account_name): \"\"\"Get the name of the leaf of this account. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. \"\"\" assert isinstance(account_name, str) return account_name.split(sep)[-1] if account_name else None beancount.core.account.parent(account_name) \uf0c1 Return the name of the parent account of the given account. Parameters: account_name \u2013 A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. Source code in beancount/core/account.py def parent(account_name): \"\"\"Return the name of the parent account of the given account. Args: account_name: A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. \"\"\" assert isinstance(account_name, str), account_name if not account_name: return None components = account_name.split(sep) components.pop(-1) return sep.join(components) beancount.core.account.parent_matcher(account_name) \uf0c1 Build a predicate that returns whether an account is under the given one. Parameters: account_name \u2013 The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of account_name . Source code in beancount/core/account.py def parent_matcher(account_name): \"\"\"Build a predicate that returns whether an account is under the given one. Args: account_name: The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of ``account_name``. \"\"\" return re.compile(r'{}($|{})'.format(re.escape(account_name), sep)).match beancount.core.account.parents(account_name) \uf0c1 A generator of the names of the parents of this account, including this account. Parameters: account_name \u2013 The name of the account we want to start iterating from. Returns: A generator of account name strings. Source code in beancount/core/account.py def parents(account_name): \"\"\"A generator of the names of the parents of this account, including this account. Args: account_name: The name of the account we want to start iterating from. Returns: A generator of account name strings. \"\"\" while account_name: yield account_name account_name = parent(account_name) beancount.core.account.root(num_components, account_name) \uf0c1 Return the first few components of an account's name. Parameters: num_components \u2013 An integer, the number of components to return. account_name \u2013 A string, an account name. Returns: A string, the account root up to 'num_components' components. Source code in beancount/core/account.py def root(num_components, account_name): \"\"\"Return the first few components of an account's name. Args: num_components: An integer, the number of components to return. account_name: A string, an account name. Returns: A string, the account root up to 'num_components' components. \"\"\" return join(*(split(account_name)[:num_components])) beancount.core.account.sans_root(account_name) \uf0c1 Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. Source code in beancount/core/account.py def sans_root(account_name): \"\"\"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. \"\"\" assert isinstance(account_name, str) components = account_name.split(sep)[1:] return join(*components) if account_name else None beancount.core.account.split(account_name) \uf0c1 Split an account's name into its components. Parameters: account_name \u2013 A string, an account name. Returns: A list of strings, the components of the account name (without the separators). Source code in beancount/core/account.py def split(account_name): \"\"\"Split an account's name into its components. Args: account_name: A string, an account name. Returns: A list of strings, the components of the account name (without the separators). \"\"\" return account_name.split(sep) beancount.core.account.walk(root_directory) \uf0c1 A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Parameters: root_directory \u2013 A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). Source code in beancount/core/account.py def walk(root_directory): \"\"\"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Args: root_directory: A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). \"\"\" for root, dirs, files in os.walk(root_directory): dirs.sort() files.sort() relroot = root[len(root_directory)+1:] account_name = relroot.replace(os.sep, sep) if is_valid(account_name): yield (root, account_name, dirs, files) beancount.core.account_types \uf0c1 Definition for global account types. This is where we keep the global account types value and definition. Note that it's unfortunate that we're using globals and side-effect here, but this is the best solution in the short-term, the account types are used in too many places to pass around that state everywhere. Maybe we change this later on. beancount.core.account_types.AccountTypes ( tuple ) \uf0c1 AccountTypes(assets, liabilities, equity, income, expenses) beancount.core.account_types.AccountTypes.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/account_types.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.account_types.AccountTypes.__new__(_cls, assets, liabilities, equity, income, expenses) special staticmethod \uf0c1 Create new instance of AccountTypes(assets, liabilities, equity, income, expenses) beancount.core.account_types.AccountTypes.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/account_types.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.account_types.get_account_sign(account_name, account_types=None) \uf0c1 Return the sign of the normal balance of a particular account. Parameters: account_name \u2013 A string, the name of the account whose sign is to return. account_types \u2013 An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. Source code in beancount/core/account_types.py def get_account_sign(account_name, account_types=None): \"\"\"Return the sign of the normal balance of a particular account. Args: account_name: A string, the name of the account whose sign is to return. account_types: An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. \"\"\" if account_types is None: account_types = DEFAULT_ACCOUNT_TYPES assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) account_type = get_account_type(account_name) return (+1 if account_type in (account_types.assets, account_types.expenses) else -1) beancount.core.account_types.get_account_sort_key(account_types, account_name) \uf0c1 Return a tuple that can be used to order/sort account names. Parameters: account_types \u2013 An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. Source code in beancount/core/account_types.py def get_account_sort_key(account_types, account_name): \"\"\"Return a tuple that can be used to order/sort account names. Args: account_types: An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. \"\"\" return (account_types.index(get_account_type(account_name)), account_name) beancount.core.account_types.get_account_type(account_name) \uf0c1 Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_name \u2013 A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. Source code in beancount/core/account_types.py def get_account_type(account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_name: A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) return account.split(account_name)[0] beancount.core.account_types.is_account_type(account_type, account_name) \uf0c1 Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_type \u2013 A string, the prefix type of the account. account_name \u2013 A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. Source code in beancount/core/account_types.py def is_account_type(account_type, account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_type: A string, the prefix type of the account. account_name: A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. \"\"\" return bool(re.match('^{}{}'.format(account_type, account.sep), account_name)) beancount.core.account_types.is_balance_sheet_account(account_name, account_types) \uf0c1 Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. Source code in beancount/core/account_types.py def is_balance_sheet_account(account_name, account_types): \"\"\"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.assets, account_types.liabilities, account_types.equity) beancount.core.account_types.is_equity_account(account_name, account_types) \uf0c1 Return true if the given account is an equity account. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. Source code in beancount/core/account_types.py def is_equity_account(account_name, account_types): \"\"\"Return true if the given account is an equity account. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type == account_types.equity beancount.core.account_types.is_income_statement_account(account_name, account_types) \uf0c1 Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. Source code in beancount/core/account_types.py def is_income_statement_account(account_name, account_types): \"\"\"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.income, account_types.expenses) beancount.core.account_types.is_root_account(account_name, account_types=None) \uf0c1 Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Parameters: account_name \u2013 A string, the name of the account to check for. account_types \u2013 An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. Source code in beancount/core/account_types.py def is_root_account(account_name, account_types=None): \"\"\"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Args: account_name: A string, the name of the account to check for. account_types: An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) if account_types is not None: assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) return account_name in account_types else: return (account_name and bool(re.match(r'([A-Z][A-Za-z0-9\\-]+)$', account_name))) beancount.core.amount \uf0c1 Amount class. This simple class is used to associate a number of units of a currency with its currency: (number, currency). beancount.core.amount.Amount ( _Amount ) \uf0c1 An 'Amount' represents a number of a particular unit of something. It's essentially a typed number, with corresponding manipulation operations defined on it. beancount.core.amount.Amount.__bool__(self) special \uf0c1 Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. Source code in beancount/core/amount.py def __bool__(self): \"\"\"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. \"\"\" return self.number != ZERO beancount.core.amount.Amount.__eq__(self, other) special \uf0c1 Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. Source code in beancount/core/amount.py def __eq__(self, other): \"\"\"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. \"\"\" if other is None: return False return (self.number, self.currency) == (other.number, other.currency) beancount.core.amount.Amount.__hash__(self) special \uf0c1 A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. Source code in beancount/core/amount.py def __hash__(self): \"\"\"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. \"\"\" return hash((self.number, self.currency)) beancount.core.amount.Amount.__lt__(self, other) special \uf0c1 Ordering comparison. This is used in the sorting key of positions. Parameters: other \u2013 An instance of Amount. Returns: True if this is less than the other Amount. Source code in beancount/core/amount.py def __lt__(self, other): \"\"\"Ordering comparison. This is used in the sorting key of positions. Args: other: An instance of Amount. Returns: True if this is less than the other Amount. \"\"\" return sortkey(self) < sortkey(other) beancount.core.amount.Amount.__neg__(self) special \uf0c1 Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. Source code in beancount/core/amount.py def __neg__(self): \"\"\"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. \"\"\" return Amount(-self.number, self.currency) beancount.core.amount.Amount.__new__(cls, number, currency) special staticmethod \uf0c1 Constructor from a number and currency. Parameters: number \u2013 A string or Decimal instance. Will get converted automatically. currency \u2013 A string, the currency symbol to use. Source code in beancount/core/amount.py def __new__(cls, number, currency): \"\"\"Constructor from a number and currency. Args: number: A string or Decimal instance. Will get converted automatically. currency: A string, the currency symbol to use. \"\"\" assert isinstance(number, Amount.valid_types_number), repr(number) assert isinstance(currency, Amount.valid_types_currency), repr(currency) return _Amount.__new__(cls, number, currency) beancount.core.amount.Amount.__repr__(self) special \uf0c1 Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string() beancount.core.amount.Amount.__str__(self) special \uf0c1 Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string() beancount.core.amount.Amount.from_string(string) staticmethod \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.Amount.to_string(self, dformat=) \uf0c1 Convert an Amount instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def to_string(self, dformat=DEFAULT_FORMATTER): \"\"\"Convert an Amount instance to a printable string. Args: dformat: An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. \"\"\" number_fmt = (dformat.format(self.number, self.currency) if isinstance(self.number, Decimal) else str(self.number)) return \"{} {}\".format(number_fmt, self.currency) beancount.core.amount.A(string) \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.abs(amount) \uf0c1 Return the absolute value of the given amount. Parameters: amount \u2013 An instance of Amount. Returns: An instance of Amount. Source code in beancount/core/amount.py def abs(amount): \"\"\"Return the absolute value of the given amount. Args: amount: An instance of Amount. Returns: An instance of Amount. \"\"\" return (amount if amount.number >= ZERO else Amount(-amount.number, amount.currency)) beancount.core.amount.add(amount1, amount2) \uf0c1 Add the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def add(amount1, amount2): \"\"\"Add the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number + amount2.number, amount1.currency) beancount.core.amount.div(amount, number) \uf0c1 Divide the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. Source code in beancount/core/amount.py def div(amount, number): \"\"\"Divide the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number / number, amount.currency) beancount.core.amount.from_string(string) \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.mul(amount, number) \uf0c1 Multiply the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. Source code in beancount/core/amount.py def mul(amount, number): \"\"\"Multiply the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number * number, amount.currency) beancount.core.amount.sortkey(amount) \uf0c1 A comparison function that sorts by currency first. Parameters: amount \u2013 An instance of Amount. Returns: A sort key, composed of the currency first and then the number. Source code in beancount/core/amount.py def sortkey(amount): \"\"\"A comparison function that sorts by currency first. Args: amount: An instance of Amount. Returns: A sort key, composed of the currency first and then the number. \"\"\" return (amount.currency, amount.number) beancount.core.amount.sub(amount1, amount2) \uf0c1 Subtract the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def sub(amount1, amount2): \"\"\"Subtract the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number - amount2.number, amount1.currency) beancount.core.compare \uf0c1 Comparison helpers for data objects. beancount.core.compare.CompareError ( tuple ) \uf0c1 CompareError(source, message, entry) beancount.core.compare.CompareError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/compare.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.compare.CompareError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CompareError(source, message, entry) beancount.core.compare.CompareError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/compare.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.compare.compare_entries(entries1, entries2) \uf0c1 Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Parameters: entries1 \u2013 A list of directives of any type. entries2 \u2013 Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are \u2013 success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def compare_entries(entries1, entries2): \"\"\"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Args: entries1: A list of directives of any type. entries2: Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are: success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Raises: ValueError: If a duplicate entry is found. \"\"\" hashes1, errors1 = hash_entries(entries1, exclude_meta=True) hashes2, errors2 = hash_entries(entries2, exclude_meta=True) keys1 = set(hashes1.keys()) keys2 = set(hashes2.keys()) if errors1 or errors2: error = (errors1 + errors2)[0] raise ValueError(str(error)) same = keys1 == keys2 missing1 = data.sorted([hashes1[key] for key in keys1 - keys2]) missing2 = data.sorted([hashes2[key] for key in keys2 - keys1]) return (same, missing1, missing2) beancount.core.compare.excludes_entries(subset_entries, entries) \uf0c1 Check that a list of entries does not appear in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def excludes_entries(subset_entries, entries): \"\"\"Check that a list of entries does not appear in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) intersection = keys.intersection(subset_keys) excludes = not bool(intersection) extra = data.sorted([subset_hashes[key] for key in intersection]) return (excludes, extra) beancount.core.compare.hash_entries(entries, exclude_meta=False) \uf0c1 Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Parameters: entries \u2013 A list of directives. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. Source code in beancount/core/compare.py def hash_entries(entries, exclude_meta=False): \"\"\"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Args: entries: A list of directives. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. \"\"\" entry_hash_dict = {} errors = [] num_legal_duplicates = 0 for entry in entries: hash_ = hash_entry(entry, exclude_meta) if hash_ in entry_hash_dict: if isinstance(entry, Price): # Note: Allow duplicate Price entries, they should be common # because of the nature of stock markets (if they're closed, the # data source is likely to return an entry for the previously # available date, which may already have been fetched). num_legal_duplicates += 1 else: other_entry = entry_hash_dict[hash_] errors.append( CompareError(entry.meta, \"Duplicate entry: {} == {}\".format(entry, other_entry), entry)) entry_hash_dict[hash_] = entry if not errors: assert len(entry_hash_dict) + num_legal_duplicates == len(entries), ( len(entry_hash_dict), len(entries), num_legal_duplicates) return entry_hash_dict, errors beancount.core.compare.hash_entry(entry, exclude_meta=False) \uf0c1 Compute the stable hash of a single entry. Parameters: entry \u2013 A directive instance. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. Source code in beancount/core/compare.py def hash_entry(entry, exclude_meta=False): \"\"\"Compute the stable hash of a single entry. Args: entry: A directive instance. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. \"\"\" return stable_hash_namedtuple(entry, IGNORED_FIELD_NAMES if exclude_meta else frozenset()) beancount.core.compare.includes_entries(subset_entries, entries) \uf0c1 Check if a list of entries is included in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def includes_entries(subset_entries, entries): \"\"\"Check if a list of entries is included in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) includes = subset_keys.issubset(keys) missing = data.sorted([subset_hashes[key] for key in subset_keys - keys]) return (includes, missing) beancount.core.compare.stable_hash_namedtuple(objtuple, ignore=frozenset()) \uf0c1 Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Parameters: objtuple \u2013 A tuple object or other. ignore \u2013 A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. Source code in beancount/core/compare.py def stable_hash_namedtuple(objtuple, ignore=frozenset()): \"\"\"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Args: objtuple: A tuple object or other. ignore: A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. \"\"\" # Note: this routine is slow and would stand to be implemented in C. hashobj = hashlib.md5() for attr_name, attr_value in zip(objtuple._fields, objtuple): if attr_name in ignore: continue if isinstance(attr_value, (list, set, frozenset)): subhashes = set() for element in attr_value: if isinstance(element, tuple): subhashes.add(stable_hash_namedtuple(element, ignore)) else: md5 = hashlib.md5() md5.update(str(element).encode()) subhashes.add(md5.hexdigest()) for subhash in sorted(subhashes): hashobj.update(subhash.encode()) else: hashobj.update(str(attr_value).encode()) return hashobj.hexdigest() beancount.core.convert \uf0c1 Conversions from Position (or Posting) to units, cost, weight, market value. Units: Just the primary amount of the position. Cost: The cost basis of the position, if available. Weight: The cost basis or price of the position. Market Value: The units converted to a value via a price map. To convert an inventory's contents, simply use these functions in conjunction with Inventory.reduce() , like cost_inv = inv.reduce(convert.get_cost) This module equivalently converts Position and Posting instances. Note that we're specifically avoiding to create an import dependency on beancount.core.data in order to keep this module isolatable, but it works on postings due to duck-typing. Function named get_*() are used to compute values from postings to their price currency. Functions named convert_*() are used to convert postings and amounts to any currency. beancount.core.convert.convert_amount(amt, target_currency, price_map, date=None, via=None) \uf0c1 Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Parameters: amt \u2013 An instance of Amount. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. via \u2013 A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. Source code in beancount/core/convert.py def convert_amount(amt, target_currency, price_map, date=None, via=None): \"\"\"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Args: amt: An instance of Amount. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. via: A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. \"\"\" # First, attempt to convert directly. This should be the most # straightforward conversion. base_quote = (amt.currency, target_currency) _, rate = prices.get_price(price_map, base_quote, date) if rate is not None: # On success, just make the conversion directly. return Amount(amt.number * rate, target_currency) elif via: assert isinstance(via, (tuple, list)) # A price is unavailable, attempt to convert via cost/price currency # hop, if the value currency isn't the target currency. for implied_currency in via: if implied_currency == target_currency: continue base_quote1 = (amt.currency, implied_currency) _, rate1 = prices.get_price(price_map, base_quote1, date) if rate1 is not None: base_quote2 = (implied_currency, target_currency) _, rate2 = prices.get_price(price_map, base_quote2, date) if rate2 is not None: return Amount(amt.number * rate1 * rate2, target_currency) # We failed to infer a conversion rate; return the amt. return amt beancount.core.convert.convert_position(pos, target_currency, price_map, date=None) \uf0c1 Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Parameters: pos \u2013 An instance of Position or Posting, equivalently. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) Source code in beancount/core/convert.py def convert_position(pos, target_currency, price_map, date=None): \"\"\"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Args: pos: An instance of Position or Posting, equivalently. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) \"\"\" cost = pos.cost value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) return convert_amount(pos.units, target_currency, price_map, date=date, via=(value_currency,)) beancount.core.convert.get_cost(pos) \uf0c1 Return the total cost of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_cost(pos): \"\"\"Return the total cost of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' cost = pos.cost return (Amount(cost.number * pos.units.number, cost.currency) if (isinstance(cost, Cost) and isinstance(cost.number, Decimal)) else pos.units) beancount.core.convert.get_units(pos) \uf0c1 Return the units of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_units(pos): \"\"\"Return the units of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' return pos.units beancount.core.convert.get_value(pos, price_map, date=None) \uf0c1 Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see convert_*() functions below. Parameters: pos \u2013 An instance of Position or Posting, equivalently. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. Source code in beancount/core/convert.py def get_value(pos, price_map, date=None): \"\"\"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see ``convert_*()`` functions below. Args: pos: An instance of Position or Posting, equivalently. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # Try to infer what the cost/price currency should be. value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) if isinstance(value_currency, str): # We have a value currency; hit the price database. base_quote = (units.currency, value_currency) _, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: return Amount(units.number * price_number, value_currency) # We failed to infer a conversion rate; return the units. return units beancount.core.convert.get_weight(pos) \uf0c1 Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a key element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_weight(pos): \"\"\"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a *key* element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # It the object has a cost, use that as the weight, to balance. if isinstance(cost, Cost) and isinstance(cost.number, Decimal): weight = Amount(cost.number * pos.units.number, cost.currency) else: # Otherwise use the postings. weight = units # Unless there is a price available; use that if present. if not isinstance(pos, Position): price = pos.price if price is not None: # Note: Here we could assert that price.currency == units.currency. if price.number is MISSING or units.number is MISSING: converted_number = MISSING else: converted_number = price.number * units.number weight = Amount(converted_number, price.currency) return weight beancount.core.data \uf0c1 Basic data structures used to represent the Ledger entries. beancount.core.data.Balance ( tuple ) \uf0c1 Balance(meta, date, account, amount, tolerance, diff_amount) beancount.core.data.Balance.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Balance.__new__(_cls, meta, date, account, amount, tolerance, diff_amount) special staticmethod \uf0c1 Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount) beancount.core.data.Balance.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Close ( tuple ) \uf0c1 Close(meta, date, account) beancount.core.data.Close.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Close.__new__(_cls, meta, date, account) special staticmethod \uf0c1 Create new instance of Close(meta, date, account) beancount.core.data.Close.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Commodity ( tuple ) \uf0c1 Commodity(meta, date, currency) beancount.core.data.Commodity.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Commodity.__new__(_cls, meta, date, currency) special staticmethod \uf0c1 Create new instance of Commodity(meta, date, currency) beancount.core.data.Commodity.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Custom ( tuple ) \uf0c1 Custom(meta, date, type, values) beancount.core.data.Custom.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Custom.__new__(_cls, meta, date, type, values) special staticmethod \uf0c1 Create new instance of Custom(meta, date, type, values) beancount.core.data.Custom.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Document ( tuple ) \uf0c1 Document(meta, date, account, filename, tags, links) beancount.core.data.Document.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Document.__new__(_cls, meta, date, account, filename, tags, links) special staticmethod \uf0c1 Create new instance of Document(meta, date, account, filename, tags, links) beancount.core.data.Document.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Event ( tuple ) \uf0c1 Event(meta, date, type, description) beancount.core.data.Event.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Event.__new__(_cls, meta, date, type, description) special staticmethod \uf0c1 Create new instance of Event(meta, date, type, description) beancount.core.data.Event.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Note ( tuple ) \uf0c1 Note(meta, date, account, comment) beancount.core.data.Note.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Note.__new__(_cls, meta, date, account, comment) special staticmethod \uf0c1 Create new instance of Note(meta, date, account, comment) beancount.core.data.Note.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Open ( tuple ) \uf0c1 Open(meta, date, account, currencies, booking) beancount.core.data.Open.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Open.__new__(_cls, meta, date, account, currencies, booking) special staticmethod \uf0c1 Create new instance of Open(meta, date, account, currencies, booking) beancount.core.data.Open.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Pad ( tuple ) \uf0c1 Pad(meta, date, account, source_account) beancount.core.data.Pad.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Pad.__new__(_cls, meta, date, account, source_account) special staticmethod \uf0c1 Create new instance of Pad(meta, date, account, source_account) beancount.core.data.Pad.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Posting ( tuple ) \uf0c1 Posting(account, units, cost, price, flag, meta) beancount.core.data.Posting.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Posting.__new__(_cls, account, units, cost, price, flag, meta) special staticmethod \uf0c1 Create new instance of Posting(account, units, cost, price, flag, meta) beancount.core.data.Posting.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Price ( tuple ) \uf0c1 Price(meta, date, currency, amount) beancount.core.data.Price.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Price.__new__(_cls, meta, date, currency, amount) special staticmethod \uf0c1 Create new instance of Price(meta, date, currency, amount) beancount.core.data.Price.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Query ( tuple ) \uf0c1 Query(meta, date, name, query_string) beancount.core.data.Query.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Query.__new__(_cls, meta, date, name, query_string) special staticmethod \uf0c1 Create new instance of Query(meta, date, name, query_string) beancount.core.data.Query.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Transaction ( tuple ) \uf0c1 Transaction(meta, date, flag, payee, narration, tags, links, postings) beancount.core.data.Transaction.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Transaction.__new__(_cls, meta, date, flag, payee, narration, tags, links, postings) special staticmethod \uf0c1 Create new instance of Transaction(meta, date, flag, payee, narration, tags, links, postings) beancount.core.data.Transaction.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.TxnPosting ( tuple ) \uf0c1 TxnPosting(txn, posting) beancount.core.data.TxnPosting.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.TxnPosting.__new__(_cls, txn, posting) special staticmethod \uf0c1 Create new instance of TxnPosting(txn, posting) beancount.core.data.TxnPosting.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.create_simple_posting(entry, account, number, currency) \uf0c1 Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting(entry, account, number, currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if number is None: units = None else: if not isinstance(number, Decimal): number = D(number) units = Amount(number, currency) posting = Posting(account, units, None, None, None, None) if entry is not None: entry.postings.append(posting) return posting beancount.core.data.create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency) \uf0c1 Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. cost_number \u2013 A Decimal number or string to use for the posting's cost Amount. cost_currency \u2013 a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. cost_number: A Decimal number or string to use for the posting's cost Amount. cost_currency: a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if not isinstance(number, Decimal): number = D(number) if cost_number and not isinstance(cost_number, Decimal): cost_number = D(cost_number) units = Amount(number, currency) cost = Cost(cost_number, cost_currency, None, None) posting = Posting(account, units, cost, None, None, None) if entry is not None: entry.postings.append(posting) return posting beancount.core.data.entry_sortkey(entry) \uf0c1 Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. Source code in beancount/core/data.py def entry_sortkey(entry): \"\"\"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. \"\"\" return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"]) beancount.core.data.filter_txns(entries) \uf0c1 A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Parameters: entries \u2013 A list of directives. Yields: A sorted list of only the Transaction directives. Source code in beancount/core/data.py def filter_txns(entries): \"\"\"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Args: entries: A list of directives. Yields: A sorted list of only the Transaction directives. \"\"\" for entry in entries: if isinstance(entry, Transaction): yield entry beancount.core.data.find_closest(entries, filename, lineno) \uf0c1 Find the closest entry from entries to (filename, lineno). Parameters: entries \u2013 A list of directives. filename \u2013 A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno \u2013 An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. Source code in beancount/core/data.py def find_closest(entries, filename, lineno): \"\"\"Find the closest entry from entries to (filename, lineno). Args: entries: A list of directives. filename: A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno: An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. \"\"\" min_diffline = sys.maxsize closest_entry = None for entry in entries: emeta = entry.meta if emeta[\"filename\"] == filename and emeta[\"lineno\"] > 0: diffline = lineno - emeta[\"lineno\"] if 0 <= diffline < min_diffline: min_diffline = diffline closest_entry = entry return closest_entry beancount.core.data.get_entry(posting_or_entry) \uf0c1 Return the entry associated with the posting or entry. Parameters: entry \u2013 A TxnPosting or entry instance Returns: A datetime instance. Source code in beancount/core/data.py def get_entry(posting_or_entry): \"\"\"Return the entry associated with the posting or entry. Args: entry: A TxnPosting or entry instance Returns: A datetime instance. \"\"\" return (posting_or_entry.txn if isinstance(posting_or_entry, TxnPosting) else posting_or_entry) beancount.core.data.has_entry_account_component(entry, component) \uf0c1 Return true if one of the entry's postings has an account component. Parameters: entry \u2013 A Transaction entry. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/data.py def has_entry_account_component(entry, component): \"\"\"Return true if one of the entry's postings has an account component. Args: entry: A Transaction entry. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return (isinstance(entry, Transaction) and any(has_component(posting.account, component) for posting in entry.postings)) beancount.core.data.iter_entry_dates(entries, date_begin, date_end) \uf0c1 Iterate over the entries in a date window. Parameters: entries \u2013 A date-sorted list of dated directives. date_begin \u2013 A datetime.date instance, the first date to include. date_end \u2013 A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. Source code in beancount/core/data.py def iter_entry_dates(entries, date_begin, date_end): \"\"\"Iterate over the entries in a date window. Args: entries: A date-sorted list of dated directives. date_begin: A datetime.date instance, the first date to include. date_end: A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. \"\"\" getdate = lambda entry: entry.date index_begin = bisect_left_with_key(entries, date_begin, key=getdate) index_end = bisect_left_with_key(entries, date_end, key=getdate) for index in range(index_begin, index_end): yield entries[index] beancount.core.data.new_directive(clsname, fields) \uf0c1 Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Parameters: name \u2013 A string, the capitalized name of the directive. fields ( List[Tuple] ) \u2013 A string or the list of strings, names for the fields to add to the base tuple. Returns: \u2013 A type object for the new directive type. Source code in beancount/core/data.py def new_directive(clsname, fields: List[Tuple]) -> NamedTuple: \"\"\"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Args: name: A string, the capitalized name of the directive. fields: A string or the list of strings, names for the fields to add to the base tuple. Returns: A type object for the new directive type. \"\"\" return NamedTuple( clsname, [('meta', Meta), ('date', datetime.date)] + fields) beancount.core.data.new_metadata(filename, lineno, kvlist=None) \uf0c1 Create a new metadata container from the filename and line number. Parameters: filename \u2013 A string, the filename for the creator of this directive. lineno \u2013 An integer, the line number where the directive has been created. kvlist \u2013 An optional container of key-values. Returns: A metadata dict. Source code in beancount/core/data.py def new_metadata(filename, lineno, kvlist=None): \"\"\"Create a new metadata container from the filename and line number. Args: filename: A string, the filename for the creator of this directive. lineno: An integer, the line number where the directive has been created. kvlist: An optional container of key-values. Returns: A metadata dict. \"\"\" meta = {'filename': filename, 'lineno': lineno} if kvlist: meta.update(kvlist) return meta beancount.core.data.posting_has_conversion(posting) \uf0c1 Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Parameters: posting \u2013 an instance of Posting Returns: A boolean, true if this posting has a price conversion. Source code in beancount/core/data.py def posting_has_conversion(posting): \"\"\"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Args: posting: an instance of Posting Return: A boolean, true if this posting has a price conversion. \"\"\" return (posting.cost is None and posting.price is not None) beancount.core.data.posting_sortkey(entry) \uf0c1 Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. Source code in beancount/core/data.py def posting_sortkey(entry): \"\"\"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. \"\"\" if isinstance(entry, TxnPosting): entry = entry.txn return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"]) beancount.core.data.remove_account_postings(account, entries) \uf0c1 Remove all postings with the given account. Parameters: account \u2013 A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. Source code in beancount/core/data.py def remove_account_postings(account, entries): \"\"\"Remove all postings with the given account. Args: account: A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, Transaction) and ( any(posting.account == account for posting in entry.postings)): entry = entry._replace(postings=[posting for posting in entry.postings if posting.account != account]) new_entries.append(entry) return new_entries beancount.core.data.sanity_check_types(entry, allow_none_for_tags_and_links=False) \uf0c1 Check that the entry and its postings has all correct data types. Parameters: entry \u2013 An instance of one of the entries to be checked. allow_none_for_tags_and_links \u2013 A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Exceptions: AssertionError \u2013 If there is anything that is unexpected, raises an exception. Source code in beancount/core/data.py def sanity_check_types(entry, allow_none_for_tags_and_links=False): \"\"\"Check that the entry and its postings has all correct data types. Args: entry: An instance of one of the entries to be checked. allow_none_for_tags_and_links: A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Raises: AssertionError: If there is anything that is unexpected, raises an exception. \"\"\" assert isinstance(entry, ALL_DIRECTIVES), \"Invalid directive type\" assert isinstance(entry.meta, dict), \"Invalid type for meta\" assert 'filename' in entry.meta, \"Missing filename in metadata\" assert 'lineno' in entry.meta, \"Missing line number in metadata\" assert isinstance(entry.date, datetime.date), \"Invalid date type\" if isinstance(entry, Transaction): assert isinstance(entry.flag, (NoneType, str)), \"Invalid flag type\" assert isinstance(entry.payee, (NoneType, str)), \"Invalid payee type\" assert isinstance(entry.narration, (NoneType, str)), \"Invalid narration type\" set_types = ((NoneType, set, frozenset) if allow_none_for_tags_and_links else (set, frozenset)) assert isinstance(entry.tags, set_types), ( \"Invalid tags type: {}\".format(type(entry.tags))) assert isinstance(entry.links, set_types), ( \"Invalid links type: {}\".format(type(entry.links))) assert isinstance(entry.postings, list), \"Invalid postings list type\" for posting in entry.postings: assert isinstance(posting, Posting), \"Invalid posting type\" assert isinstance(posting.account, str), \"Invalid account type\" assert isinstance(posting.units, (Amount, NoneType)), \"Invalid units type\" assert isinstance(posting.cost, (Cost, CostSpec, NoneType)), \"Invalid cost type\" assert isinstance(posting.price, (Amount, NoneType)), \"Invalid price type\" assert isinstance(posting.flag, (str, NoneType)), \"Invalid flag type\" beancount.core.data.sorted(entries) \uf0c1 A convenience to sort a list of entries, using entry_sortkey(). Parameters: entries \u2013 A list of directives. Returns: A sorted list of directives. Source code in beancount/core/data.py def sorted(entries): \"\"\"A convenience to sort a list of entries, using entry_sortkey(). Args: entries: A list of directives. Returns: A sorted list of directives. \"\"\" return builtins.sorted(entries, key=entry_sortkey) beancount.core.data.transaction_has_conversion(transaction) \uf0c1 Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Parameters: transaction \u2013 an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. Source code in beancount/core/data.py def transaction_has_conversion(transaction): \"\"\"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Args: transaction: an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. \"\"\" assert isinstance(transaction, Transaction), ( \"Invalid type of entry for transaction: {}\".format(transaction)) for posting in transaction.postings: if posting_has_conversion(posting): return True return False beancount.core.display_context \uf0c1 A settings class to offer control over the number of digits rendered. This module contains routines that can accumulate information on the width and precision of numbers to be rendered and derive the precision required to render all of them consistently and under certain common alignment requirements. This is required in order to output neatly lined up columns of numbers in various styles. A common case is that the precision can be observed for numbers present in the input file. This display precision can be used as the \"precision by default\" if we write a routine for which it is inconvenient to feed all the numbers to build such an accumulator. Here are all the aspects supported by this module: PRECISION: Numbers for a particular currency are always rendered to the same precision, and they can be rendered to one of two precisions; either the most common number of fractional digits, or the maximum number of digits seen (this is useful for rendering prices). ALIGNMENT: Several alignment methods are supported. \"natural\": Render the strings as small as possible with no padding, but to their currency's precision. Like this: '1.2345' '764' '-7,409.01' '0.00000125' \"dot-aligned\": The periods will align vertically, the left and right sides are padded so that the column of numbers has the same width: ' 1.2345 ' ' 764 ' '-7,409.01 ' ' 0.00000125' \"right\": The strings are all flushed right, the left side is padded so that the column of numbers has the same width: ' 1.2345' ' 764' ' -7,409.01' ' 0.00000125' SIGN: If a negative sign is present in the input numbers, the rendered numbers reserve a space for it. If not, then we save the space. COMMAS: If the user requests to render commas, commas are rendered in the output. RESERVED: A number of extra integral digits reserved on the left in order to allow rendering novel numbers that haven't yet been seen. For example, balances may contains much larger numbers than the numbers seen in input files, and these need to be accommodated when aligning to the right. beancount.core.display_context.Align ( Enum ) \uf0c1 Alignment style for numbers. beancount.core.display_context.DisplayContext \uf0c1 A builder object used to construct a DisplayContext from a series of numbers. Attributes: Name Type Description ccontexts A dict of currency string to CurrencyContext instance. commas A bool, true if we should render commas. This just gets propagated onwards as the default value of to build with. beancount.core.display_context.DisplayContext.build(self, alignment=, precision=, commas=None, reserved=0) \uf0c1 Build a formatter for the given display context. Parameters: alignment \u2013 The desired alignment. precision \u2013 The desired precision. commas \u2013 Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved \u2013 An integer, the number of extra digits to be allocated in the maximum width calculations. Source code in beancount/core/display_context.py def build(self, alignment=Align.NATURAL, precision=Precision.MOST_COMMON, commas=None, reserved=0): \"\"\"Build a formatter for the given display context. Args: alignment: The desired alignment. precision: The desired precision. commas: Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved: An integer, the number of extra digits to be allocated in the maximum width calculations. \"\"\" if commas is None: commas = self.commas if alignment == Align.NATURAL: build_method = self._build_natural elif alignment == Align.RIGHT: build_method = self._build_right elif alignment == Align.DOT: build_method = self._build_dot else: raise ValueError(\"Unknown alignment: {}\".format(alignment)) fmtstrings = build_method(precision, commas, reserved) return DisplayFormatter(self, precision, fmtstrings) beancount.core.display_context.DisplayContext.quantize(self, number, currency, precision=) \uf0c1 Quantize the given number to the given precision. Parameters: number \u2013 A Decimal instance, the number to be quantized. currency \u2013 A currency string. precision \u2013 Which precision to use. Returns: A Decimal instance, the quantized number. Source code in beancount/core/display_context.py def quantize(self, number, currency, precision=Precision.MOST_COMMON): \"\"\"Quantize the given number to the given precision. Args: number: A Decimal instance, the number to be quantized. currency: A currency string. precision: Which precision to use. Returns: A Decimal instance, the quantized number. \"\"\" assert isinstance(number, Decimal), \"Invalid data: {}\".format(number) ccontext = self.ccontexts[currency] num_fractional_digits = ccontext.get_fractional(precision) if num_fractional_digits is None: # Note: We could probably logging.warn() this situation here. return number qdigit = Decimal(1).scaleb(-num_fractional_digits) return number.quantize(qdigit) beancount.core.display_context.DisplayContext.set_commas(self, commas) \uf0c1 Set the default value for rendering commas. Source code in beancount/core/display_context.py def set_commas(self, commas): \"\"\"Set the default value for rendering commas.\"\"\" self.commas = commas beancount.core.display_context.DisplayContext.update(self, number, currency='__default__') \uf0c1 Update the builder with the given number for the given currency. Parameters: number \u2013 An instance of Decimal to consider for this currency. currency \u2013 An optional string, the currency this numbers applies to. Source code in beancount/core/display_context.py def update(self, number, currency='__default__'): \"\"\"Update the builder with the given number for the given currency. Args: number: An instance of Decimal to consider for this currency. currency: An optional string, the currency this numbers applies to. \"\"\" self.ccontexts[currency].update(number) beancount.core.display_context.DisplayFormatter \uf0c1 A class used to contain various settings that control how we output numbers. In particular, the precision used for each currency, and whether or not commas should be printed. This object is intended to be passed around to all functions that format numbers to strings. Attributes: Name Type Description dcontext A DisplayContext instance. precision An enum of Precision from which it was built. fmtstrings A dict of currency to pre-baked format strings for it. fmtfuncs A dict of currency to pre-baked formatting functions for it. beancount.core.display_context.Precision ( Enum ) \uf0c1 The type of precision required. beancount.core.distribution \uf0c1 A simple accumulator for data about a mathematical distribution. beancount.core.distribution.Distribution \uf0c1 A class that computes a histogram of integer values. This is used to compute a length that will cover at least some decent fraction of the samples. beancount.core.distribution.Distribution.empty(self) \uf0c1 Return true if the distribution is empty. Returns: A boolean. Source code in beancount/core/distribution.py def empty(self): \"\"\"Return true if the distribution is empty. Returns: A boolean. \"\"\" return len(self.hist) == 0 beancount.core.distribution.Distribution.max(self) \uf0c1 Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def max(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[-1] return value beancount.core.distribution.Distribution.min(self) \uf0c1 Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def min(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[0] return value beancount.core.distribution.Distribution.mode(self) \uf0c1 Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def mode(self): \"\"\"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None max_value = 0 max_count = 0 for value, count in sorted(self.hist.items()): if count >= max_count: max_count = count max_value = value return max_value beancount.core.distribution.Distribution.update(self, value) \uf0c1 Add a sample to the distribution. Parameters: value \u2013 A value of the function. Source code in beancount/core/distribution.py def update(self, value): \"\"\"Add a sample to the distribution. Args: value: A value of the function. \"\"\" self.hist[value] += 1 beancount.core.flags \uf0c1 Flag constants. beancount.core.getters \uf0c1 Getter functions that operate on lists of entries to return various lists of things that they reference, accounts, tags, links, currencies, etc. beancount.core.getters.GetAccounts \uf0c1 Accounts gatherer. beancount.core.getters.GetAccounts.Balance(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Close(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Commodity(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Custom(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Document(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Event(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Note(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Open(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Pad(_, entry) \uf0c1 Process a Pad directive. Parameters: entry \u2013 An instance of Pad. Returns: The two accounts of the Pad directive. Source code in beancount/core/getters.py def Pad(_, entry): \"\"\"Process a Pad directive. Args: entry: An instance of Pad. Returns: The two accounts of the Pad directive. \"\"\" return (entry.account, entry.source_account) beancount.core.getters.GetAccounts.Price(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Query(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Transaction(_, entry) \uf0c1 Process a Transaction directive. Parameters: entry \u2013 An instance of Transaction. Yields: The accounts of the legs of the transaction. Source code in beancount/core/getters.py def Transaction(_, entry): \"\"\"Process a Transaction directive. Args: entry: An instance of Transaction. Yields: The accounts of the legs of the transaction. \"\"\" for posting in entry.postings: yield posting.account beancount.core.getters.GetAccounts.get_accounts_use_map(self, entries) \uf0c1 Gather the list of accounts from the list of entries. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(self, entries): \"\"\"Gather the list of accounts from the list of entries. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" accounts_first = {} accounts_last = {} for entry in entries: method = getattr(self, entry.__class__.__name__) for account_ in method(entry): if account_ not in accounts_first: accounts_first[account_] = entry.date accounts_last[account_] = entry.date return accounts_first, accounts_last beancount.core.getters.GetAccounts.get_entry_accounts(self, entry) \uf0c1 Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entry \u2013 A directive instance. Returns: A set of account name strings. Source code in beancount/core/getters.py def get_entry_accounts(self, entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entry: A directive instance. Returns: A set of account name strings. \"\"\" method = getattr(self, entry.__class__.__name__) return set(method(entry)) beancount.core.getters.get_account_components(entries) \uf0c1 Gather all the account components available in the given directives. Parameters: entries \u2013 A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. Source code in beancount/core/getters.py def get_account_components(entries): \"\"\"Gather all the account components available in the given directives. Args: entries: A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. \"\"\" accounts = get_accounts(entries) components = set() for account_name in accounts: components.update(account.split(account_name)) return sorted(components) beancount.core.getters.get_account_open_close(entries) \uf0c1 Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Parameters: entries \u2013 A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. Source code in beancount/core/getters.py def get_account_open_close(entries): \"\"\"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Args: entries: A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. \"\"\" # A dict of account name to (open-entry, close-entry). open_close_map = defaultdict(lambda: [None, None]) for entry in entries: if not isinstance(entry, (Open, Close)): continue open_close = open_close_map[entry.account] index = 0 if isinstance(entry, Open) else 1 previous_entry = open_close[index] if previous_entry is not None: if previous_entry.date <= entry.date: entry = previous_entry open_close[index] = entry return dict(open_close_map) beancount.core.getters.get_accounts(entries) \uf0c1 Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A set of account strings. Source code in beancount/core/getters.py def get_accounts(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A set of account strings. \"\"\" _, accounts_last = _GetAccounts.get_accounts_use_map(entries) return accounts_last.keys() beancount.core.getters.get_accounts_use_map(entries) \uf0c1 Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" return _GetAccounts.get_accounts_use_map(entries) beancount.core.getters.get_active_years(entries) \uf0c1 Yield all the years that have at least one entry in them. Parameters: entries \u2013 A list of directive instances. Yields: Unique dates see in the list of directives. Source code in beancount/core/getters.py def get_active_years(entries): \"\"\"Yield all the years that have at least one entry in them. Args: entries: A list of directive instances. Yields: Unique dates see in the list of directives. \"\"\" seen = set() prev_year = None for entry in entries: year = entry.date.year if year != prev_year: prev_year = year assert year not in seen seen.add(year) yield year beancount.core.getters.get_all_links(entries) \uf0c1 Return a list of all the links seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of links strings. Source code in beancount/core/getters.py def get_all_links(entries): \"\"\"Return a list of all the links seen in the given entries. Args: entries: A list of directive instances. Returns: A set of links strings. \"\"\" all_links = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.links: all_links.update(entry.links) return sorted(all_links) beancount.core.getters.get_all_payees(entries) \uf0c1 Return a list of all the unique payees seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of payee strings. Source code in beancount/core/getters.py def get_all_payees(entries): \"\"\"Return a list of all the unique payees seen in the given entries. Args: entries: A list of directive instances. Returns: A set of payee strings. \"\"\" all_payees = set() for entry in entries: if not isinstance(entry, Transaction): continue all_payees.add(entry.payee) all_payees.discard(None) return sorted(all_payees) beancount.core.getters.get_all_tags(entries) \uf0c1 Return a list of all the tags seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of tag strings. Source code in beancount/core/getters.py def get_all_tags(entries): \"\"\"Return a list of all the tags seen in the given entries. Args: entries: A list of directive instances. Returns: A set of tag strings. \"\"\" all_tags = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.tags: all_tags.update(entry.tags) return sorted(all_tags) beancount.core.getters.get_commodity_map(entries, create_missing=True) \uf0c1 Create map of commodity names to Commodity entries. Parameters: entries \u2013 A list of directive instances. create_missing \u2013 A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. Source code in beancount/core/getters.py def get_commodity_map(entries, create_missing=True): \"\"\"Create map of commodity names to Commodity entries. Args: entries: A list of directive instances. create_missing: A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. \"\"\" if not entries: return {} commodities_map = {} for entry in entries: if isinstance(entry, Commodity): commodities_map[entry.currency] = entry elif isinstance(entry, Transaction): for posting in entry.postings: # Main currency. units = posting.units commodities_map.setdefault(units.currency, None) # Currency in cost. cost = posting.cost if cost: commodities_map.setdefault(cost.currency, None) # Currency in price. price = posting.price if price: commodities_map.setdefault(price.currency, None) elif isinstance(entry, Balance): commodities_map.setdefault(entry.amount.currency, None) elif isinstance(entry, Price): commodities_map.setdefault(entry.currency, None) if create_missing: # Create missing Commodity directives when they haven't been specified explicitly. # (I think it might be better to always do this from the loader.) date = entries[0].date meta = data.new_metadata('', 0) commodities_map = { commodity: (entry if entry is not None else Commodity(meta, date, commodity)) for commodity, entry in commodities_map.items()} return commodities_map beancount.core.getters.get_dict_accounts(account_names) \uf0c1 Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Parameters: account_names \u2013 An iterable of account names (strings) Returns: A nested OrderedDict of account leafs Source code in beancount/core/getters.py def get_dict_accounts(account_names): \"\"\"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Args: account_names: An iterable of account names (strings) Returns: A nested OrderedDict of account leafs \"\"\" leveldict = OrderedDict() for account_name in account_names: nested_dict = leveldict for component in account.split(account_name): nested_dict = nested_dict.setdefault(component, OrderedDict()) nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True return leveldict beancount.core.getters.get_entry_accounts(entry) \uf0c1 Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entries \u2013 A directive instance. Returns: A set of account strings. Source code in beancount/core/getters.py def get_entry_accounts(entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entries: A directive instance. Returns: A set of account strings. \"\"\" return _GetAccounts.get_entry_accounts(entry) beancount.core.getters.get_leveln_parent_accounts(account_names, level, nrepeats=0) \uf0c1 Return a list of all the unique leaf names at level N in an account hierarchy. Parameters: account_names \u2013 A list of account names (strings) level \u2013 The level to cross-cut. 0 is for root accounts. nrepeats \u2013 A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. Source code in beancount/core/getters.py def get_leveln_parent_accounts(account_names, level, nrepeats=0): \"\"\"Return a list of all the unique leaf names at level N in an account hierarchy. Args: account_names: A list of account names (strings) level: The level to cross-cut. 0 is for root accounts. nrepeats: A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. \"\"\" leveldict = defaultdict(int) for account_name in set(account_names): components = account.split(account_name) if level < len(components): leveldict[components[level]] += 1 levels = {level_ for level_, count in leveldict.items() if count > nrepeats} return sorted(levels) beancount.core.getters.get_min_max_dates(entries, types=None) \uf0c1 Return the minimum and maximum dates in the list of entries. Parameters: entries \u2013 A list of directive instances. types \u2013 An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. Source code in beancount/core/getters.py def get_min_max_dates(entries, types=None): \"\"\"Return the minimum and maximum dates in the list of entries. Args: entries: A list of directive instances. types: An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. \"\"\" date_first = date_last = None for entry in entries: if types and not isinstance(entry, types): continue date_first = entry.date break for entry in reversed(entries): if types and not isinstance(entry, types): continue date_last = entry.date break return (date_first, date_last) beancount.core.getters.get_values_meta(name_to_entries_map, *meta_keys, *, default=None) \uf0c1 Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Parameters: name_to_entries_map \u2013 A dict of something to an entry or None. meta_keys \u2013 A list of strings, the keys to fetch from the metadata. default \u2013 The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. Source code in beancount/core/getters.py def get_values_meta(name_to_entries_map, *meta_keys, default=None): \"\"\"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Args: name_to_entries_map: A dict of something to an entry or None. meta_keys: A list of strings, the keys to fetch from the metadata. default: The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. \"\"\" value_map = {} for key, entry in name_to_entries_map.items(): value_list = [] for meta_key in meta_keys: value_list.append(entry.meta.get(meta_key, default) if entry is not None else default) value_map[key] = (value_list[0] if len(meta_keys) == 1 else tuple(value_list)) return value_map beancount.core.interpolate \uf0c1 Code used to automatically complete postings without positions. beancount.core.interpolate.BalanceError ( tuple ) \uf0c1 BalanceError(source, message, entry) beancount.core.interpolate.BalanceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/interpolate.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.interpolate.BalanceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BalanceError(source, message, entry) beancount.core.interpolate.BalanceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/interpolate.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.interpolate.compute_entries_balance(entries, prefix=None, date=None) \uf0c1 Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Parameters: entries \u2013 A list of directives. prefix \u2013 If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date \u2013 A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. Source code in beancount/core/interpolate.py def compute_entries_balance(entries, prefix=None, date=None): \"\"\"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Args: entries: A list of directives. prefix: If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date: A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. \"\"\" total_balance = Inventory() for entry in entries: if not (date is None or entry.date < date): break if isinstance(entry, Transaction): for posting in entry.postings: if prefix is None or posting.account.startswith(prefix): total_balance.add_position(posting) return total_balance beancount.core.interpolate.compute_entry_context(entries, context_entry) \uf0c1 Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Parameters: entries \u2013 A list of directives. context_entry \u2013 The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. Source code in beancount/core/interpolate.py def compute_entry_context(entries, context_entry): \"\"\"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Args: entries: A list of directives. context_entry: The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. \"\"\" assert context_entry is not None, \"context_entry is missing.\" # Get the set of accounts for which to compute the context. context_accounts = getters.get_entry_accounts(context_entry) # Iterate over the entries until we find the target one and accumulate the # balance. context_before = collections.defaultdict(inventory.Inventory) for entry in entries: if entry is context_entry: break if isinstance(entry, Transaction): for posting in entry.postings: if not any(posting.account == account for account in context_accounts): continue balance = context_before[posting.account] balance.add_position(posting) # Compute the after context for the entry. context_after = copy.deepcopy(context_before) if isinstance(context_entry, Transaction): for posting in entry.postings: balance = context_after[posting.account] balance.add_position(posting) return context_before, context_after beancount.core.interpolate.compute_residual(postings) \uf0c1 Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Parameters: postings \u2013 A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. Source code in beancount/core/interpolate.py def compute_residual(postings): \"\"\"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Args: postings: A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. \"\"\" inventory = Inventory() for posting in postings: # Skip auto-postings inserted to absorb the residual (rounding error). if posting.meta and posting.meta.get(AUTOMATIC_RESIDUAL, False): continue # Add to total residual balance. inventory.add_amount(convert.get_weight(posting)) return inventory beancount.core.interpolate.fill_residual_posting(entry, account_rounding) \uf0c1 If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Parameters: entry \u2013 An instance of a Transaction. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. Source code in beancount/core/interpolate.py def fill_residual_posting(entry, account_rounding): \"\"\"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Args: entry: An instance of a Transaction. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. \"\"\" residual = compute_residual(entry.postings) if not residual.is_empty(): new_postings = list(entry.postings) new_postings.extend(get_residual_postings(residual, account_rounding)) entry = entry._replace(postings=new_postings) return entry beancount.core.interpolate.get_residual_postings(residual, account_rounding) \uf0c1 Create postings to book the given residuals. Parameters: residual \u2013 An Inventory, the residual positions. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. Source code in beancount/core/interpolate.py def get_residual_postings(residual, account_rounding): \"\"\"Create postings to book the given residuals. Args: residual: An Inventory, the residual positions. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. \"\"\" meta = {AUTOMATIC_META: True, AUTOMATIC_RESIDUAL: True} return [Posting(account_rounding, -position.units, position.cost, None, None, meta.copy()) for position in residual.get_positions()] beancount.core.interpolate.has_nontrivial_balance(posting) \uf0c1 Return True if a Posting has a balance amount that would have to be calculated. Parameters: posting \u2013 A Posting instance. Returns: A boolean. Source code in beancount/core/interpolate.py def has_nontrivial_balance(posting): \"\"\"Return True if a Posting has a balance amount that would have to be calculated. Args: posting: A Posting instance. Returns: A boolean. \"\"\" return posting.cost or posting.price beancount.core.interpolate.infer_tolerances(postings, options_map, use_cost=None) \uf0c1 Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Parameters: postings \u2013 A list of Posting instances. options_map \u2013 A dict of options. use_cost \u2013 A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. Source code in beancount/core/interpolate.py def infer_tolerances(postings, options_map, use_cost=None): \"\"\"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Args: postings: A list of Posting instances. options_map: A dict of options. use_cost: A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. \"\"\" if use_cost is None: use_cost = options_map[\"infer_tolerance_from_cost\"] inferred_tolerance_multiplier = options_map[\"inferred_tolerance_multiplier\"] default_tolerances = options_map[\"inferred_tolerance_default\"] tolerances = default_tolerances.copy() cost_tolerances = collections.defaultdict(D) for posting in postings: # Skip the precision on automatically inferred postings. if posting.meta and AUTOMATIC_META in posting.meta: continue units = posting.units if not (isinstance(units, Amount) and isinstance(units.number, Decimal)): continue # Compute bounds on the number. currency = units.currency expo = units.number.as_tuple().exponent if expo < 0: # Note: the exponent is a negative value. tolerance = ONE.scaleb(expo) * inferred_tolerance_multiplier tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) if not use_cost: continue # Compute bounds on the smallest digit of the number implied as cost. cost = posting.cost if cost is not None: cost_currency = cost.currency if isinstance(cost, Cost): cost_tolerance = min(tolerance * cost.number, MAXIMUM_TOLERANCE) else: assert isinstance(cost, CostSpec) cost_tolerance = MAXIMUM_TOLERANCE for cost_number in cost.number_total, cost.number_per: if cost_number is None or cost_number is MISSING: continue cost_tolerance = min(tolerance * cost_number, cost_tolerance) cost_tolerances[cost_currency] += cost_tolerance # Compute bounds on the smallest digit of the number implied as cost. price = posting.price if isinstance(price, Amount) and isinstance(price.number, Decimal): price_currency = price.currency price_tolerance = min(tolerance * price.number, MAXIMUM_TOLERANCE) cost_tolerances[price_currency] += price_tolerance for currency, tolerance in cost_tolerances.items(): tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) default = tolerances.pop('*', ZERO) return defdict.ImmutableDictWithDefault(tolerances, default=default) beancount.core.interpolate.is_tolerance_user_specified(tolerance) \uf0c1 Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Parameters: tolerance \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/interpolate.py def is_tolerance_user_specified(tolerance): \"\"\"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Args: tolerance: An instance of Decimal. Returns: A boolean. \"\"\" return len(tolerance.as_tuple().digits) < MAX_TOLERANCE_DIGITS beancount.core.interpolate.quantize_with_tolerance(tolerances, currency, number) \uf0c1 Quantize the units using the tolerance dict. Parameters: tolerances \u2013 A dict of currency to tolerance Decimalvalues. number \u2013 A number to quantize. currency \u2013 A string currency. Returns: A Decimal, the number possibly quantized. Source code in beancount/core/interpolate.py def quantize_with_tolerance(tolerances, currency, number): \"\"\"Quantize the units using the tolerance dict. Args: tolerances: A dict of currency to tolerance Decimalvalues. number: A number to quantize. currency: A string currency. Returns: A Decimal, the number possibly quantized. \"\"\" # Applying rounding to the default tolerance, if there is one. tolerance = tolerances.get(currency) if tolerance: quantum = (tolerance * 2).normalize() # If the tolerance is a neat number provided by the user, # quantize the inferred numbers. See doc on quantize(): # # Unlike other operations, if the length of the coefficient # after the quantize operation would be greater than # precision, then an InvalidOperation is signaled. This # guarantees that, unless there is an error condition, the # quantized exponent is always equal to that of the # right-hand operand. if is_tolerance_user_specified(quantum): number = number.quantize(quantum) return number beancount.core.inventory \uf0c1 A container for an inventory of positions. This module provides a container class that can hold positions. An inventory is a mapping of positions, where each position is keyed by (currency: str, cost: Cost) -> position: Position where 'currency': The commodity under consideration, USD, CAD, or stock units such as HOOL, MSFT, AAPL, etc.; 'cost': None or a Cost instance existing of cost currency, number, date, and label; 'position': A Position object, whose 'units' attribute is guaranteed to have the same currency as 'currency' and whose 'cost' attribute is equal to the 'cost' key. It basically stores the number of units. This is meant to accommodate both booked and non-booked amounts. The clever trick that we pull to do this is that for positions which aren't booked, we simply leave the 'cost' as None. This is the case for most of the transactions. beancount.core.inventory.Booking ( Enum ) \uf0c1 Result of booking a new lot to an existing inventory. beancount.core.inventory.Inventory ( dict ) \uf0c1 An Inventory is a set of positions. Attributes: Name Type Description positions A list of Position instances, held in this Inventory object. beancount.core.inventory.Inventory.__abs__(self) special \uf0c1 Return an inventory with the absolute value of each position. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __abs__(self): \"\"\"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. \"\"\" return Inventory({key: abs(pos) for key, pos in self.items()}) beancount.core.inventory.Inventory.__add__(self, other) special \uf0c1 Add another inventory to this one. This inventory is not modified. Parameters: other \u2013 An instance of Inventory. Returns: A new instance of Inventory. Source code in beancount/core/inventory.py def __add__(self, other): \"\"\"Add another inventory to this one. This inventory is not modified. Args: other: An instance of Inventory. Returns: A new instance of Inventory. \"\"\" new_inventory = self.__copy__() new_inventory.add_inventory(other) return new_inventory beancount.core.inventory.Inventory.__copy__(self) special \uf0c1 A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. Source code in beancount/core/inventory.py def __copy__(self): \"\"\"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. \"\"\" return Inventory(self) beancount.core.inventory.Inventory.__iadd__(self, other) special \uf0c1 Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self beancount.core.inventory.Inventory.__init__(self, positions=None) special \uf0c1 Create a new inventory using a list of existing positions. Parameters: positions \u2013 A list of Position instances or an existing dict or Inventory instance. Source code in beancount/core/inventory.py def __init__(self, positions=None): \"\"\"Create a new inventory using a list of existing positions. Args: positions: A list of Position instances or an existing dict or Inventory instance. \"\"\" if isinstance(positions, (dict, Inventory)): dict.__init__(self, positions) else: dict.__init__(self) if positions: assert isinstance(positions, Iterable) for position in positions: self.add_position(position) beancount.core.inventory.Inventory.__iter__(self) special \uf0c1 Iterate over the positions. Note that there is no guaranteed order. Source code in beancount/core/inventory.py def __iter__(self): \"\"\"Iterate over the positions. Note that there is no guaranteed order.\"\"\" return iter(self.values()) beancount.core.inventory.Inventory.__lt__(self, other) special \uf0c1 Inequality comparison operator. Source code in beancount/core/inventory.py def __lt__(self, other): \"\"\"Inequality comparison operator.\"\"\" return sorted(self) < sorted(other) beancount.core.inventory.Inventory.__mul__(self, scalar) special \uf0c1 Scale/multiply the contents of the inventory. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the inventory. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Inventory({key: pos * scalar for key, pos in self.items()}) beancount.core.inventory.Inventory.__neg__(self) special \uf0c1 Return an inventory with the negative of values of this one. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __neg__(self): \"\"\"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. \"\"\" return Inventory({key: -pos for key, pos in self.items()}) beancount.core.inventory.Inventory.__repr__(self) special \uf0c1 Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string() beancount.core.inventory.Inventory.__str__(self) special \uf0c1 Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string() beancount.core.inventory.Inventory.add_amount(self, units, cost=None) \uf0c1 Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Parameters: units \u2013 An Amount instance to add. cost \u2013 An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. Source code in beancount/core/inventory.py def add_amount(self, units, cost=None): \"\"\"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Args: units: An Amount instance to add. cost: An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. \"\"\" if ASSERTS_TYPES: assert isinstance(units, Amount), ( \"Internal error: {!r} (type: {})\".format(units, type(units).__name__)) assert cost is None or isinstance(cost, Cost), ( \"Internal error: {!r} (type: {})\".format(cost, type(cost).__name__)) # Find the position. key = (units.currency, cost) pos = self.get(key, None) if pos is not None: # Note: In order to augment or reduce, all the fields have to match. # Check if reducing. booking = (Booking.REDUCED if not same_sign(pos.units.number, units.number) else Booking.AUGMENTED) # Compute the new number of units. number = pos.units.number + units.number if number == ZERO: # If empty, delete the position. del self[key] else: # Otherwise update it. self[key] = Position(Amount(number, units.currency), cost) else: # If not found, create a new one. if units.number == ZERO: booking = Booking.IGNORED else: self[key] = Position(units, cost) booking = Booking.CREATED return pos, booking beancount.core.inventory.Inventory.add_inventory(self, other) \uf0c1 Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self beancount.core.inventory.Inventory.add_position(self, position) \uf0c1 Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Parameters: position \u2013 The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Source code in beancount/core/inventory.py def add_position(self, position): \"\"\"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Args: position: The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. \"\"\" if ASSERTS_TYPES: assert hasattr(position, 'units') and hasattr(position, 'cost'), ( \"Invalid type for position: {}\".format(position)) assert isinstance(position.cost, (type(None), Cost)), ( \"Invalid type for cost: {}\".format(position.cost)) return self.add_amount(position.units, position.cost) beancount.core.inventory.Inventory.average(self) \uf0c1 Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def average(self): \"\"\"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. \"\"\" groups = collections.defaultdict(list) for position in self: key = (position.units.currency, position.cost.currency if position.cost else None) groups[key].append(position) average_inventory = Inventory() for (currency, cost_currency), positions in groups.items(): total_units = sum(position.units.number for position in positions) # Explicitly skip aggregates when resulting in zero units. if total_units == ZERO: continue units_amount = Amount(total_units, currency) if cost_currency: total_cost = sum(convert.get_cost(position).number for position in positions) cost_number = (Decimal('Infinity') if total_units == ZERO else (total_cost / total_units)) min_date = None for pos in positions: pos_date = pos.cost.date if pos.cost else None if pos_date is not None: min_date = (pos_date if min_date is None else min(min_date, pos_date)) cost = Cost(cost_number, cost_currency, min_date, None) else: cost = None average_inventory.add_amount(units_amount, cost) return average_inventory beancount.core.inventory.Inventory.cost_currencies(self) \uf0c1 Return the list of unit currencies held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def cost_currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. \"\"\" return set(cost.currency for _, cost in self.keys() if cost is not None) beancount.core.inventory.Inventory.currencies(self) \uf0c1 Return the list of unit currencies held in this inventory. Returns: A list of currency strings. Source code in beancount/core/inventory.py def currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. \"\"\" return set(currency for currency, _ in self.keys()) beancount.core.inventory.Inventory.currency_pairs(self) \uf0c1 Return the commodities held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def currency_pairs(self): \"\"\"Return the commodities held in this inventory. Returns: A set of currency strings. \"\"\" return set(position.currency_pair() for position in self) beancount.core.inventory.Inventory.from_string(string) staticmethod \uf0c1 Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory beancount.core.inventory.Inventory.get_currency_units(self, currency) \uf0c1 Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Parameters: currency \u2013 A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. Source code in beancount/core/inventory.py def get_currency_units(self, currency): \"\"\"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Args: currency: A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. \"\"\" total_units = ZERO for position in self: if position.units.currency == currency: total_units += position.units.number return Amount(total_units, currency) beancount.core.inventory.Inventory.get_only_position(self) \uf0c1 Return the first position and assert there are no more. If the inventory is empty, return None. Source code in beancount/core/inventory.py def get_only_position(self): \"\"\"Return the first position and assert there are no more. If the inventory is empty, return None. \"\"\" if len(self) > 0: if len(self) > 1: raise AssertionError(\"Inventory has more than one expected \" \"position: {}\".format(self)) return next(iter(self)) beancount.core.inventory.Inventory.get_positions(self) \uf0c1 Return the positions in this inventory. Returns: A shallow copy of the list of positions. Source code in beancount/core/inventory.py def get_positions(self): \"\"\"Return the positions in this inventory. Returns: A shallow copy of the list of positions. \"\"\" return list(iter(self)) beancount.core.inventory.Inventory.is_empty(self) \uf0c1 Return true if the inventory is empty, that is, has no positions. Returns: A boolean. Source code in beancount/core/inventory.py def is_empty(self): \"\"\"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. \"\"\" return len(self) == 0 beancount.core.inventory.Inventory.is_mixed(self) \uf0c1 Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. Source code in beancount/core/inventory.py def is_mixed(self): \"\"\"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. \"\"\" signs_map = {} for position in self: sign = position.units.number >= 0 prev_sign = signs_map.setdefault(position.units.currency, sign) if sign != prev_sign: return True return False beancount.core.inventory.Inventory.is_reduced_by(self, ramount) \uf0c1 Return true if the amount could reduce this inventory. Parameters: ramount \u2013 An instance of Amount. Returns: A boolean. Source code in beancount/core/inventory.py def is_reduced_by(self, ramount): \"\"\"Return true if the amount could reduce this inventory. Args: ramount: An instance of Amount. Returns: A boolean. \"\"\" if ramount.number == ZERO: return False for position in self: units = position.units if (ramount.currency == units.currency and not same_sign(ramount.number, units.number)): return True return False beancount.core.inventory.Inventory.is_small(self, tolerances) \uf0c1 Return true if all the positions in the inventory are small. Parameters: tolerances \u2013 A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. Source code in beancount/core/inventory.py def is_small(self, tolerances): \"\"\"Return true if all the positions in the inventory are small. Args: tolerances: A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. \"\"\" if isinstance(tolerances, dict): for position in self: tolerance = tolerances.get(position.units.currency, ZERO) if abs(position.units.number) > tolerance: return False small = True else: small = not any(abs(position.units.number) > tolerances for position in self) return small beancount.core.inventory.Inventory.reduce(self, reducer, *args) \uf0c1 Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def reduce(self, reducer, *args): \"\"\"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. \"\"\" inventory = Inventory() for position in self: inventory.add_amount(reducer(position, *args)) return inventory beancount.core.inventory.Inventory.segregate_units(self, currencies) \uf0c1 Split up the list of positions to the given currencies. Parameters: currencies \u2013 A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. Source code in beancount/core/inventory.py def segregate_units(self, currencies): \"\"\"Split up the list of positions to the given currencies. Args: currencies: A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. \"\"\" per_currency_dict = {currency: Inventory() for currency in currencies} per_currency_dict[None] = Inventory() for position in self: currency = position.units.currency key = (currency if currency in currencies else None) per_currency_dict[key].add_position(position) return per_currency_dict beancount.core.inventory.Inventory.to_string(self, dformat=, parens=True) \uf0c1 Convert an Inventory instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. parents \u2013 A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/inventory.py def to_string(self, dformat=DEFAULT_FORMATTER, parens=True): \"\"\"Convert an Inventory instance to a printable string. Args: dformat: An instance of DisplayFormatter. parents: A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. \"\"\" fmt = '({})' if parens else '{}' return fmt.format( ', '.join(pos.to_string(dformat) for pos in sorted(self))) beancount.core.inventory.check_invariants(inv) \uf0c1 Check the invariants of the Inventory. Parameters: inventory \u2013 An instance of Inventory. Returns: True if the invariants are respected. Source code in beancount/core/inventory.py def check_invariants(inv): \"\"\"Check the invariants of the Inventory. Args: inventory: An instance of Inventory. Returns: True if the invariants are respected. \"\"\" # Check that all the keys are unique. lots = set((pos.units.currency, pos.cost) for pos in inv) assert len(lots) == len(inv), \"Invalid inventory: {}\".format(inv) # Check that none of the amounts is zero. for pos in inv: assert pos.units.number != ZERO, \"Invalid position size: {}\".format(pos) beancount.core.inventory.from_string(string) \uf0c1 Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory beancount.core.number \uf0c1 The module contains the basic Decimal type import. About Decimal usage: Do not import Decimal from the 'decimal' or 'cdecimal' modules; always import your Decimal class from beancount.core.amount. Prefer to use D() to create new instances of Decimal objects, which handles more syntax, e.g., handles None, and numbers with commas. beancount.core.number.D(strord=None) \uf0c1 Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Parameters: strord \u2013 A string or Decimal instance. Returns: A Decimal instance. Source code in beancount/core/number.py def D(strord=None): \"\"\"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Args: strord: A string or Decimal instance. Returns: A Decimal instance. \"\"\" try: # Note: try a map lookup and optimize performance here. if strord is None or strord == '': return Decimal() elif isinstance(strord, str): return Decimal(_CLEAN_NUMBER_RE.sub('', strord)) elif isinstance(strord, Decimal): return strord elif isinstance(strord, (int, float)): return Decimal(strord) else: assert strord is None, \"Invalid value to convert: {}\".format(strord) except Exception as exc: raise ValueError(\"Impossible to create Decimal instance from {!s}: {}\".format( strord, exc)) beancount.core.number.is_fast_decimal(decimal_module) \uf0c1 Return true if a fast C decimal implementation is installed. Source code in beancount/core/number.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType) beancount.core.number.round_to(number, increment) \uf0c1 Round a number down to a particular increment. Parameters: number \u2013 A Decimal, the number to be rounded. increment \u2013 A Decimal, the size of the increment. Returns: A Decimal, the rounded number. Source code in beancount/core/number.py def round_to(number, increment): \"\"\"Round a number *down* to a particular increment. Args: number: A Decimal, the number to be rounded. increment: A Decimal, the size of the increment. Returns: A Decimal, the rounded number. \"\"\" return int((number / increment)) * increment beancount.core.number.same_sign(number1, number2) \uf0c1 Return true if both numbers have the same sign. Parameters: number1 \u2013 An instance of Decimal. number2 \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/number.py def same_sign(number1, number2): \"\"\"Return true if both numbers have the same sign. Args: number1: An instance of Decimal. number2: An instance of Decimal. Returns: A boolean. \"\"\" return (number1 >= 0) == (number2 >= 0) beancount.core.position \uf0c1 A position object, which consists of units Amount and cost Cost. See types below for details. beancount.core.position.Cost ( tuple ) \uf0c1 Cost(number, currency, date, label) beancount.core.position.Cost.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.position.Cost.__new__(_cls, number, currency, date, label) special staticmethod \uf0c1 Create new instance of Cost(number, currency, date, label) beancount.core.position.Cost.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.position.CostSpec ( tuple ) \uf0c1 CostSpec(number_per, number_total, currency, date, label, merge) beancount.core.position.CostSpec.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.position.CostSpec.__new__(_cls, number_per, number_total, currency, date, label, merge) special staticmethod \uf0c1 Create new instance of CostSpec(number_per, number_total, currency, date, label, merge) beancount.core.position.CostSpec.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.position.Position ( _Position ) \uf0c1 A 'Position' is a pair of units and optional cost. This is used to track inventories. Attributes: Name Type Description units Amount An Amount, the number of units and its currency. cost Cost A Cost that represents the lot, or None. beancount.core.position.Position.__abs__(self) special \uf0c1 Return the absolute value of the position. Returns: An instance of Position with the absolute units. Source code in beancount/core/position.py def __abs__(self): \"\"\"Return the absolute value of the position. Returns: An instance of Position with the absolute units. \"\"\" return Position(amount_abs(self.units), self.cost) beancount.core.position.Position.__copy__(self) special \uf0c1 Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. Source code in beancount/core/position.py def __copy__(self): \"\"\"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. \"\"\" # Note: We use Decimal() for efficiency. return Position(copy.copy(self.units), copy.copy(self.cost)) beancount.core.position.Position.__eq__(self, other) special \uf0c1 Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Parameters: other \u2013 An instance of Position, or None. Returns: A boolean, true if the positions are equal. Source code in beancount/core/position.py def __eq__(self, other): \"\"\"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Args: other: An instance of Position, or None. Returns: A boolean, true if the positions are equal. \"\"\" return (self.units.number == ZERO if other is None else (self.units == other.units and self.cost == other.cost)) beancount.core.position.Position.__hash__(self) special \uf0c1 Compute a hash for this position. Returns: A hash of this position object. Source code in beancount/core/position.py def __hash__(self): \"\"\"Compute a hash for this position. Returns: A hash of this position object. \"\"\" return hash((self.units, self.cost)) beancount.core.position.Position.__lt__(self, other) special \uf0c1 A less-than comparison operator for positions. Parameters: other \u2013 Another instance of Position. Returns: True if this positions is smaller than the other position. Source code in beancount/core/position.py def __lt__(self, other): \"\"\"A less-than comparison operator for positions. Args: other: Another instance of Position. Returns: True if this positions is smaller than the other position. \"\"\" return self.sortkey() < other.sortkey() beancount.core.position.Position.__mul__(self, scalar) special \uf0c1 Scale/multiply the contents of the position. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/position.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the position. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Position(amount_mul(self.units, scalar), self.cost) beancount.core.position.Position.__neg__(self) special \uf0c1 Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost) beancount.core.position.Position.__new__(cls, units, cost=None) special staticmethod \uf0c1 Create new instance of _Position(units, cost) Source code in beancount/core/position.py def __new__(cls, units, cost=None): assert isinstance(units, Amount), ( \"Expected an Amount for units; received '{}'\".format(units)) assert cost is None or isinstance(cost, Position.cost_types), ( \"Expected a Cost for cost; received '{}'\".format(cost)) return _Position.__new__(cls, units, cost) beancount.core.position.Position.__repr__(self) special \uf0c1 Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string() beancount.core.position.Position.__str__(self) special \uf0c1 Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string() beancount.core.position.Position.currency_pair(self) \uf0c1 Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. Source code in beancount/core/position.py def currency_pair(self): \"\"\"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. \"\"\" return (self.units.currency, self.cost.currency if self.cost else None) beancount.core.position.Position.from_amounts(units, cost_amount=None) staticmethod \uf0c1 Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost) beancount.core.position.Position.from_string(string) staticmethod \uf0c1 Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost) beancount.core.position.Position.get_negative(self) \uf0c1 Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost) beancount.core.position.Position.is_negative_at_cost(self) \uf0c1 Return true if the position is held at cost and negative. Returns: A boolean. Source code in beancount/core/position.py def is_negative_at_cost(self): \"\"\"Return true if the position is held at cost and negative. Returns: A boolean. \"\"\" return (self.units.number < ZERO and self.cost is not None) beancount.core.position.Position.sortkey(self) \uf0c1 Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. Source code in beancount/core/position.py def sortkey(self): \"\"\"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. \"\"\" currency = self.units.currency order_units = CURRENCY_ORDER.get(currency, NCURRENCIES + len(currency)) if self.cost is not None: cost_number = self.cost.number cost_currency = self.cost.currency else: cost_number = ZERO cost_currency = '' return (order_units, cost_number, cost_currency, self.units.number) beancount.core.position.Position.to_string(self, dformat=, detail=True) \uf0c1 Render the position to a string.See to_string() for details. Source code in beancount/core/position.py def to_string(self, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the position to a string.See to_string() for details. \"\"\" return to_string(self, dformat, detail) beancount.core.position.cost_to_str(cost, dformat, detail=True) \uf0c1 Format an instance of Cost or a CostSpec to a string. Parameters: cost \u2013 An instance of Cost or CostSpec. dformat \u2013 A DisplayFormatter object. detail \u2013 A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. Source code in beancount/core/position.py def cost_to_str(cost, dformat, detail=True): \"\"\"Format an instance of Cost or a CostSpec to a string. Args: cost: An instance of Cost or CostSpec. dformat: A DisplayFormatter object. detail: A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. \"\"\" strlist = [] if isinstance(cost, Cost): if isinstance(cost.number, Decimal): strlist.append(Amount(cost.number, cost.currency).to_string(dformat)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) elif isinstance(cost, CostSpec): if isinstance(cost.number_per, Decimal) or isinstance(cost.number_total, Decimal): amountlist = [] if isinstance(cost.number_per, Decimal): amountlist.append(dformat.format(cost.number_per)) if isinstance(cost.number_total, Decimal): amountlist.append('#') amountlist.append(dformat.format(cost.number_total)) if isinstance(cost.currency, str): amountlist.append(cost.currency) strlist.append(' '.join(amountlist)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) if cost.merge: strlist.append('*') return ', '.join(strlist) beancount.core.position.from_amounts(units, cost_amount=None) \uf0c1 Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost) beancount.core.position.from_string(string) \uf0c1 Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost) beancount.core.position.get_position(posting) \uf0c1 Build a Position instance from a Posting instance. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Position. Source code in beancount/core/position.py def get_position(posting): \"\"\"Build a Position instance from a Posting instance. Args: posting: An instance of Posting. Returns: An instance of Position. \"\"\" return Position(posting.units, posting.cost) beancount.core.position.to_string(pos, dformat=, detail=True) \uf0c1 Render the Position or Posting instance to a string. Parameters: pos \u2013 An instance of Position or Posting. dformat \u2013 An instance of DisplayFormatter. detail \u2013 A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. Source code in beancount/core/position.py def to_string(pos, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the Position or Posting instance to a string. Args: pos: An instance of Position or Posting. dformat: An instance of DisplayFormatter. detail: A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. \"\"\" pos_str = pos.units.to_string(dformat) if pos.cost is not None: pos_str = '{} {{{}}}'.format(pos_str, cost_to_str(pos.cost, dformat, detail)) return pos_str beancount.core.prices \uf0c1 This module has code that can build a database of historical prices at various times, from which unrealized capital gains and market value can be deduced. Prices are deduced from Price entries found in the file, or perhaps created by scripts (for example you could build a script that will fetch live prices online and create entries on-the-fly). beancount.core.prices.PriceMap ( dict ) \uf0c1 A price map dictionary. The keys include both the set of forward (base, quote) pairs and their inverse. In order to determine which are the forward pairs, access the 'forward_pairs' attribute Attributes: Name Type Description forward_pairs A list of (base, quote) keys for the forward pairs. beancount.core.prices.build_price_map(entries) \uf0c1 Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Parameters: entries \u2013 A list of directives, hopefully including some Price and/or Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. Source code in beancount/core/prices.py def build_price_map(entries): \"\"\"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Args: entries: A list of directives, hopefully including some Price and/or Transaction entries. Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. \"\"\" # Fetch a list of all the price entries seen in the ledger. price_entries = [entry for entry in entries if isinstance(entry, Price)] # Build a map of exchange rates between these units. # (base-currency, quote-currency) -> List of (date, rate). price_map = collections.defaultdict(list) for price in price_entries: base_quote = (price.currency, price.amount.currency) price_map[base_quote].append((price.date, price.amount.number)) # Find pairs of inversed units. inversed_units = [] for base_quote, values in price_map.items(): base, quote = base_quote if (quote, base) in price_map: inversed_units.append(base_quote) # Find pairs of inversed units, and swallow the conversion with the smaller # number of rates into the other one. for base, quote in inversed_units: bq_prices = price_map[(base, quote)] qb_prices = price_map[(quote, base)] remove = ((base, quote) if len(bq_prices) < len(qb_prices) else (quote, base)) base, quote = remove remove_list = price_map[remove] insert_list = price_map[(quote, base)] del price_map[remove] inverted_list = [(date, ONE/rate) for (date, rate) in remove_list if rate != ZERO] insert_list.extend(inverted_list) # Unzip and sort each of the entries and eliminate duplicates on the date. sorted_price_map = PriceMap({ base_quote: list(misc_utils.sorted_uniquify(date_rates, lambda x: x[0], last=True)) for (base_quote, date_rates) in price_map.items()}) # Compute and insert all the inverted rates. forward_pairs = list(sorted_price_map.keys()) for (base, quote), price_list in list(sorted_price_map.items()): # Note: You have to filter out zero prices for zero-cost postings, like # gifted options. sorted_price_map[(quote, base)] = [ (date, ONE/price) for date, price in price_list if price != ZERO] sorted_price_map.forward_pairs = forward_pairs return sorted_price_map beancount.core.prices.get_all_prices(price_map, base_quote) \uf0c1 Return a sorted list of all (date, number) price pairs. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Exceptions: KeyError \u2013 If the base/quote could not be found. Source code in beancount/core/prices.py def get_all_prices(price_map, base_quote): \"\"\"Return a sorted list of all (date, number) price pairs. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Raises: KeyError: If the base/quote could not be found. \"\"\" base_quote = normalize_base_quote(base_quote) return _lookup_price_and_inverse(price_map, base_quote) beancount.core.prices.get_last_price_entries(entries, date) \uf0c1 Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Parameters: entries \u2013 A list of directives. date \u2013 An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. Source code in beancount/core/prices.py def get_last_price_entries(entries, date): \"\"\"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Args: entries: A list of directives. date: An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. \"\"\" price_entry_map = {} for entry in entries: if date is not None and entry.date >= date: break if isinstance(entry, Price): base_quote = (entry.currency, entry.amount.currency) price_entry_map[base_quote] = entry return sorted(price_entry_map.values(), key=data.entry_sortkey) beancount.core.prices.get_latest_price(price_map, base_quote) \uf0c1 Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. Source code in beancount/core/prices.py def get_latest_price(price_map, base_quote): \"\"\"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. \"\"\" base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) # Look up the list and return the latest element. The lists are assumed to # be sorted. try: price_list = _lookup_price_and_inverse(price_map, base_quote) except KeyError: price_list = None if price_list: return price_list[-1] else: return None, None beancount.core.prices.get_price(price_map, base_quote, date=None) \uf0c1 Return the price as of the given date. If the date is unspecified, return the latest price. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date \u2013 A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). Source code in beancount/core/prices.py def get_price(price_map, base_quote, date=None): \"\"\"Return the price as of the given date. If the date is unspecified, return the latest price. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date: A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). \"\"\" if date is None: return get_latest_price(price_map, base_quote) base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) try: price_list = _lookup_price_and_inverse(price_map, base_quote) index = bisect_key.bisect_right_with_key(price_list, date, key=lambda x: x[0]) if index == 0: return None, None else: return price_list[index-1] except KeyError: return None, None beancount.core.prices.normalize_base_quote(base_quote) \uf0c1 Convert a slash-separated string to a pair of strings. Parameters: base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. Source code in beancount/core/prices.py def normalize_base_quote(base_quote): \"\"\"Convert a slash-separated string to a pair of strings. Args: base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. \"\"\" if isinstance(base_quote, str): base_quote_norm = tuple(base_quote.split('/')) assert len(base_quote_norm) == 2, base_quote base_quote = base_quote_norm assert isinstance(base_quote, tuple), base_quote return base_quote beancount.core.realization \uf0c1 Realization of specific lists of account postings into reports. This code converts a list of entries into a tree of RealAccount nodes (which stands for \"realized accounts\"). The RealAccount objects contain lists of Posting instances instead of Transactions, or other entry types that are attached to an account, such as a Balance or Note entry. The interface of RealAccount corresponds to that of a regular Python dict, where the keys are the names of the individual components of an account's name, and the values are always other RealAccount instances. If you want to get an account by long account name, there are helper functions in this module for this purpose (see realization.get(), for instance). RealAccount instances also contain the final balance of that account, resulting from its list of postings. You should not build RealAccount trees yourself; instead, you should filter the list of desired directives to display and call the realize() function with them. beancount.core.realization.RealAccount ( dict ) \uf0c1 A realized account, inserted in a tree, that contains the list of realized entries. Attributes: Name Type Description account A string, the full name of the corresponding account. postings A list of postings associated with this accounting (does not include the postings of children accounts). balance The final balance of the list of postings associated with this account. beancount.core.realization.RealAccount.__eq__(self, other) special \uf0c1 Equality predicate. All attributes are compared. Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. Source code in beancount/core/realization.py def __eq__(self, other): \"\"\"Equality predicate. All attributes are compared. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. \"\"\" return (dict.__eq__(self, other) and self.account == other.account and self.balance == other.balance and self.txn_postings == other.txn_postings) beancount.core.realization.RealAccount.__init__(self, account_name, *args, **kwargs) special \uf0c1 Create a RealAccount instance. Parameters: account_name \u2013 a string, the name of the account. Maybe not be None. Source code in beancount/core/realization.py def __init__(self, account_name, *args, **kwargs): \"\"\"Create a RealAccount instance. Args: account_name: a string, the name of the account. Maybe not be None. \"\"\" super().__init__(*args, **kwargs) assert isinstance(account_name, str) self.account = account_name self.txn_postings = [] self.balance = inventory.Inventory() beancount.core.realization.RealAccount.__ne__(self, other) special \uf0c1 Not-equality predicate. See eq . Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. Source code in beancount/core/realization.py def __ne__(self, other): \"\"\"Not-equality predicate. See __eq__. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. \"\"\" return not self.__eq__(other) beancount.core.realization.RealAccount.__setitem__(self, key, value) special \uf0c1 Prevent the setting of non-string or non-empty keys on this dict. Parameters: key \u2013 The dictionary key. Must be a string. value \u2013 The value, must be a RealAccount instance. Exceptions: KeyError \u2013 If the key is not a string, or is invalid. ValueError \u2013 If the value is not a RealAccount instance. Source code in beancount/core/realization.py def __setitem__(self, key, value): \"\"\"Prevent the setting of non-string or non-empty keys on this dict. Args: key: The dictionary key. Must be a string. value: The value, must be a RealAccount instance. Raises: KeyError: If the key is not a string, or is invalid. ValueError: If the value is not a RealAccount instance. \"\"\" if not isinstance(key, str) or not key: raise KeyError(\"Invalid RealAccount key: '{}'\".format(key)) if not isinstance(value, RealAccount): raise ValueError(\"Invalid RealAccount value: '{}'\".format(value)) if not value.account.endswith(key): raise ValueError(\"RealAccount name '{}' inconsistent with key: '{}'\".format( value.account, key)) return super().__setitem__(key, value) beancount.core.realization.RealAccount.copy(self) \uf0c1 Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. Source code in beancount/core/realization.py def copy(self): \"\"\"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. \"\"\" return copy.copy(self) beancount.core.realization.compute_balance(real_account, leaf_only=False) \uf0c1 Compute the total balance of this account and all its subaccounts. Parameters: real_account \u2013 A RealAccount instance. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Returns: An Inventory. Source code in beancount/core/realization.py def compute_balance(real_account, leaf_only=False): \"\"\"Compute the total balance of this account and all its subaccounts. Args: real_account: A RealAccount instance. leaf_only: A boolean flag, true if we should yield only leaves. Returns: An Inventory. \"\"\" return functools.reduce(operator.add, [ ra.balance for ra in iter_children(real_account, leaf_only)]) beancount.core.realization.compute_postings_balance(txn_postings) \uf0c1 Compute the balance of a list of Postings's or TxnPosting's positions. Parameters: postings \u2013 A list of Posting instances and other directives (which are skipped). Returns: An Inventory. Source code in beancount/core/realization.py def compute_postings_balance(txn_postings): \"\"\"Compute the balance of a list of Postings's or TxnPosting's positions. Args: postings: A list of Posting instances and other directives (which are skipped). Returns: An Inventory. \"\"\" final_balance = inventory.Inventory() for txn_posting in txn_postings: if isinstance(txn_posting, Posting): final_balance.add_position(txn_posting) elif isinstance(txn_posting, TxnPosting): final_balance.add_position(txn_posting.posting) return final_balance beancount.core.realization.contains(real_account, account_name) \uf0c1 True if the given account node contains the subaccount name. Parameters: account_name \u2013 A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. Source code in beancount/core/realization.py def contains(real_account, account_name): \"\"\"True if the given account node contains the subaccount name. Args: account_name: A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. \"\"\" return get(real_account, account_name) is not None beancount.core.realization.dump(root_account) \uf0c1 Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Parameters: root_account \u2013 A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. Source code in beancount/core/realization.py def dump(root_account): \"\"\"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Args: root_account: A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root_account.account, root_account, True)] while stack: prefix, name, real_account, is_last = stack.pop(-1) if real_account is root_account: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(real_account) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (real_account is root_account and not name): lines.append((first + name, cont + cont_name, real_account)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. child_items = sorted(real_account.items(), reverse=True) if child_items: child_iter = iter(child_items) child_name, child_account = next(child_iter) stack.append((cont, child_name, child_account, True)) for child_name, child_account in child_iter: stack.append((cont, child_name, child_account, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), real_node) for (first_line, cont_line, real_node) in lines] beancount.core.realization.dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None) \uf0c1 Dump a realization tree with balances. Parameters: real_root \u2013 An instance of RealAccount. dformat \u2013 An instance of DisplayFormatter to format the numbers with. at_cost \u2013 A boolean, if true, render the values at cost. fullnames \u2013 A boolean, if true, don't render a tree of accounts and render the full account names. file \u2013 A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. Source code in beancount/core/realization.py def dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None): \"\"\"Dump a realization tree with balances. Args: real_root: An instance of RealAccount. dformat: An instance of DisplayFormatter to format the numbers with. at_cost: A boolean, if true, render the values at cost. fullnames: A boolean, if true, don't render a tree of accounts and render the full account names. file: A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. \"\"\" if fullnames: # Compute the maximum account name length; maxlen = max(len(real_child.account) for real_child in iter_children(real_root, leaf_only=True)) line_format = '{{:{width}}} {{}}\\n'.format(width=maxlen) else: line_format = '{} {}\\n' output = file or io.StringIO() for first_line, cont_line, real_account in dump(real_root): if not real_account.balance.is_empty(): if at_cost: rinv = real_account.balance.reduce(convert.get_cost) else: rinv = real_account.balance.reduce(convert.get_units) amounts = [position.units for position in rinv.get_positions()] positions = [amount_.to_string(dformat) for amount_ in sorted(amounts, key=amount.sortkey)] else: positions = [''] if fullnames: for position in positions: if not position and len(real_account) > 0: continue # Skip parent accounts with no position to render. output.write(line_format.format(real_account.account, position)) else: line = first_line for position in positions: output.write(line_format.format(line, position)) line = cont_line if file is None: return output.getvalue() beancount.core.realization.filter(real_account, predicate) \uf0c1 Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Parameters: real_account \u2013 An instance of RealAccount. predicate \u2013 A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. Source code in beancount/core/realization.py def filter(real_account, predicate): \"\"\"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Args: real_account: An instance of RealAccount. predicate: A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. \"\"\" assert isinstance(real_account, RealAccount) real_copy = RealAccount(real_account.account) real_copy.balance = real_account.balance real_copy.txn_postings = real_account.txn_postings for child_name, real_child in real_account.items(): real_child_copy = filter(real_child, predicate) if real_child_copy is not None: real_copy[child_name] = real_child_copy if len(real_copy) > 0 or predicate(real_account): return real_copy beancount.core.realization.find_last_active_posting(txn_postings) \uf0c1 Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Parameters: txn_postings \u2013 a list of postings or entries. Returns: An entry, or None, if the input list was empty. Source code in beancount/core/realization.py def find_last_active_posting(txn_postings): \"\"\"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Args: txn_postings: a list of postings or entries. Returns: An entry, or None, if the input list was empty. \"\"\" for txn_posting in reversed(txn_postings): assert not isinstance(txn_posting, Posting) if not isinstance(txn_posting, (TxnPosting, Open, Close, Pad, Balance, Note)): continue # pylint: disable=bad-continuation if (isinstance(txn_posting, TxnPosting) and txn_posting.txn.flag == flags.FLAG_UNREALIZED): continue return txn_posting beancount.core.realization.get(real_account, account_name, default=None) \uf0c1 Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of. account_name \u2013 A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default \u2013 The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get(real_account, account_name, default=None): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of. account_name: A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default: The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) for component in components: real_child = real_account.get(component, default) if real_child is default: return default real_account = real_child return real_account beancount.core.realization.get_or_create(real_account, account_name) \uf0c1 Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of, or create under. account_name \u2013 A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get_or_create(real_account, account_name): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of, or create under. account_name: A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) path = [] for component in components: path.append(component) real_child = real_account.get(component, None) if real_child is None: real_child = RealAccount(account.join(*path)) real_account[component] = real_child real_account = real_child return real_account beancount.core.realization.get_postings(real_account) \uf0c1 Return a sorted list a RealAccount's postings and children. Parameters: real_account \u2013 An instance of RealAccount. Returns: A list of Posting or directories. Source code in beancount/core/realization.py def get_postings(real_account): \"\"\"Return a sorted list a RealAccount's postings and children. Args: real_account: An instance of RealAccount. Returns: A list of Posting or directories. \"\"\" # We accumulate all the postings at once here instead of incrementally # because we need to return them sorted. accumulator = [] for real_child in iter_children(real_account): accumulator.extend(real_child.txn_postings) accumulator.sort(key=data.posting_sortkey) return accumulator beancount.core.realization.index_key(sequence, value, key, cmp) \uf0c1 Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Parameters: sequence \u2013 The sequence to search. value \u2013 The value to search for. key \u2013 A predicate to call to obtain the value to compare against. cmp \u2013 A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. Source code in beancount/core/realization.py def index_key(sequence, value, key, cmp): \"\"\"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Args: sequence: The sequence to search. value: The value to search for. key: A predicate to call to obtain the value to compare against. cmp: A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. \"\"\" for index, element in enumerate(sequence): if cmp(key(element), value): return index return beancount.core.realization.iter_children(real_account, leaf_only=False) \uf0c1 Iterate this account node and all its children, depth-first. Parameters: real_account \u2013 An instance of RealAccount. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. Source code in beancount/core/realization.py def iter_children(real_account, leaf_only=False): \"\"\"Iterate this account node and all its children, depth-first. Args: real_account: An instance of RealAccount. leaf_only: A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. \"\"\" if leaf_only: if len(real_account) == 0: yield real_account else: for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child, leaf_only): yield real_subchild else: yield real_account for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child): yield real_subchild beancount.core.realization.iterate_with_balance(txn_postings) \uf0c1 Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance after adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Parameters: txn_postings \u2013 A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. Source code in beancount/core/realization.py def iterate_with_balance(txn_postings): \"\"\"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance *after* adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Args: txn_postings: A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. \"\"\" # The running balance. running_balance = inventory.Inventory() # Previous date. prev_date = None # A list of entries at the current date. date_entries = [] first = lambda pair: pair[0] for txn_posting in txn_postings: # Get the posting if we are dealing with one. assert not isinstance(txn_posting, Posting) if isinstance(txn_posting, TxnPosting): posting = txn_posting.posting entry = txn_posting.txn else: posting = None entry = txn_posting if entry.date != prev_date: assert prev_date is None or entry.date > prev_date, ( \"Invalid date order for postings: {} > {}\".format(prev_date, entry.date)) prev_date = entry.date # Flush the dated entries. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: # Compute the change due to this transaction and update the # total balance at the same time. for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() assert not date_entries if posting is not None: # De-dup multiple postings on the same transaction entry by # grouping their positions together. index = index_key(date_entries, entry, first, operator.is_) if index is None: date_entries.append((entry, [posting])) else: # We are indeed de-duping! postings = date_entries[index][1] postings.append(posting) else: # This is a regular entry; nothing to add/remove. date_entries.append((entry, [])) # Flush the final dated entries if any, same as above. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() beancount.core.realization.postings_by_account(entries) \uf0c1 Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Parameters: entries \u2013 A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. Source code in beancount/core/realization.py def postings_by_account(entries): \"\"\"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Args: entries: A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. \"\"\" txn_postings_map = collections.defaultdict(list) for entry in entries: if isinstance(entry, Transaction): # Insert an entry for each of the postings. for posting in entry.postings: txn_postings_map[posting.account].append( TxnPosting(entry, posting)) elif isinstance(entry, (Open, Close, Balance, Note, Document)): # Append some other entries in the realized list. txn_postings_map[entry.account].append(entry) elif isinstance(entry, Pad): # Insert the pad entry in both realized accounts. txn_postings_map[entry.account].append(entry) txn_postings_map[entry.source_account].append(entry) elif isinstance(entry, Custom): # Insert custom entry for each account in its values. for custom_value in entry.values: if custom_value.dtype == account.TYPE: txn_postings_map[custom_value.value].append(entry) return txn_postings_map beancount.core.realization.realize(entries, min_accounts=None, compute_balance=True) \uf0c1 Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\ v \\.__ +---------+ +-----+ -------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Parameters: entries \u2013 A list of directives. min_accounts \u2013 A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance \u2013 A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. Source code in beancount/core/realization.py def realize(entries, min_accounts=None, compute_balance=True): r\"\"\"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\\\ v `\\.__ +---------+ +-----+ `-------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Args: entries: A list of directives. min_accounts: A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance: A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. \"\"\" # Create lists of the entries by account. txn_postings_map = postings_by_account(entries) # Create a RealAccount tree and compute the balance for each. real_root = RealAccount('') for account_name, txn_postings in txn_postings_map.items(): real_account = get_or_create(real_root, account_name) real_account.txn_postings = txn_postings if compute_balance: real_account.balance = compute_postings_balance(txn_postings) # Ensure a minimum set of accounts that should exist. This is typically # called with an instance of AccountTypes to make sure that those exist. if min_accounts: for account_name in min_accounts: get_or_create(real_root, account_name) return real_root","title":"beancount.core"},{"location":"api_reference/beancount.core.html#beancountcore","text":"Core basic objects and data structures to represent a list of entries.","title":"beancount.core"},{"location":"api_reference/beancount.core.html#beancount.core.account","text":"Functions that operate on account strings. These account objects are rather simple and dumb; they do not contain the list of their associated postings. This is achieved by building a realization; see realization.py for details.","title":"account"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer","text":"Account name transformer. This is used to support Win... huh, filesystems and platforms which do not support colon characters. Attributes: Name Type Description rsep A character string, the new separator to use in link names.","title":"AccountTransformer"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer.parse","text":"Convert the transform account name to an account name. Source code in beancount/core/account.py def parse(self, transformed_name): \"Convert the transform account name to an account name.\" return (transformed_name if self.rsep is None else transformed_name.replace(self.rsep, sep))","title":"parse()"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer.render","text":"Convert the account name to a transformed account name. Source code in beancount/core/account.py def render(self, account_name): \"Convert the account name to a transformed account name.\" return (account_name if self.rsep is None else account_name.replace(sep, self.rsep))","title":"render()"},{"location":"api_reference/beancount.core.html#beancount.core.account.commonprefix","text":"Return the common prefix of a list of account names. Parameters: accounts \u2013 A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. Source code in beancount/core/account.py def commonprefix(accounts): \"\"\"Return the common prefix of a list of account names. Args: accounts: A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. \"\"\" accounts_lists = [account_.split(sep) for account_ in accounts] # Note: the os.path.commonprefix() function just happens to work here. # Inspect its code, and even the special case of no common prefix # works well with str.join() below. common_list = path.commonprefix(accounts_lists) return sep.join(common_list)","title":"commonprefix()"},{"location":"api_reference/beancount.core.html#beancount.core.account.has_component","text":"Return true if one of the account contains a given component. Parameters: account_name \u2013 A string, an account name. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/account.py def has_component(account_name, component): \"\"\"Return true if one of the account contains a given component. Args: account_name: A string, an account name. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return bool(re.search('(^|:){}(:|$)'.format(component), account_name))","title":"has_component()"},{"location":"api_reference/beancount.core.html#beancount.core.account.is_valid","text":"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Parameters: string \u2013 A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. Source code in beancount/core/account.py def is_valid(string): \"\"\"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Args: string: A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. \"\"\" return (isinstance(string, str) and bool(re.match('{}$'.format(ACCOUNT_RE), string)))","title":"is_valid()"},{"location":"api_reference/beancount.core.html#beancount.core.account.join","text":"Join the names with the account separator. Parameters: *components \u2013 Strings, the components of an account name. Returns: A string, joined in a single account name. Source code in beancount/core/account.py def join(*components): \"\"\"Join the names with the account separator. Args: *components: Strings, the components of an account name. Returns: A string, joined in a single account name. \"\"\" return sep.join(components)","title":"join()"},{"location":"api_reference/beancount.core.html#beancount.core.account.leaf","text":"Get the name of the leaf of this account. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. Source code in beancount/core/account.py def leaf(account_name): \"\"\"Get the name of the leaf of this account. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. \"\"\" assert isinstance(account_name, str) return account_name.split(sep)[-1] if account_name else None","title":"leaf()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parent","text":"Return the name of the parent account of the given account. Parameters: account_name \u2013 A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. Source code in beancount/core/account.py def parent(account_name): \"\"\"Return the name of the parent account of the given account. Args: account_name: A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. \"\"\" assert isinstance(account_name, str), account_name if not account_name: return None components = account_name.split(sep) components.pop(-1) return sep.join(components)","title":"parent()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parent_matcher","text":"Build a predicate that returns whether an account is under the given one. Parameters: account_name \u2013 The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of account_name . Source code in beancount/core/account.py def parent_matcher(account_name): \"\"\"Build a predicate that returns whether an account is under the given one. Args: account_name: The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of ``account_name``. \"\"\" return re.compile(r'{}($|{})'.format(re.escape(account_name), sep)).match","title":"parent_matcher()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parents","text":"A generator of the names of the parents of this account, including this account. Parameters: account_name \u2013 The name of the account we want to start iterating from. Returns: A generator of account name strings. Source code in beancount/core/account.py def parents(account_name): \"\"\"A generator of the names of the parents of this account, including this account. Args: account_name: The name of the account we want to start iterating from. Returns: A generator of account name strings. \"\"\" while account_name: yield account_name account_name = parent(account_name)","title":"parents()"},{"location":"api_reference/beancount.core.html#beancount.core.account.root","text":"Return the first few components of an account's name. Parameters: num_components \u2013 An integer, the number of components to return. account_name \u2013 A string, an account name. Returns: A string, the account root up to 'num_components' components. Source code in beancount/core/account.py def root(num_components, account_name): \"\"\"Return the first few components of an account's name. Args: num_components: An integer, the number of components to return. account_name: A string, an account name. Returns: A string, the account root up to 'num_components' components. \"\"\" return join(*(split(account_name)[:num_components]))","title":"root()"},{"location":"api_reference/beancount.core.html#beancount.core.account.sans_root","text":"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. Source code in beancount/core/account.py def sans_root(account_name): \"\"\"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. \"\"\" assert isinstance(account_name, str) components = account_name.split(sep)[1:] return join(*components) if account_name else None","title":"sans_root()"},{"location":"api_reference/beancount.core.html#beancount.core.account.split","text":"Split an account's name into its components. Parameters: account_name \u2013 A string, an account name. Returns: A list of strings, the components of the account name (without the separators). Source code in beancount/core/account.py def split(account_name): \"\"\"Split an account's name into its components. Args: account_name: A string, an account name. Returns: A list of strings, the components of the account name (without the separators). \"\"\" return account_name.split(sep)","title":"split()"},{"location":"api_reference/beancount.core.html#beancount.core.account.walk","text":"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Parameters: root_directory \u2013 A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). Source code in beancount/core/account.py def walk(root_directory): \"\"\"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Args: root_directory: A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). \"\"\" for root, dirs, files in os.walk(root_directory): dirs.sort() files.sort() relroot = root[len(root_directory)+1:] account_name = relroot.replace(os.sep, sep) if is_valid(account_name): yield (root, account_name, dirs, files)","title":"walk()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types","text":"Definition for global account types. This is where we keep the global account types value and definition. Note that it's unfortunate that we're using globals and side-effect here, but this is the best solution in the short-term, the account types are used in too many places to pass around that state everywhere. Maybe we change this later on.","title":"account_types"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes","text":"AccountTypes(assets, liabilities, equity, income, expenses)","title":"AccountTypes"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/account_types.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__new__","text":"Create new instance of AccountTypes(assets, liabilities, equity, income, expenses)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/account_types.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_sign","text":"Return the sign of the normal balance of a particular account. Parameters: account_name \u2013 A string, the name of the account whose sign is to return. account_types \u2013 An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. Source code in beancount/core/account_types.py def get_account_sign(account_name, account_types=None): \"\"\"Return the sign of the normal balance of a particular account. Args: account_name: A string, the name of the account whose sign is to return. account_types: An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. \"\"\" if account_types is None: account_types = DEFAULT_ACCOUNT_TYPES assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) account_type = get_account_type(account_name) return (+1 if account_type in (account_types.assets, account_types.expenses) else -1)","title":"get_account_sign()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_sort_key","text":"Return a tuple that can be used to order/sort account names. Parameters: account_types \u2013 An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. Source code in beancount/core/account_types.py def get_account_sort_key(account_types, account_name): \"\"\"Return a tuple that can be used to order/sort account names. Args: account_types: An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. \"\"\" return (account_types.index(get_account_type(account_name)), account_name)","title":"get_account_sort_key()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_type","text":"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_name \u2013 A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. Source code in beancount/core/account_types.py def get_account_type(account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_name: A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) return account.split(account_name)[0]","title":"get_account_type()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_account_type","text":"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_type \u2013 A string, the prefix type of the account. account_name \u2013 A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. Source code in beancount/core/account_types.py def is_account_type(account_type, account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_type: A string, the prefix type of the account. account_name: A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. \"\"\" return bool(re.match('^{}{}'.format(account_type, account.sep), account_name))","title":"is_account_type()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_balance_sheet_account","text":"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. Source code in beancount/core/account_types.py def is_balance_sheet_account(account_name, account_types): \"\"\"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.assets, account_types.liabilities, account_types.equity)","title":"is_balance_sheet_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_equity_account","text":"Return true if the given account is an equity account. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. Source code in beancount/core/account_types.py def is_equity_account(account_name, account_types): \"\"\"Return true if the given account is an equity account. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type == account_types.equity","title":"is_equity_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_income_statement_account","text":"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. Source code in beancount/core/account_types.py def is_income_statement_account(account_name, account_types): \"\"\"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.income, account_types.expenses)","title":"is_income_statement_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_root_account","text":"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Parameters: account_name \u2013 A string, the name of the account to check for. account_types \u2013 An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. Source code in beancount/core/account_types.py def is_root_account(account_name, account_types=None): \"\"\"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Args: account_name: A string, the name of the account to check for. account_types: An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) if account_types is not None: assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) return account_name in account_types else: return (account_name and bool(re.match(r'([A-Z][A-Za-z0-9\\-]+)$', account_name)))","title":"is_root_account()"},{"location":"api_reference/beancount.core.html#beancount.core.amount","text":"Amount class. This simple class is used to associate a number of units of a currency with its currency: (number, currency).","title":"amount"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount","text":"An 'Amount' represents a number of a particular unit of something. It's essentially a typed number, with corresponding manipulation operations defined on it.","title":"Amount"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__bool__","text":"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. Source code in beancount/core/amount.py def __bool__(self): \"\"\"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. \"\"\" return self.number != ZERO","title":"__bool__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__eq__","text":"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. Source code in beancount/core/amount.py def __eq__(self, other): \"\"\"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. \"\"\" if other is None: return False return (self.number, self.currency) == (other.number, other.currency)","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__hash__","text":"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. Source code in beancount/core/amount.py def __hash__(self): \"\"\"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. \"\"\" return hash((self.number, self.currency))","title":"__hash__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__lt__","text":"Ordering comparison. This is used in the sorting key of positions. Parameters: other \u2013 An instance of Amount. Returns: True if this is less than the other Amount. Source code in beancount/core/amount.py def __lt__(self, other): \"\"\"Ordering comparison. This is used in the sorting key of positions. Args: other: An instance of Amount. Returns: True if this is less than the other Amount. \"\"\" return sortkey(self) < sortkey(other)","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__neg__","text":"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. Source code in beancount/core/amount.py def __neg__(self): \"\"\"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. \"\"\" return Amount(-self.number, self.currency)","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__new__","text":"Constructor from a number and currency. Parameters: number \u2013 A string or Decimal instance. Will get converted automatically. currency \u2013 A string, the currency symbol to use. Source code in beancount/core/amount.py def __new__(cls, number, currency): \"\"\"Constructor from a number and currency. Args: number: A string or Decimal instance. Will get converted automatically. currency: A string, the currency symbol to use. \"\"\" assert isinstance(number, Amount.valid_types_number), repr(number) assert isinstance(currency, Amount.valid_types_currency), repr(currency) return _Amount.__new__(cls, number, currency)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__repr__","text":"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__str__","text":"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.from_string","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.to_string","text":"Convert an Amount instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def to_string(self, dformat=DEFAULT_FORMATTER): \"\"\"Convert an Amount instance to a printable string. Args: dformat: An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. \"\"\" number_fmt = (dformat.format(self.number, self.currency) if isinstance(self.number, Decimal) else str(self.number)) return \"{} {}\".format(number_fmt, self.currency)","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.A","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"A()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.abs","text":"Return the absolute value of the given amount. Parameters: amount \u2013 An instance of Amount. Returns: An instance of Amount. Source code in beancount/core/amount.py def abs(amount): \"\"\"Return the absolute value of the given amount. Args: amount: An instance of Amount. Returns: An instance of Amount. \"\"\" return (amount if amount.number >= ZERO else Amount(-amount.number, amount.currency))","title":"abs()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.add","text":"Add the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def add(amount1, amount2): \"\"\"Add the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number + amount2.number, amount1.currency)","title":"add()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.div","text":"Divide the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. Source code in beancount/core/amount.py def div(amount, number): \"\"\"Divide the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number / number, amount.currency)","title":"div()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.from_string","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.mul","text":"Multiply the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. Source code in beancount/core/amount.py def mul(amount, number): \"\"\"Multiply the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number * number, amount.currency)","title":"mul()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.sortkey","text":"A comparison function that sorts by currency first. Parameters: amount \u2013 An instance of Amount. Returns: A sort key, composed of the currency first and then the number. Source code in beancount/core/amount.py def sortkey(amount): \"\"\"A comparison function that sorts by currency first. Args: amount: An instance of Amount. Returns: A sort key, composed of the currency first and then the number. \"\"\" return (amount.currency, amount.number)","title":"sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.sub","text":"Subtract the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def sub(amount1, amount2): \"\"\"Subtract the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number - amount2.number, amount1.currency)","title":"sub()"},{"location":"api_reference/beancount.core.html#beancount.core.compare","text":"Comparison helpers for data objects.","title":"compare"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError","text":"CompareError(source, message, entry)","title":"CompareError"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/compare.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__new__","text":"Create new instance of CompareError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/compare.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.compare_entries","text":"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Parameters: entries1 \u2013 A list of directives of any type. entries2 \u2013 Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are \u2013 success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def compare_entries(entries1, entries2): \"\"\"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Args: entries1: A list of directives of any type. entries2: Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are: success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Raises: ValueError: If a duplicate entry is found. \"\"\" hashes1, errors1 = hash_entries(entries1, exclude_meta=True) hashes2, errors2 = hash_entries(entries2, exclude_meta=True) keys1 = set(hashes1.keys()) keys2 = set(hashes2.keys()) if errors1 or errors2: error = (errors1 + errors2)[0] raise ValueError(str(error)) same = keys1 == keys2 missing1 = data.sorted([hashes1[key] for key in keys1 - keys2]) missing2 = data.sorted([hashes2[key] for key in keys2 - keys1]) return (same, missing1, missing2)","title":"compare_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.excludes_entries","text":"Check that a list of entries does not appear in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def excludes_entries(subset_entries, entries): \"\"\"Check that a list of entries does not appear in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) intersection = keys.intersection(subset_keys) excludes = not bool(intersection) extra = data.sorted([subset_hashes[key] for key in intersection]) return (excludes, extra)","title":"excludes_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.hash_entries","text":"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Parameters: entries \u2013 A list of directives. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. Source code in beancount/core/compare.py def hash_entries(entries, exclude_meta=False): \"\"\"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Args: entries: A list of directives. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. \"\"\" entry_hash_dict = {} errors = [] num_legal_duplicates = 0 for entry in entries: hash_ = hash_entry(entry, exclude_meta) if hash_ in entry_hash_dict: if isinstance(entry, Price): # Note: Allow duplicate Price entries, they should be common # because of the nature of stock markets (if they're closed, the # data source is likely to return an entry for the previously # available date, which may already have been fetched). num_legal_duplicates += 1 else: other_entry = entry_hash_dict[hash_] errors.append( CompareError(entry.meta, \"Duplicate entry: {} == {}\".format(entry, other_entry), entry)) entry_hash_dict[hash_] = entry if not errors: assert len(entry_hash_dict) + num_legal_duplicates == len(entries), ( len(entry_hash_dict), len(entries), num_legal_duplicates) return entry_hash_dict, errors","title":"hash_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.hash_entry","text":"Compute the stable hash of a single entry. Parameters: entry \u2013 A directive instance. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. Source code in beancount/core/compare.py def hash_entry(entry, exclude_meta=False): \"\"\"Compute the stable hash of a single entry. Args: entry: A directive instance. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. \"\"\" return stable_hash_namedtuple(entry, IGNORED_FIELD_NAMES if exclude_meta else frozenset())","title":"hash_entry()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.includes_entries","text":"Check if a list of entries is included in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def includes_entries(subset_entries, entries): \"\"\"Check if a list of entries is included in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) includes = subset_keys.issubset(keys) missing = data.sorted([subset_hashes[key] for key in subset_keys - keys]) return (includes, missing)","title":"includes_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.stable_hash_namedtuple","text":"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Parameters: objtuple \u2013 A tuple object or other. ignore \u2013 A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. Source code in beancount/core/compare.py def stable_hash_namedtuple(objtuple, ignore=frozenset()): \"\"\"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Args: objtuple: A tuple object or other. ignore: A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. \"\"\" # Note: this routine is slow and would stand to be implemented in C. hashobj = hashlib.md5() for attr_name, attr_value in zip(objtuple._fields, objtuple): if attr_name in ignore: continue if isinstance(attr_value, (list, set, frozenset)): subhashes = set() for element in attr_value: if isinstance(element, tuple): subhashes.add(stable_hash_namedtuple(element, ignore)) else: md5 = hashlib.md5() md5.update(str(element).encode()) subhashes.add(md5.hexdigest()) for subhash in sorted(subhashes): hashobj.update(subhash.encode()) else: hashobj.update(str(attr_value).encode()) return hashobj.hexdigest()","title":"stable_hash_namedtuple()"},{"location":"api_reference/beancount.core.html#beancount.core.convert","text":"Conversions from Position (or Posting) to units, cost, weight, market value. Units: Just the primary amount of the position. Cost: The cost basis of the position, if available. Weight: The cost basis or price of the position. Market Value: The units converted to a value via a price map. To convert an inventory's contents, simply use these functions in conjunction with Inventory.reduce() , like cost_inv = inv.reduce(convert.get_cost) This module equivalently converts Position and Posting instances. Note that we're specifically avoiding to create an import dependency on beancount.core.data in order to keep this module isolatable, but it works on postings due to duck-typing. Function named get_*() are used to compute values from postings to their price currency. Functions named convert_*() are used to convert postings and amounts to any currency.","title":"convert"},{"location":"api_reference/beancount.core.html#beancount.core.convert.convert_amount","text":"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Parameters: amt \u2013 An instance of Amount. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. via \u2013 A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. Source code in beancount/core/convert.py def convert_amount(amt, target_currency, price_map, date=None, via=None): \"\"\"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Args: amt: An instance of Amount. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. via: A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. \"\"\" # First, attempt to convert directly. This should be the most # straightforward conversion. base_quote = (amt.currency, target_currency) _, rate = prices.get_price(price_map, base_quote, date) if rate is not None: # On success, just make the conversion directly. return Amount(amt.number * rate, target_currency) elif via: assert isinstance(via, (tuple, list)) # A price is unavailable, attempt to convert via cost/price currency # hop, if the value currency isn't the target currency. for implied_currency in via: if implied_currency == target_currency: continue base_quote1 = (amt.currency, implied_currency) _, rate1 = prices.get_price(price_map, base_quote1, date) if rate1 is not None: base_quote2 = (implied_currency, target_currency) _, rate2 = prices.get_price(price_map, base_quote2, date) if rate2 is not None: return Amount(amt.number * rate1 * rate2, target_currency) # We failed to infer a conversion rate; return the amt. return amt","title":"convert_amount()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.convert_position","text":"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Parameters: pos \u2013 An instance of Position or Posting, equivalently. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) Source code in beancount/core/convert.py def convert_position(pos, target_currency, price_map, date=None): \"\"\"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Args: pos: An instance of Position or Posting, equivalently. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) \"\"\" cost = pos.cost value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) return convert_amount(pos.units, target_currency, price_map, date=date, via=(value_currency,))","title":"convert_position()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_cost","text":"Return the total cost of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_cost(pos): \"\"\"Return the total cost of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' cost = pos.cost return (Amount(cost.number * pos.units.number, cost.currency) if (isinstance(cost, Cost) and isinstance(cost.number, Decimal)) else pos.units)","title":"get_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_units","text":"Return the units of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_units(pos): \"\"\"Return the units of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' return pos.units","title":"get_units()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_value","text":"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see convert_*() functions below. Parameters: pos \u2013 An instance of Position or Posting, equivalently. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. Source code in beancount/core/convert.py def get_value(pos, price_map, date=None): \"\"\"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see ``convert_*()`` functions below. Args: pos: An instance of Position or Posting, equivalently. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # Try to infer what the cost/price currency should be. value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) if isinstance(value_currency, str): # We have a value currency; hit the price database. base_quote = (units.currency, value_currency) _, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: return Amount(units.number * price_number, value_currency) # We failed to infer a conversion rate; return the units. return units","title":"get_value()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_weight","text":"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a key element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_weight(pos): \"\"\"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a *key* element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # It the object has a cost, use that as the weight, to balance. if isinstance(cost, Cost) and isinstance(cost.number, Decimal): weight = Amount(cost.number * pos.units.number, cost.currency) else: # Otherwise use the postings. weight = units # Unless there is a price available; use that if present. if not isinstance(pos, Position): price = pos.price if price is not None: # Note: Here we could assert that price.currency == units.currency. if price.number is MISSING or units.number is MISSING: converted_number = MISSING else: converted_number = price.number * units.number weight = Amount(converted_number, price.currency) return weight","title":"get_weight()"},{"location":"api_reference/beancount.core.html#beancount.core.data","text":"Basic data structures used to represent the Ledger entries.","title":"data"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance","text":"Balance(meta, date, account, amount, tolerance, diff_amount)","title":"Balance"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__new__","text":"Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close","text":"Close(meta, date, account)","title":"Close"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__new__","text":"Create new instance of Close(meta, date, account)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity","text":"Commodity(meta, date, currency)","title":"Commodity"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__new__","text":"Create new instance of Commodity(meta, date, currency)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom","text":"Custom(meta, date, type, values)","title":"Custom"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__new__","text":"Create new instance of Custom(meta, date, type, values)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document","text":"Document(meta, date, account, filename, tags, links)","title":"Document"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__new__","text":"Create new instance of Document(meta, date, account, filename, tags, links)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event","text":"Event(meta, date, type, description)","title":"Event"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__new__","text":"Create new instance of Event(meta, date, type, description)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note","text":"Note(meta, date, account, comment)","title":"Note"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__new__","text":"Create new instance of Note(meta, date, account, comment)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open","text":"Open(meta, date, account, currencies, booking)","title":"Open"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__new__","text":"Create new instance of Open(meta, date, account, currencies, booking)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad","text":"Pad(meta, date, account, source_account)","title":"Pad"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__new__","text":"Create new instance of Pad(meta, date, account, source_account)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting","text":"Posting(account, units, cost, price, flag, meta)","title":"Posting"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__new__","text":"Create new instance of Posting(account, units, cost, price, flag, meta)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price","text":"Price(meta, date, currency, amount)","title":"Price"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__new__","text":"Create new instance of Price(meta, date, currency, amount)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query","text":"Query(meta, date, name, query_string)","title":"Query"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__new__","text":"Create new instance of Query(meta, date, name, query_string)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction","text":"Transaction(meta, date, flag, payee, narration, tags, links, postings)","title":"Transaction"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__new__","text":"Create new instance of Transaction(meta, date, flag, payee, narration, tags, links, postings)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting","text":"TxnPosting(txn, posting)","title":"TxnPosting"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__new__","text":"Create new instance of TxnPosting(txn, posting)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.create_simple_posting","text":"Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting(entry, account, number, currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if number is None: units = None else: if not isinstance(number, Decimal): number = D(number) units = Amount(number, currency) posting = Posting(account, units, None, None, None, None) if entry is not None: entry.postings.append(posting) return posting","title":"create_simple_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.data.create_simple_posting_with_cost","text":"Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. cost_number \u2013 A Decimal number or string to use for the posting's cost Amount. cost_currency \u2013 a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. cost_number: A Decimal number or string to use for the posting's cost Amount. cost_currency: a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if not isinstance(number, Decimal): number = D(number) if cost_number and not isinstance(cost_number, Decimal): cost_number = D(cost_number) units = Amount(number, currency) cost = Cost(cost_number, cost_currency, None, None) posting = Posting(account, units, cost, None, None, None) if entry is not None: entry.postings.append(posting) return posting","title":"create_simple_posting_with_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.data.entry_sortkey","text":"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. Source code in beancount/core/data.py def entry_sortkey(entry): \"\"\"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. \"\"\" return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"])","title":"entry_sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.data.filter_txns","text":"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Parameters: entries \u2013 A list of directives. Yields: A sorted list of only the Transaction directives. Source code in beancount/core/data.py def filter_txns(entries): \"\"\"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Args: entries: A list of directives. Yields: A sorted list of only the Transaction directives. \"\"\" for entry in entries: if isinstance(entry, Transaction): yield entry","title":"filter_txns()"},{"location":"api_reference/beancount.core.html#beancount.core.data.find_closest","text":"Find the closest entry from entries to (filename, lineno). Parameters: entries \u2013 A list of directives. filename \u2013 A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno \u2013 An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. Source code in beancount/core/data.py def find_closest(entries, filename, lineno): \"\"\"Find the closest entry from entries to (filename, lineno). Args: entries: A list of directives. filename: A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno: An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. \"\"\" min_diffline = sys.maxsize closest_entry = None for entry in entries: emeta = entry.meta if emeta[\"filename\"] == filename and emeta[\"lineno\"] > 0: diffline = lineno - emeta[\"lineno\"] if 0 <= diffline < min_diffline: min_diffline = diffline closest_entry = entry return closest_entry","title":"find_closest()"},{"location":"api_reference/beancount.core.html#beancount.core.data.get_entry","text":"Return the entry associated with the posting or entry. Parameters: entry \u2013 A TxnPosting or entry instance Returns: A datetime instance. Source code in beancount/core/data.py def get_entry(posting_or_entry): \"\"\"Return the entry associated with the posting or entry. Args: entry: A TxnPosting or entry instance Returns: A datetime instance. \"\"\" return (posting_or_entry.txn if isinstance(posting_or_entry, TxnPosting) else posting_or_entry)","title":"get_entry()"},{"location":"api_reference/beancount.core.html#beancount.core.data.has_entry_account_component","text":"Return true if one of the entry's postings has an account component. Parameters: entry \u2013 A Transaction entry. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/data.py def has_entry_account_component(entry, component): \"\"\"Return true if one of the entry's postings has an account component. Args: entry: A Transaction entry. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return (isinstance(entry, Transaction) and any(has_component(posting.account, component) for posting in entry.postings))","title":"has_entry_account_component()"},{"location":"api_reference/beancount.core.html#beancount.core.data.iter_entry_dates","text":"Iterate over the entries in a date window. Parameters: entries \u2013 A date-sorted list of dated directives. date_begin \u2013 A datetime.date instance, the first date to include. date_end \u2013 A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. Source code in beancount/core/data.py def iter_entry_dates(entries, date_begin, date_end): \"\"\"Iterate over the entries in a date window. Args: entries: A date-sorted list of dated directives. date_begin: A datetime.date instance, the first date to include. date_end: A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. \"\"\" getdate = lambda entry: entry.date index_begin = bisect_left_with_key(entries, date_begin, key=getdate) index_end = bisect_left_with_key(entries, date_end, key=getdate) for index in range(index_begin, index_end): yield entries[index]","title":"iter_entry_dates()"},{"location":"api_reference/beancount.core.html#beancount.core.data.new_directive","text":"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Parameters: name \u2013 A string, the capitalized name of the directive. fields ( List[Tuple] ) \u2013 A string or the list of strings, names for the fields to add to the base tuple. Returns: \u2013 A type object for the new directive type. Source code in beancount/core/data.py def new_directive(clsname, fields: List[Tuple]) -> NamedTuple: \"\"\"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Args: name: A string, the capitalized name of the directive. fields: A string or the list of strings, names for the fields to add to the base tuple. Returns: A type object for the new directive type. \"\"\" return NamedTuple( clsname, [('meta', Meta), ('date', datetime.date)] + fields)","title":"new_directive()"},{"location":"api_reference/beancount.core.html#beancount.core.data.new_metadata","text":"Create a new metadata container from the filename and line number. Parameters: filename \u2013 A string, the filename for the creator of this directive. lineno \u2013 An integer, the line number where the directive has been created. kvlist \u2013 An optional container of key-values. Returns: A metadata dict. Source code in beancount/core/data.py def new_metadata(filename, lineno, kvlist=None): \"\"\"Create a new metadata container from the filename and line number. Args: filename: A string, the filename for the creator of this directive. lineno: An integer, the line number where the directive has been created. kvlist: An optional container of key-values. Returns: A metadata dict. \"\"\" meta = {'filename': filename, 'lineno': lineno} if kvlist: meta.update(kvlist) return meta","title":"new_metadata()"},{"location":"api_reference/beancount.core.html#beancount.core.data.posting_has_conversion","text":"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Parameters: posting \u2013 an instance of Posting Returns: A boolean, true if this posting has a price conversion. Source code in beancount/core/data.py def posting_has_conversion(posting): \"\"\"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Args: posting: an instance of Posting Return: A boolean, true if this posting has a price conversion. \"\"\" return (posting.cost is None and posting.price is not None)","title":"posting_has_conversion()"},{"location":"api_reference/beancount.core.html#beancount.core.data.posting_sortkey","text":"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. Source code in beancount/core/data.py def posting_sortkey(entry): \"\"\"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. \"\"\" if isinstance(entry, TxnPosting): entry = entry.txn return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"])","title":"posting_sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.data.remove_account_postings","text":"Remove all postings with the given account. Parameters: account \u2013 A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. Source code in beancount/core/data.py def remove_account_postings(account, entries): \"\"\"Remove all postings with the given account. Args: account: A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, Transaction) and ( any(posting.account == account for posting in entry.postings)): entry = entry._replace(postings=[posting for posting in entry.postings if posting.account != account]) new_entries.append(entry) return new_entries","title":"remove_account_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.data.sanity_check_types","text":"Check that the entry and its postings has all correct data types. Parameters: entry \u2013 An instance of one of the entries to be checked. allow_none_for_tags_and_links \u2013 A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Exceptions: AssertionError \u2013 If there is anything that is unexpected, raises an exception. Source code in beancount/core/data.py def sanity_check_types(entry, allow_none_for_tags_and_links=False): \"\"\"Check that the entry and its postings has all correct data types. Args: entry: An instance of one of the entries to be checked. allow_none_for_tags_and_links: A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Raises: AssertionError: If there is anything that is unexpected, raises an exception. \"\"\" assert isinstance(entry, ALL_DIRECTIVES), \"Invalid directive type\" assert isinstance(entry.meta, dict), \"Invalid type for meta\" assert 'filename' in entry.meta, \"Missing filename in metadata\" assert 'lineno' in entry.meta, \"Missing line number in metadata\" assert isinstance(entry.date, datetime.date), \"Invalid date type\" if isinstance(entry, Transaction): assert isinstance(entry.flag, (NoneType, str)), \"Invalid flag type\" assert isinstance(entry.payee, (NoneType, str)), \"Invalid payee type\" assert isinstance(entry.narration, (NoneType, str)), \"Invalid narration type\" set_types = ((NoneType, set, frozenset) if allow_none_for_tags_and_links else (set, frozenset)) assert isinstance(entry.tags, set_types), ( \"Invalid tags type: {}\".format(type(entry.tags))) assert isinstance(entry.links, set_types), ( \"Invalid links type: {}\".format(type(entry.links))) assert isinstance(entry.postings, list), \"Invalid postings list type\" for posting in entry.postings: assert isinstance(posting, Posting), \"Invalid posting type\" assert isinstance(posting.account, str), \"Invalid account type\" assert isinstance(posting.units, (Amount, NoneType)), \"Invalid units type\" assert isinstance(posting.cost, (Cost, CostSpec, NoneType)), \"Invalid cost type\" assert isinstance(posting.price, (Amount, NoneType)), \"Invalid price type\" assert isinstance(posting.flag, (str, NoneType)), \"Invalid flag type\"","title":"sanity_check_types()"},{"location":"api_reference/beancount.core.html#beancount.core.data.sorted","text":"A convenience to sort a list of entries, using entry_sortkey(). Parameters: entries \u2013 A list of directives. Returns: A sorted list of directives. Source code in beancount/core/data.py def sorted(entries): \"\"\"A convenience to sort a list of entries, using entry_sortkey(). Args: entries: A list of directives. Returns: A sorted list of directives. \"\"\" return builtins.sorted(entries, key=entry_sortkey)","title":"sorted()"},{"location":"api_reference/beancount.core.html#beancount.core.data.transaction_has_conversion","text":"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Parameters: transaction \u2013 an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. Source code in beancount/core/data.py def transaction_has_conversion(transaction): \"\"\"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Args: transaction: an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. \"\"\" assert isinstance(transaction, Transaction), ( \"Invalid type of entry for transaction: {}\".format(transaction)) for posting in transaction.postings: if posting_has_conversion(posting): return True return False","title":"transaction_has_conversion()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context","text":"A settings class to offer control over the number of digits rendered. This module contains routines that can accumulate information on the width and precision of numbers to be rendered and derive the precision required to render all of them consistently and under certain common alignment requirements. This is required in order to output neatly lined up columns of numbers in various styles. A common case is that the precision can be observed for numbers present in the input file. This display precision can be used as the \"precision by default\" if we write a routine for which it is inconvenient to feed all the numbers to build such an accumulator. Here are all the aspects supported by this module: PRECISION: Numbers for a particular currency are always rendered to the same precision, and they can be rendered to one of two precisions; either the most common number of fractional digits, or the maximum number of digits seen (this is useful for rendering prices). ALIGNMENT: Several alignment methods are supported. \"natural\": Render the strings as small as possible with no padding, but to their currency's precision. Like this: '1.2345' '764' '-7,409.01' '0.00000125' \"dot-aligned\": The periods will align vertically, the left and right sides are padded so that the column of numbers has the same width: ' 1.2345 ' ' 764 ' '-7,409.01 ' ' 0.00000125' \"right\": The strings are all flushed right, the left side is padded so that the column of numbers has the same width: ' 1.2345' ' 764' ' -7,409.01' ' 0.00000125' SIGN: If a negative sign is present in the input numbers, the rendered numbers reserve a space for it. If not, then we save the space. COMMAS: If the user requests to render commas, commas are rendered in the output. RESERVED: A number of extra integral digits reserved on the left in order to allow rendering novel numbers that haven't yet been seen. For example, balances may contains much larger numbers than the numbers seen in input files, and these need to be accommodated when aligning to the right.","title":"display_context"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.Align","text":"Alignment style for numbers.","title":"Align"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext","text":"A builder object used to construct a DisplayContext from a series of numbers. Attributes: Name Type Description ccontexts A dict of currency string to CurrencyContext instance. commas A bool, true if we should render commas. This just gets propagated onwards as the default value of to build with.","title":"DisplayContext"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.build","text":"Build a formatter for the given display context. Parameters: alignment \u2013 The desired alignment. precision \u2013 The desired precision. commas \u2013 Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved \u2013 An integer, the number of extra digits to be allocated in the maximum width calculations. Source code in beancount/core/display_context.py def build(self, alignment=Align.NATURAL, precision=Precision.MOST_COMMON, commas=None, reserved=0): \"\"\"Build a formatter for the given display context. Args: alignment: The desired alignment. precision: The desired precision. commas: Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved: An integer, the number of extra digits to be allocated in the maximum width calculations. \"\"\" if commas is None: commas = self.commas if alignment == Align.NATURAL: build_method = self._build_natural elif alignment == Align.RIGHT: build_method = self._build_right elif alignment == Align.DOT: build_method = self._build_dot else: raise ValueError(\"Unknown alignment: {}\".format(alignment)) fmtstrings = build_method(precision, commas, reserved) return DisplayFormatter(self, precision, fmtstrings)","title":"build()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.quantize","text":"Quantize the given number to the given precision. Parameters: number \u2013 A Decimal instance, the number to be quantized. currency \u2013 A currency string. precision \u2013 Which precision to use. Returns: A Decimal instance, the quantized number. Source code in beancount/core/display_context.py def quantize(self, number, currency, precision=Precision.MOST_COMMON): \"\"\"Quantize the given number to the given precision. Args: number: A Decimal instance, the number to be quantized. currency: A currency string. precision: Which precision to use. Returns: A Decimal instance, the quantized number. \"\"\" assert isinstance(number, Decimal), \"Invalid data: {}\".format(number) ccontext = self.ccontexts[currency] num_fractional_digits = ccontext.get_fractional(precision) if num_fractional_digits is None: # Note: We could probably logging.warn() this situation here. return number qdigit = Decimal(1).scaleb(-num_fractional_digits) return number.quantize(qdigit)","title":"quantize()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.set_commas","text":"Set the default value for rendering commas. Source code in beancount/core/display_context.py def set_commas(self, commas): \"\"\"Set the default value for rendering commas.\"\"\" self.commas = commas","title":"set_commas()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.update","text":"Update the builder with the given number for the given currency. Parameters: number \u2013 An instance of Decimal to consider for this currency. currency \u2013 An optional string, the currency this numbers applies to. Source code in beancount/core/display_context.py def update(self, number, currency='__default__'): \"\"\"Update the builder with the given number for the given currency. Args: number: An instance of Decimal to consider for this currency. currency: An optional string, the currency this numbers applies to. \"\"\" self.ccontexts[currency].update(number)","title":"update()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayFormatter","text":"A class used to contain various settings that control how we output numbers. In particular, the precision used for each currency, and whether or not commas should be printed. This object is intended to be passed around to all functions that format numbers to strings. Attributes: Name Type Description dcontext A DisplayContext instance. precision An enum of Precision from which it was built. fmtstrings A dict of currency to pre-baked format strings for it. fmtfuncs A dict of currency to pre-baked formatting functions for it.","title":"DisplayFormatter"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.Precision","text":"The type of precision required.","title":"Precision"},{"location":"api_reference/beancount.core.html#beancount.core.distribution","text":"A simple accumulator for data about a mathematical distribution.","title":"distribution"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution","text":"A class that computes a histogram of integer values. This is used to compute a length that will cover at least some decent fraction of the samples.","title":"Distribution"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.empty","text":"Return true if the distribution is empty. Returns: A boolean. Source code in beancount/core/distribution.py def empty(self): \"\"\"Return true if the distribution is empty. Returns: A boolean. \"\"\" return len(self.hist) == 0","title":"empty()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.max","text":"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def max(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[-1] return value","title":"max()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.min","text":"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def min(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[0] return value","title":"min()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.mode","text":"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def mode(self): \"\"\"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None max_value = 0 max_count = 0 for value, count in sorted(self.hist.items()): if count >= max_count: max_count = count max_value = value return max_value","title":"mode()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.update","text":"Add a sample to the distribution. Parameters: value \u2013 A value of the function. Source code in beancount/core/distribution.py def update(self, value): \"\"\"Add a sample to the distribution. Args: value: A value of the function. \"\"\" self.hist[value] += 1","title":"update()"},{"location":"api_reference/beancount.core.html#beancount.core.flags","text":"Flag constants.","title":"flags"},{"location":"api_reference/beancount.core.html#beancount.core.getters","text":"Getter functions that operate on lists of entries to return various lists of things that they reference, accounts, tags, links, currencies, etc.","title":"getters"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts","text":"Accounts gatherer.","title":"GetAccounts"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Balance","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Balance()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Close","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Close()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Commodity","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Commodity()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Custom","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Custom()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Document","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Document()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Event","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Event()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Note","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Note()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Open","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Open()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Pad","text":"Process a Pad directive. Parameters: entry \u2013 An instance of Pad. Returns: The two accounts of the Pad directive. Source code in beancount/core/getters.py def Pad(_, entry): \"\"\"Process a Pad directive. Args: entry: An instance of Pad. Returns: The two accounts of the Pad directive. \"\"\" return (entry.account, entry.source_account)","title":"Pad()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Price","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Price()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Query","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Query()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Transaction","text":"Process a Transaction directive. Parameters: entry \u2013 An instance of Transaction. Yields: The accounts of the legs of the transaction. Source code in beancount/core/getters.py def Transaction(_, entry): \"\"\"Process a Transaction directive. Args: entry: An instance of Transaction. Yields: The accounts of the legs of the transaction. \"\"\" for posting in entry.postings: yield posting.account","title":"Transaction()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.get_accounts_use_map","text":"Gather the list of accounts from the list of entries. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(self, entries): \"\"\"Gather the list of accounts from the list of entries. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" accounts_first = {} accounts_last = {} for entry in entries: method = getattr(self, entry.__class__.__name__) for account_ in method(entry): if account_ not in accounts_first: accounts_first[account_] = entry.date accounts_last[account_] = entry.date return accounts_first, accounts_last","title":"get_accounts_use_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.get_entry_accounts","text":"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entry \u2013 A directive instance. Returns: A set of account name strings. Source code in beancount/core/getters.py def get_entry_accounts(self, entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entry: A directive instance. Returns: A set of account name strings. \"\"\" method = getattr(self, entry.__class__.__name__) return set(method(entry))","title":"get_entry_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_account_components","text":"Gather all the account components available in the given directives. Parameters: entries \u2013 A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. Source code in beancount/core/getters.py def get_account_components(entries): \"\"\"Gather all the account components available in the given directives. Args: entries: A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. \"\"\" accounts = get_accounts(entries) components = set() for account_name in accounts: components.update(account.split(account_name)) return sorted(components)","title":"get_account_components()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_account_open_close","text":"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Parameters: entries \u2013 A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. Source code in beancount/core/getters.py def get_account_open_close(entries): \"\"\"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Args: entries: A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. \"\"\" # A dict of account name to (open-entry, close-entry). open_close_map = defaultdict(lambda: [None, None]) for entry in entries: if not isinstance(entry, (Open, Close)): continue open_close = open_close_map[entry.account] index = 0 if isinstance(entry, Open) else 1 previous_entry = open_close[index] if previous_entry is not None: if previous_entry.date <= entry.date: entry = previous_entry open_close[index] = entry return dict(open_close_map)","title":"get_account_open_close()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_accounts","text":"Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A set of account strings. Source code in beancount/core/getters.py def get_accounts(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A set of account strings. \"\"\" _, accounts_last = _GetAccounts.get_accounts_use_map(entries) return accounts_last.keys()","title":"get_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_accounts_use_map","text":"Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" return _GetAccounts.get_accounts_use_map(entries)","title":"get_accounts_use_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_active_years","text":"Yield all the years that have at least one entry in them. Parameters: entries \u2013 A list of directive instances. Yields: Unique dates see in the list of directives. Source code in beancount/core/getters.py def get_active_years(entries): \"\"\"Yield all the years that have at least one entry in them. Args: entries: A list of directive instances. Yields: Unique dates see in the list of directives. \"\"\" seen = set() prev_year = None for entry in entries: year = entry.date.year if year != prev_year: prev_year = year assert year not in seen seen.add(year) yield year","title":"get_active_years()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_links","text":"Return a list of all the links seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of links strings. Source code in beancount/core/getters.py def get_all_links(entries): \"\"\"Return a list of all the links seen in the given entries. Args: entries: A list of directive instances. Returns: A set of links strings. \"\"\" all_links = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.links: all_links.update(entry.links) return sorted(all_links)","title":"get_all_links()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_payees","text":"Return a list of all the unique payees seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of payee strings. Source code in beancount/core/getters.py def get_all_payees(entries): \"\"\"Return a list of all the unique payees seen in the given entries. Args: entries: A list of directive instances. Returns: A set of payee strings. \"\"\" all_payees = set() for entry in entries: if not isinstance(entry, Transaction): continue all_payees.add(entry.payee) all_payees.discard(None) return sorted(all_payees)","title":"get_all_payees()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_tags","text":"Return a list of all the tags seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of tag strings. Source code in beancount/core/getters.py def get_all_tags(entries): \"\"\"Return a list of all the tags seen in the given entries. Args: entries: A list of directive instances. Returns: A set of tag strings. \"\"\" all_tags = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.tags: all_tags.update(entry.tags) return sorted(all_tags)","title":"get_all_tags()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_commodity_map","text":"Create map of commodity names to Commodity entries. Parameters: entries \u2013 A list of directive instances. create_missing \u2013 A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. Source code in beancount/core/getters.py def get_commodity_map(entries, create_missing=True): \"\"\"Create map of commodity names to Commodity entries. Args: entries: A list of directive instances. create_missing: A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. \"\"\" if not entries: return {} commodities_map = {} for entry in entries: if isinstance(entry, Commodity): commodities_map[entry.currency] = entry elif isinstance(entry, Transaction): for posting in entry.postings: # Main currency. units = posting.units commodities_map.setdefault(units.currency, None) # Currency in cost. cost = posting.cost if cost: commodities_map.setdefault(cost.currency, None) # Currency in price. price = posting.price if price: commodities_map.setdefault(price.currency, None) elif isinstance(entry, Balance): commodities_map.setdefault(entry.amount.currency, None) elif isinstance(entry, Price): commodities_map.setdefault(entry.currency, None) if create_missing: # Create missing Commodity directives when they haven't been specified explicitly. # (I think it might be better to always do this from the loader.) date = entries[0].date meta = data.new_metadata('', 0) commodities_map = { commodity: (entry if entry is not None else Commodity(meta, date, commodity)) for commodity, entry in commodities_map.items()} return commodities_map","title":"get_commodity_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_dict_accounts","text":"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Parameters: account_names \u2013 An iterable of account names (strings) Returns: A nested OrderedDict of account leafs Source code in beancount/core/getters.py def get_dict_accounts(account_names): \"\"\"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Args: account_names: An iterable of account names (strings) Returns: A nested OrderedDict of account leafs \"\"\" leveldict = OrderedDict() for account_name in account_names: nested_dict = leveldict for component in account.split(account_name): nested_dict = nested_dict.setdefault(component, OrderedDict()) nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True return leveldict","title":"get_dict_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_entry_accounts","text":"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entries \u2013 A directive instance. Returns: A set of account strings. Source code in beancount/core/getters.py def get_entry_accounts(entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entries: A directive instance. Returns: A set of account strings. \"\"\" return _GetAccounts.get_entry_accounts(entry)","title":"get_entry_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_leveln_parent_accounts","text":"Return a list of all the unique leaf names at level N in an account hierarchy. Parameters: account_names \u2013 A list of account names (strings) level \u2013 The level to cross-cut. 0 is for root accounts. nrepeats \u2013 A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. Source code in beancount/core/getters.py def get_leveln_parent_accounts(account_names, level, nrepeats=0): \"\"\"Return a list of all the unique leaf names at level N in an account hierarchy. Args: account_names: A list of account names (strings) level: The level to cross-cut. 0 is for root accounts. nrepeats: A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. \"\"\" leveldict = defaultdict(int) for account_name in set(account_names): components = account.split(account_name) if level < len(components): leveldict[components[level]] += 1 levels = {level_ for level_, count in leveldict.items() if count > nrepeats} return sorted(levels)","title":"get_leveln_parent_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_min_max_dates","text":"Return the minimum and maximum dates in the list of entries. Parameters: entries \u2013 A list of directive instances. types \u2013 An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. Source code in beancount/core/getters.py def get_min_max_dates(entries, types=None): \"\"\"Return the minimum and maximum dates in the list of entries. Args: entries: A list of directive instances. types: An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. \"\"\" date_first = date_last = None for entry in entries: if types and not isinstance(entry, types): continue date_first = entry.date break for entry in reversed(entries): if types and not isinstance(entry, types): continue date_last = entry.date break return (date_first, date_last)","title":"get_min_max_dates()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_values_meta","text":"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Parameters: name_to_entries_map \u2013 A dict of something to an entry or None. meta_keys \u2013 A list of strings, the keys to fetch from the metadata. default \u2013 The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. Source code in beancount/core/getters.py def get_values_meta(name_to_entries_map, *meta_keys, default=None): \"\"\"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Args: name_to_entries_map: A dict of something to an entry or None. meta_keys: A list of strings, the keys to fetch from the metadata. default: The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. \"\"\" value_map = {} for key, entry in name_to_entries_map.items(): value_list = [] for meta_key in meta_keys: value_list.append(entry.meta.get(meta_key, default) if entry is not None else default) value_map[key] = (value_list[0] if len(meta_keys) == 1 else tuple(value_list)) return value_map","title":"get_values_meta()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate","text":"Code used to automatically complete postings without positions.","title":"interpolate"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError","text":"BalanceError(source, message, entry)","title":"BalanceError"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/interpolate.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__new__","text":"Create new instance of BalanceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/interpolate.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_entries_balance","text":"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Parameters: entries \u2013 A list of directives. prefix \u2013 If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date \u2013 A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. Source code in beancount/core/interpolate.py def compute_entries_balance(entries, prefix=None, date=None): \"\"\"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Args: entries: A list of directives. prefix: If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date: A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. \"\"\" total_balance = Inventory() for entry in entries: if not (date is None or entry.date < date): break if isinstance(entry, Transaction): for posting in entry.postings: if prefix is None or posting.account.startswith(prefix): total_balance.add_position(posting) return total_balance","title":"compute_entries_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_entry_context","text":"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Parameters: entries \u2013 A list of directives. context_entry \u2013 The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. Source code in beancount/core/interpolate.py def compute_entry_context(entries, context_entry): \"\"\"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Args: entries: A list of directives. context_entry: The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. \"\"\" assert context_entry is not None, \"context_entry is missing.\" # Get the set of accounts for which to compute the context. context_accounts = getters.get_entry_accounts(context_entry) # Iterate over the entries until we find the target one and accumulate the # balance. context_before = collections.defaultdict(inventory.Inventory) for entry in entries: if entry is context_entry: break if isinstance(entry, Transaction): for posting in entry.postings: if not any(posting.account == account for account in context_accounts): continue balance = context_before[posting.account] balance.add_position(posting) # Compute the after context for the entry. context_after = copy.deepcopy(context_before) if isinstance(context_entry, Transaction): for posting in entry.postings: balance = context_after[posting.account] balance.add_position(posting) return context_before, context_after","title":"compute_entry_context()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_residual","text":"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Parameters: postings \u2013 A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. Source code in beancount/core/interpolate.py def compute_residual(postings): \"\"\"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Args: postings: A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. \"\"\" inventory = Inventory() for posting in postings: # Skip auto-postings inserted to absorb the residual (rounding error). if posting.meta and posting.meta.get(AUTOMATIC_RESIDUAL, False): continue # Add to total residual balance. inventory.add_amount(convert.get_weight(posting)) return inventory","title":"compute_residual()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.fill_residual_posting","text":"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Parameters: entry \u2013 An instance of a Transaction. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. Source code in beancount/core/interpolate.py def fill_residual_posting(entry, account_rounding): \"\"\"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Args: entry: An instance of a Transaction. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. \"\"\" residual = compute_residual(entry.postings) if not residual.is_empty(): new_postings = list(entry.postings) new_postings.extend(get_residual_postings(residual, account_rounding)) entry = entry._replace(postings=new_postings) return entry","title":"fill_residual_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.get_residual_postings","text":"Create postings to book the given residuals. Parameters: residual \u2013 An Inventory, the residual positions. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. Source code in beancount/core/interpolate.py def get_residual_postings(residual, account_rounding): \"\"\"Create postings to book the given residuals. Args: residual: An Inventory, the residual positions. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. \"\"\" meta = {AUTOMATIC_META: True, AUTOMATIC_RESIDUAL: True} return [Posting(account_rounding, -position.units, position.cost, None, None, meta.copy()) for position in residual.get_positions()]","title":"get_residual_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.has_nontrivial_balance","text":"Return True if a Posting has a balance amount that would have to be calculated. Parameters: posting \u2013 A Posting instance. Returns: A boolean. Source code in beancount/core/interpolate.py def has_nontrivial_balance(posting): \"\"\"Return True if a Posting has a balance amount that would have to be calculated. Args: posting: A Posting instance. Returns: A boolean. \"\"\" return posting.cost or posting.price","title":"has_nontrivial_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.infer_tolerances","text":"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Parameters: postings \u2013 A list of Posting instances. options_map \u2013 A dict of options. use_cost \u2013 A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. Source code in beancount/core/interpolate.py def infer_tolerances(postings, options_map, use_cost=None): \"\"\"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Args: postings: A list of Posting instances. options_map: A dict of options. use_cost: A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. \"\"\" if use_cost is None: use_cost = options_map[\"infer_tolerance_from_cost\"] inferred_tolerance_multiplier = options_map[\"inferred_tolerance_multiplier\"] default_tolerances = options_map[\"inferred_tolerance_default\"] tolerances = default_tolerances.copy() cost_tolerances = collections.defaultdict(D) for posting in postings: # Skip the precision on automatically inferred postings. if posting.meta and AUTOMATIC_META in posting.meta: continue units = posting.units if not (isinstance(units, Amount) and isinstance(units.number, Decimal)): continue # Compute bounds on the number. currency = units.currency expo = units.number.as_tuple().exponent if expo < 0: # Note: the exponent is a negative value. tolerance = ONE.scaleb(expo) * inferred_tolerance_multiplier tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) if not use_cost: continue # Compute bounds on the smallest digit of the number implied as cost. cost = posting.cost if cost is not None: cost_currency = cost.currency if isinstance(cost, Cost): cost_tolerance = min(tolerance * cost.number, MAXIMUM_TOLERANCE) else: assert isinstance(cost, CostSpec) cost_tolerance = MAXIMUM_TOLERANCE for cost_number in cost.number_total, cost.number_per: if cost_number is None or cost_number is MISSING: continue cost_tolerance = min(tolerance * cost_number, cost_tolerance) cost_tolerances[cost_currency] += cost_tolerance # Compute bounds on the smallest digit of the number implied as cost. price = posting.price if isinstance(price, Amount) and isinstance(price.number, Decimal): price_currency = price.currency price_tolerance = min(tolerance * price.number, MAXIMUM_TOLERANCE) cost_tolerances[price_currency] += price_tolerance for currency, tolerance in cost_tolerances.items(): tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) default = tolerances.pop('*', ZERO) return defdict.ImmutableDictWithDefault(tolerances, default=default)","title":"infer_tolerances()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.is_tolerance_user_specified","text":"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Parameters: tolerance \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/interpolate.py def is_tolerance_user_specified(tolerance): \"\"\"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Args: tolerance: An instance of Decimal. Returns: A boolean. \"\"\" return len(tolerance.as_tuple().digits) < MAX_TOLERANCE_DIGITS","title":"is_tolerance_user_specified()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.quantize_with_tolerance","text":"Quantize the units using the tolerance dict. Parameters: tolerances \u2013 A dict of currency to tolerance Decimalvalues. number \u2013 A number to quantize. currency \u2013 A string currency. Returns: A Decimal, the number possibly quantized. Source code in beancount/core/interpolate.py def quantize_with_tolerance(tolerances, currency, number): \"\"\"Quantize the units using the tolerance dict. Args: tolerances: A dict of currency to tolerance Decimalvalues. number: A number to quantize. currency: A string currency. Returns: A Decimal, the number possibly quantized. \"\"\" # Applying rounding to the default tolerance, if there is one. tolerance = tolerances.get(currency) if tolerance: quantum = (tolerance * 2).normalize() # If the tolerance is a neat number provided by the user, # quantize the inferred numbers. See doc on quantize(): # # Unlike other operations, if the length of the coefficient # after the quantize operation would be greater than # precision, then an InvalidOperation is signaled. This # guarantees that, unless there is an error condition, the # quantized exponent is always equal to that of the # right-hand operand. if is_tolerance_user_specified(quantum): number = number.quantize(quantum) return number","title":"quantize_with_tolerance()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory","text":"A container for an inventory of positions. This module provides a container class that can hold positions. An inventory is a mapping of positions, where each position is keyed by (currency: str, cost: Cost) -> position: Position where 'currency': The commodity under consideration, USD, CAD, or stock units such as HOOL, MSFT, AAPL, etc.; 'cost': None or a Cost instance existing of cost currency, number, date, and label; 'position': A Position object, whose 'units' attribute is guaranteed to have the same currency as 'currency' and whose 'cost' attribute is equal to the 'cost' key. It basically stores the number of units. This is meant to accommodate both booked and non-booked amounts. The clever trick that we pull to do this is that for positions which aren't booked, we simply leave the 'cost' as None. This is the case for most of the transactions.","title":"inventory"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Booking","text":"Result of booking a new lot to an existing inventory.","title":"Booking"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory","text":"An Inventory is a set of positions. Attributes: Name Type Description positions A list of Position instances, held in this Inventory object.","title":"Inventory"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__abs__","text":"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __abs__(self): \"\"\"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. \"\"\" return Inventory({key: abs(pos) for key, pos in self.items()})","title":"__abs__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__add__","text":"Add another inventory to this one. This inventory is not modified. Parameters: other \u2013 An instance of Inventory. Returns: A new instance of Inventory. Source code in beancount/core/inventory.py def __add__(self, other): \"\"\"Add another inventory to this one. This inventory is not modified. Args: other: An instance of Inventory. Returns: A new instance of Inventory. \"\"\" new_inventory = self.__copy__() new_inventory.add_inventory(other) return new_inventory","title":"__add__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__copy__","text":"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. Source code in beancount/core/inventory.py def __copy__(self): \"\"\"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. \"\"\" return Inventory(self)","title":"__copy__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__iadd__","text":"Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self","title":"__iadd__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__init__","text":"Create a new inventory using a list of existing positions. Parameters: positions \u2013 A list of Position instances or an existing dict or Inventory instance. Source code in beancount/core/inventory.py def __init__(self, positions=None): \"\"\"Create a new inventory using a list of existing positions. Args: positions: A list of Position instances or an existing dict or Inventory instance. \"\"\" if isinstance(positions, (dict, Inventory)): dict.__init__(self, positions) else: dict.__init__(self) if positions: assert isinstance(positions, Iterable) for position in positions: self.add_position(position)","title":"__init__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__iter__","text":"Iterate over the positions. Note that there is no guaranteed order. Source code in beancount/core/inventory.py def __iter__(self): \"\"\"Iterate over the positions. Note that there is no guaranteed order.\"\"\" return iter(self.values())","title":"__iter__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__lt__","text":"Inequality comparison operator. Source code in beancount/core/inventory.py def __lt__(self, other): \"\"\"Inequality comparison operator.\"\"\" return sorted(self) < sorted(other)","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__mul__","text":"Scale/multiply the contents of the inventory. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the inventory. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Inventory({key: pos * scalar for key, pos in self.items()})","title":"__mul__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__neg__","text":"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __neg__(self): \"\"\"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. \"\"\" return Inventory({key: -pos for key, pos in self.items()})","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__repr__","text":"Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__str__","text":"Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_amount","text":"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Parameters: units \u2013 An Amount instance to add. cost \u2013 An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. Source code in beancount/core/inventory.py def add_amount(self, units, cost=None): \"\"\"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Args: units: An Amount instance to add. cost: An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. \"\"\" if ASSERTS_TYPES: assert isinstance(units, Amount), ( \"Internal error: {!r} (type: {})\".format(units, type(units).__name__)) assert cost is None or isinstance(cost, Cost), ( \"Internal error: {!r} (type: {})\".format(cost, type(cost).__name__)) # Find the position. key = (units.currency, cost) pos = self.get(key, None) if pos is not None: # Note: In order to augment or reduce, all the fields have to match. # Check if reducing. booking = (Booking.REDUCED if not same_sign(pos.units.number, units.number) else Booking.AUGMENTED) # Compute the new number of units. number = pos.units.number + units.number if number == ZERO: # If empty, delete the position. del self[key] else: # Otherwise update it. self[key] = Position(Amount(number, units.currency), cost) else: # If not found, create a new one. if units.number == ZERO: booking = Booking.IGNORED else: self[key] = Position(units, cost) booking = Booking.CREATED return pos, booking","title":"add_amount()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_inventory","text":"Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self","title":"add_inventory()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_position","text":"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Parameters: position \u2013 The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Source code in beancount/core/inventory.py def add_position(self, position): \"\"\"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Args: position: The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. \"\"\" if ASSERTS_TYPES: assert hasattr(position, 'units') and hasattr(position, 'cost'), ( \"Invalid type for position: {}\".format(position)) assert isinstance(position.cost, (type(None), Cost)), ( \"Invalid type for cost: {}\".format(position.cost)) return self.add_amount(position.units, position.cost)","title":"add_position()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.average","text":"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def average(self): \"\"\"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. \"\"\" groups = collections.defaultdict(list) for position in self: key = (position.units.currency, position.cost.currency if position.cost else None) groups[key].append(position) average_inventory = Inventory() for (currency, cost_currency), positions in groups.items(): total_units = sum(position.units.number for position in positions) # Explicitly skip aggregates when resulting in zero units. if total_units == ZERO: continue units_amount = Amount(total_units, currency) if cost_currency: total_cost = sum(convert.get_cost(position).number for position in positions) cost_number = (Decimal('Infinity') if total_units == ZERO else (total_cost / total_units)) min_date = None for pos in positions: pos_date = pos.cost.date if pos.cost else None if pos_date is not None: min_date = (pos_date if min_date is None else min(min_date, pos_date)) cost = Cost(cost_number, cost_currency, min_date, None) else: cost = None average_inventory.add_amount(units_amount, cost) return average_inventory","title":"average()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.cost_currencies","text":"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def cost_currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. \"\"\" return set(cost.currency for _, cost in self.keys() if cost is not None)","title":"cost_currencies()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.currencies","text":"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. Source code in beancount/core/inventory.py def currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. \"\"\" return set(currency for currency, _ in self.keys())","title":"currencies()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.currency_pairs","text":"Return the commodities held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def currency_pairs(self): \"\"\"Return the commodities held in this inventory. Returns: A set of currency strings. \"\"\" return set(position.currency_pair() for position in self)","title":"currency_pairs()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.from_string","text":"Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_currency_units","text":"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Parameters: currency \u2013 A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. Source code in beancount/core/inventory.py def get_currency_units(self, currency): \"\"\"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Args: currency: A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. \"\"\" total_units = ZERO for position in self: if position.units.currency == currency: total_units += position.units.number return Amount(total_units, currency)","title":"get_currency_units()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_only_position","text":"Return the first position and assert there are no more. If the inventory is empty, return None. Source code in beancount/core/inventory.py def get_only_position(self): \"\"\"Return the first position and assert there are no more. If the inventory is empty, return None. \"\"\" if len(self) > 0: if len(self) > 1: raise AssertionError(\"Inventory has more than one expected \" \"position: {}\".format(self)) return next(iter(self))","title":"get_only_position()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_positions","text":"Return the positions in this inventory. Returns: A shallow copy of the list of positions. Source code in beancount/core/inventory.py def get_positions(self): \"\"\"Return the positions in this inventory. Returns: A shallow copy of the list of positions. \"\"\" return list(iter(self))","title":"get_positions()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_empty","text":"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. Source code in beancount/core/inventory.py def is_empty(self): \"\"\"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. \"\"\" return len(self) == 0","title":"is_empty()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_mixed","text":"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. Source code in beancount/core/inventory.py def is_mixed(self): \"\"\"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. \"\"\" signs_map = {} for position in self: sign = position.units.number >= 0 prev_sign = signs_map.setdefault(position.units.currency, sign) if sign != prev_sign: return True return False","title":"is_mixed()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_reduced_by","text":"Return true if the amount could reduce this inventory. Parameters: ramount \u2013 An instance of Amount. Returns: A boolean. Source code in beancount/core/inventory.py def is_reduced_by(self, ramount): \"\"\"Return true if the amount could reduce this inventory. Args: ramount: An instance of Amount. Returns: A boolean. \"\"\" if ramount.number == ZERO: return False for position in self: units = position.units if (ramount.currency == units.currency and not same_sign(ramount.number, units.number)): return True return False","title":"is_reduced_by()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_small","text":"Return true if all the positions in the inventory are small. Parameters: tolerances \u2013 A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. Source code in beancount/core/inventory.py def is_small(self, tolerances): \"\"\"Return true if all the positions in the inventory are small. Args: tolerances: A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. \"\"\" if isinstance(tolerances, dict): for position in self: tolerance = tolerances.get(position.units.currency, ZERO) if abs(position.units.number) > tolerance: return False small = True else: small = not any(abs(position.units.number) > tolerances for position in self) return small","title":"is_small()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.reduce","text":"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def reduce(self, reducer, *args): \"\"\"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. \"\"\" inventory = Inventory() for position in self: inventory.add_amount(reducer(position, *args)) return inventory","title":"reduce()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.segregate_units","text":"Split up the list of positions to the given currencies. Parameters: currencies \u2013 A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. Source code in beancount/core/inventory.py def segregate_units(self, currencies): \"\"\"Split up the list of positions to the given currencies. Args: currencies: A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. \"\"\" per_currency_dict = {currency: Inventory() for currency in currencies} per_currency_dict[None] = Inventory() for position in self: currency = position.units.currency key = (currency if currency in currencies else None) per_currency_dict[key].add_position(position) return per_currency_dict","title":"segregate_units()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.to_string","text":"Convert an Inventory instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. parents \u2013 A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/inventory.py def to_string(self, dformat=DEFAULT_FORMATTER, parens=True): \"\"\"Convert an Inventory instance to a printable string. Args: dformat: An instance of DisplayFormatter. parents: A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. \"\"\" fmt = '({})' if parens else '{}' return fmt.format( ', '.join(pos.to_string(dformat) for pos in sorted(self)))","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.check_invariants","text":"Check the invariants of the Inventory. Parameters: inventory \u2013 An instance of Inventory. Returns: True if the invariants are respected. Source code in beancount/core/inventory.py def check_invariants(inv): \"\"\"Check the invariants of the Inventory. Args: inventory: An instance of Inventory. Returns: True if the invariants are respected. \"\"\" # Check that all the keys are unique. lots = set((pos.units.currency, pos.cost) for pos in inv) assert len(lots) == len(inv), \"Invalid inventory: {}\".format(inv) # Check that none of the amounts is zero. for pos in inv: assert pos.units.number != ZERO, \"Invalid position size: {}\".format(pos)","title":"check_invariants()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.from_string","text":"Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.number","text":"The module contains the basic Decimal type import. About Decimal usage: Do not import Decimal from the 'decimal' or 'cdecimal' modules; always import your Decimal class from beancount.core.amount. Prefer to use D() to create new instances of Decimal objects, which handles more syntax, e.g., handles None, and numbers with commas.","title":"number"},{"location":"api_reference/beancount.core.html#beancount.core.number.D","text":"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Parameters: strord \u2013 A string or Decimal instance. Returns: A Decimal instance. Source code in beancount/core/number.py def D(strord=None): \"\"\"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Args: strord: A string or Decimal instance. Returns: A Decimal instance. \"\"\" try: # Note: try a map lookup and optimize performance here. if strord is None or strord == '': return Decimal() elif isinstance(strord, str): return Decimal(_CLEAN_NUMBER_RE.sub('', strord)) elif isinstance(strord, Decimal): return strord elif isinstance(strord, (int, float)): return Decimal(strord) else: assert strord is None, \"Invalid value to convert: {}\".format(strord) except Exception as exc: raise ValueError(\"Impossible to create Decimal instance from {!s}: {}\".format( strord, exc))","title":"D()"},{"location":"api_reference/beancount.core.html#beancount.core.number.is_fast_decimal","text":"Return true if a fast C decimal implementation is installed. Source code in beancount/core/number.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType)","title":"is_fast_decimal()"},{"location":"api_reference/beancount.core.html#beancount.core.number.round_to","text":"Round a number down to a particular increment. Parameters: number \u2013 A Decimal, the number to be rounded. increment \u2013 A Decimal, the size of the increment. Returns: A Decimal, the rounded number. Source code in beancount/core/number.py def round_to(number, increment): \"\"\"Round a number *down* to a particular increment. Args: number: A Decimal, the number to be rounded. increment: A Decimal, the size of the increment. Returns: A Decimal, the rounded number. \"\"\" return int((number / increment)) * increment","title":"round_to()"},{"location":"api_reference/beancount.core.html#beancount.core.number.same_sign","text":"Return true if both numbers have the same sign. Parameters: number1 \u2013 An instance of Decimal. number2 \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/number.py def same_sign(number1, number2): \"\"\"Return true if both numbers have the same sign. Args: number1: An instance of Decimal. number2: An instance of Decimal. Returns: A boolean. \"\"\" return (number1 >= 0) == (number2 >= 0)","title":"same_sign()"},{"location":"api_reference/beancount.core.html#beancount.core.position","text":"A position object, which consists of units Amount and cost Cost. See types below for details.","title":"position"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost","text":"Cost(number, currency, date, label)","title":"Cost"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__new__","text":"Create new instance of Cost(number, currency, date, label)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec","text":"CostSpec(number_per, number_total, currency, date, label, merge)","title":"CostSpec"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__new__","text":"Create new instance of CostSpec(number_per, number_total, currency, date, label, merge)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position","text":"A 'Position' is a pair of units and optional cost. This is used to track inventories. Attributes: Name Type Description units Amount An Amount, the number of units and its currency. cost Cost A Cost that represents the lot, or None.","title":"Position"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__abs__","text":"Return the absolute value of the position. Returns: An instance of Position with the absolute units. Source code in beancount/core/position.py def __abs__(self): \"\"\"Return the absolute value of the position. Returns: An instance of Position with the absolute units. \"\"\" return Position(amount_abs(self.units), self.cost)","title":"__abs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__copy__","text":"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. Source code in beancount/core/position.py def __copy__(self): \"\"\"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. \"\"\" # Note: We use Decimal() for efficiency. return Position(copy.copy(self.units), copy.copy(self.cost))","title":"__copy__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__eq__","text":"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Parameters: other \u2013 An instance of Position, or None. Returns: A boolean, true if the positions are equal. Source code in beancount/core/position.py def __eq__(self, other): \"\"\"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Args: other: An instance of Position, or None. Returns: A boolean, true if the positions are equal. \"\"\" return (self.units.number == ZERO if other is None else (self.units == other.units and self.cost == other.cost))","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__hash__","text":"Compute a hash for this position. Returns: A hash of this position object. Source code in beancount/core/position.py def __hash__(self): \"\"\"Compute a hash for this position. Returns: A hash of this position object. \"\"\" return hash((self.units, self.cost))","title":"__hash__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__lt__","text":"A less-than comparison operator for positions. Parameters: other \u2013 Another instance of Position. Returns: True if this positions is smaller than the other position. Source code in beancount/core/position.py def __lt__(self, other): \"\"\"A less-than comparison operator for positions. Args: other: Another instance of Position. Returns: True if this positions is smaller than the other position. \"\"\" return self.sortkey() < other.sortkey()","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__mul__","text":"Scale/multiply the contents of the position. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/position.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the position. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Position(amount_mul(self.units, scalar), self.cost)","title":"__mul__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__neg__","text":"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost)","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__new__","text":"Create new instance of _Position(units, cost) Source code in beancount/core/position.py def __new__(cls, units, cost=None): assert isinstance(units, Amount), ( \"Expected an Amount for units; received '{}'\".format(units)) assert cost is None or isinstance(cost, Position.cost_types), ( \"Expected a Cost for cost; received '{}'\".format(cost)) return _Position.__new__(cls, units, cost)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__repr__","text":"Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__str__","text":"Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.currency_pair","text":"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. Source code in beancount/core/position.py def currency_pair(self): \"\"\"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. \"\"\" return (self.units.currency, self.cost.currency if self.cost else None)","title":"currency_pair()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.from_amounts","text":"Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost)","title":"from_amounts()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.from_string","text":"Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.get_negative","text":"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost)","title":"get_negative()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.is_negative_at_cost","text":"Return true if the position is held at cost and negative. Returns: A boolean. Source code in beancount/core/position.py def is_negative_at_cost(self): \"\"\"Return true if the position is held at cost and negative. Returns: A boolean. \"\"\" return (self.units.number < ZERO and self.cost is not None)","title":"is_negative_at_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.sortkey","text":"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. Source code in beancount/core/position.py def sortkey(self): \"\"\"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. \"\"\" currency = self.units.currency order_units = CURRENCY_ORDER.get(currency, NCURRENCIES + len(currency)) if self.cost is not None: cost_number = self.cost.number cost_currency = self.cost.currency else: cost_number = ZERO cost_currency = '' return (order_units, cost_number, cost_currency, self.units.number)","title":"sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.to_string","text":"Render the position to a string.See to_string() for details. Source code in beancount/core/position.py def to_string(self, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the position to a string.See to_string() for details. \"\"\" return to_string(self, dformat, detail)","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.cost_to_str","text":"Format an instance of Cost or a CostSpec to a string. Parameters: cost \u2013 An instance of Cost or CostSpec. dformat \u2013 A DisplayFormatter object. detail \u2013 A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. Source code in beancount/core/position.py def cost_to_str(cost, dformat, detail=True): \"\"\"Format an instance of Cost or a CostSpec to a string. Args: cost: An instance of Cost or CostSpec. dformat: A DisplayFormatter object. detail: A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. \"\"\" strlist = [] if isinstance(cost, Cost): if isinstance(cost.number, Decimal): strlist.append(Amount(cost.number, cost.currency).to_string(dformat)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) elif isinstance(cost, CostSpec): if isinstance(cost.number_per, Decimal) or isinstance(cost.number_total, Decimal): amountlist = [] if isinstance(cost.number_per, Decimal): amountlist.append(dformat.format(cost.number_per)) if isinstance(cost.number_total, Decimal): amountlist.append('#') amountlist.append(dformat.format(cost.number_total)) if isinstance(cost.currency, str): amountlist.append(cost.currency) strlist.append(' '.join(amountlist)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) if cost.merge: strlist.append('*') return ', '.join(strlist)","title":"cost_to_str()"},{"location":"api_reference/beancount.core.html#beancount.core.position.from_amounts","text":"Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost)","title":"from_amounts()"},{"location":"api_reference/beancount.core.html#beancount.core.position.from_string","text":"Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.get_position","text":"Build a Position instance from a Posting instance. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Position. Source code in beancount/core/position.py def get_position(posting): \"\"\"Build a Position instance from a Posting instance. Args: posting: An instance of Posting. Returns: An instance of Position. \"\"\" return Position(posting.units, posting.cost)","title":"get_position()"},{"location":"api_reference/beancount.core.html#beancount.core.position.to_string","text":"Render the Position or Posting instance to a string. Parameters: pos \u2013 An instance of Position or Posting. dformat \u2013 An instance of DisplayFormatter. detail \u2013 A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. Source code in beancount/core/position.py def to_string(pos, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the Position or Posting instance to a string. Args: pos: An instance of Position or Posting. dformat: An instance of DisplayFormatter. detail: A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. \"\"\" pos_str = pos.units.to_string(dformat) if pos.cost is not None: pos_str = '{} {{{}}}'.format(pos_str, cost_to_str(pos.cost, dformat, detail)) return pos_str","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.prices","text":"This module has code that can build a database of historical prices at various times, from which unrealized capital gains and market value can be deduced. Prices are deduced from Price entries found in the file, or perhaps created by scripts (for example you could build a script that will fetch live prices online and create entries on-the-fly).","title":"prices"},{"location":"api_reference/beancount.core.html#beancount.core.prices.PriceMap","text":"A price map dictionary. The keys include both the set of forward (base, quote) pairs and their inverse. In order to determine which are the forward pairs, access the 'forward_pairs' attribute Attributes: Name Type Description forward_pairs A list of (base, quote) keys for the forward pairs.","title":"PriceMap"},{"location":"api_reference/beancount.core.html#beancount.core.prices.build_price_map","text":"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Parameters: entries \u2013 A list of directives, hopefully including some Price and/or Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. Source code in beancount/core/prices.py def build_price_map(entries): \"\"\"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Args: entries: A list of directives, hopefully including some Price and/or Transaction entries. Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. \"\"\" # Fetch a list of all the price entries seen in the ledger. price_entries = [entry for entry in entries if isinstance(entry, Price)] # Build a map of exchange rates between these units. # (base-currency, quote-currency) -> List of (date, rate). price_map = collections.defaultdict(list) for price in price_entries: base_quote = (price.currency, price.amount.currency) price_map[base_quote].append((price.date, price.amount.number)) # Find pairs of inversed units. inversed_units = [] for base_quote, values in price_map.items(): base, quote = base_quote if (quote, base) in price_map: inversed_units.append(base_quote) # Find pairs of inversed units, and swallow the conversion with the smaller # number of rates into the other one. for base, quote in inversed_units: bq_prices = price_map[(base, quote)] qb_prices = price_map[(quote, base)] remove = ((base, quote) if len(bq_prices) < len(qb_prices) else (quote, base)) base, quote = remove remove_list = price_map[remove] insert_list = price_map[(quote, base)] del price_map[remove] inverted_list = [(date, ONE/rate) for (date, rate) in remove_list if rate != ZERO] insert_list.extend(inverted_list) # Unzip and sort each of the entries and eliminate duplicates on the date. sorted_price_map = PriceMap({ base_quote: list(misc_utils.sorted_uniquify(date_rates, lambda x: x[0], last=True)) for (base_quote, date_rates) in price_map.items()}) # Compute and insert all the inverted rates. forward_pairs = list(sorted_price_map.keys()) for (base, quote), price_list in list(sorted_price_map.items()): # Note: You have to filter out zero prices for zero-cost postings, like # gifted options. sorted_price_map[(quote, base)] = [ (date, ONE/price) for date, price in price_list if price != ZERO] sorted_price_map.forward_pairs = forward_pairs return sorted_price_map","title":"build_price_map()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_all_prices","text":"Return a sorted list of all (date, number) price pairs. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Exceptions: KeyError \u2013 If the base/quote could not be found. Source code in beancount/core/prices.py def get_all_prices(price_map, base_quote): \"\"\"Return a sorted list of all (date, number) price pairs. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Raises: KeyError: If the base/quote could not be found. \"\"\" base_quote = normalize_base_quote(base_quote) return _lookup_price_and_inverse(price_map, base_quote)","title":"get_all_prices()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_last_price_entries","text":"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Parameters: entries \u2013 A list of directives. date \u2013 An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. Source code in beancount/core/prices.py def get_last_price_entries(entries, date): \"\"\"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Args: entries: A list of directives. date: An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. \"\"\" price_entry_map = {} for entry in entries: if date is not None and entry.date >= date: break if isinstance(entry, Price): base_quote = (entry.currency, entry.amount.currency) price_entry_map[base_quote] = entry return sorted(price_entry_map.values(), key=data.entry_sortkey)","title":"get_last_price_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_latest_price","text":"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. Source code in beancount/core/prices.py def get_latest_price(price_map, base_quote): \"\"\"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. \"\"\" base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) # Look up the list and return the latest element. The lists are assumed to # be sorted. try: price_list = _lookup_price_and_inverse(price_map, base_quote) except KeyError: price_list = None if price_list: return price_list[-1] else: return None, None","title":"get_latest_price()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_price","text":"Return the price as of the given date. If the date is unspecified, return the latest price. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date \u2013 A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). Source code in beancount/core/prices.py def get_price(price_map, base_quote, date=None): \"\"\"Return the price as of the given date. If the date is unspecified, return the latest price. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date: A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). \"\"\" if date is None: return get_latest_price(price_map, base_quote) base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) try: price_list = _lookup_price_and_inverse(price_map, base_quote) index = bisect_key.bisect_right_with_key(price_list, date, key=lambda x: x[0]) if index == 0: return None, None else: return price_list[index-1] except KeyError: return None, None","title":"get_price()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.normalize_base_quote","text":"Convert a slash-separated string to a pair of strings. Parameters: base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. Source code in beancount/core/prices.py def normalize_base_quote(base_quote): \"\"\"Convert a slash-separated string to a pair of strings. Args: base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. \"\"\" if isinstance(base_quote, str): base_quote_norm = tuple(base_quote.split('/')) assert len(base_quote_norm) == 2, base_quote base_quote = base_quote_norm assert isinstance(base_quote, tuple), base_quote return base_quote","title":"normalize_base_quote()"},{"location":"api_reference/beancount.core.html#beancount.core.realization","text":"Realization of specific lists of account postings into reports. This code converts a list of entries into a tree of RealAccount nodes (which stands for \"realized accounts\"). The RealAccount objects contain lists of Posting instances instead of Transactions, or other entry types that are attached to an account, such as a Balance or Note entry. The interface of RealAccount corresponds to that of a regular Python dict, where the keys are the names of the individual components of an account's name, and the values are always other RealAccount instances. If you want to get an account by long account name, there are helper functions in this module for this purpose (see realization.get(), for instance). RealAccount instances also contain the final balance of that account, resulting from its list of postings. You should not build RealAccount trees yourself; instead, you should filter the list of desired directives to display and call the realize() function with them.","title":"realization"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount","text":"A realized account, inserted in a tree, that contains the list of realized entries. Attributes: Name Type Description account A string, the full name of the corresponding account. postings A list of postings associated with this accounting (does not include the postings of children accounts). balance The final balance of the list of postings associated with this account.","title":"RealAccount"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__eq__","text":"Equality predicate. All attributes are compared. Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. Source code in beancount/core/realization.py def __eq__(self, other): \"\"\"Equality predicate. All attributes are compared. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. \"\"\" return (dict.__eq__(self, other) and self.account == other.account and self.balance == other.balance and self.txn_postings == other.txn_postings)","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__init__","text":"Create a RealAccount instance. Parameters: account_name \u2013 a string, the name of the account. Maybe not be None. Source code in beancount/core/realization.py def __init__(self, account_name, *args, **kwargs): \"\"\"Create a RealAccount instance. Args: account_name: a string, the name of the account. Maybe not be None. \"\"\" super().__init__(*args, **kwargs) assert isinstance(account_name, str) self.account = account_name self.txn_postings = [] self.balance = inventory.Inventory()","title":"__init__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__ne__","text":"Not-equality predicate. See eq . Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. Source code in beancount/core/realization.py def __ne__(self, other): \"\"\"Not-equality predicate. See __eq__. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. \"\"\" return not self.__eq__(other)","title":"__ne__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__setitem__","text":"Prevent the setting of non-string or non-empty keys on this dict. Parameters: key \u2013 The dictionary key. Must be a string. value \u2013 The value, must be a RealAccount instance. Exceptions: KeyError \u2013 If the key is not a string, or is invalid. ValueError \u2013 If the value is not a RealAccount instance. Source code in beancount/core/realization.py def __setitem__(self, key, value): \"\"\"Prevent the setting of non-string or non-empty keys on this dict. Args: key: The dictionary key. Must be a string. value: The value, must be a RealAccount instance. Raises: KeyError: If the key is not a string, or is invalid. ValueError: If the value is not a RealAccount instance. \"\"\" if not isinstance(key, str) or not key: raise KeyError(\"Invalid RealAccount key: '{}'\".format(key)) if not isinstance(value, RealAccount): raise ValueError(\"Invalid RealAccount value: '{}'\".format(value)) if not value.account.endswith(key): raise ValueError(\"RealAccount name '{}' inconsistent with key: '{}'\".format( value.account, key)) return super().__setitem__(key, value)","title":"__setitem__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.copy","text":"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. Source code in beancount/core/realization.py def copy(self): \"\"\"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. \"\"\" return copy.copy(self)","title":"copy()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.compute_balance","text":"Compute the total balance of this account and all its subaccounts. Parameters: real_account \u2013 A RealAccount instance. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Returns: An Inventory. Source code in beancount/core/realization.py def compute_balance(real_account, leaf_only=False): \"\"\"Compute the total balance of this account and all its subaccounts. Args: real_account: A RealAccount instance. leaf_only: A boolean flag, true if we should yield only leaves. Returns: An Inventory. \"\"\" return functools.reduce(operator.add, [ ra.balance for ra in iter_children(real_account, leaf_only)])","title":"compute_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.compute_postings_balance","text":"Compute the balance of a list of Postings's or TxnPosting's positions. Parameters: postings \u2013 A list of Posting instances and other directives (which are skipped). Returns: An Inventory. Source code in beancount/core/realization.py def compute_postings_balance(txn_postings): \"\"\"Compute the balance of a list of Postings's or TxnPosting's positions. Args: postings: A list of Posting instances and other directives (which are skipped). Returns: An Inventory. \"\"\" final_balance = inventory.Inventory() for txn_posting in txn_postings: if isinstance(txn_posting, Posting): final_balance.add_position(txn_posting) elif isinstance(txn_posting, TxnPosting): final_balance.add_position(txn_posting.posting) return final_balance","title":"compute_postings_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.contains","text":"True if the given account node contains the subaccount name. Parameters: account_name \u2013 A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. Source code in beancount/core/realization.py def contains(real_account, account_name): \"\"\"True if the given account node contains the subaccount name. Args: account_name: A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. \"\"\" return get(real_account, account_name) is not None","title":"contains()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.dump","text":"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Parameters: root_account \u2013 A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. Source code in beancount/core/realization.py def dump(root_account): \"\"\"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Args: root_account: A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root_account.account, root_account, True)] while stack: prefix, name, real_account, is_last = stack.pop(-1) if real_account is root_account: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(real_account) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (real_account is root_account and not name): lines.append((first + name, cont + cont_name, real_account)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. child_items = sorted(real_account.items(), reverse=True) if child_items: child_iter = iter(child_items) child_name, child_account = next(child_iter) stack.append((cont, child_name, child_account, True)) for child_name, child_account in child_iter: stack.append((cont, child_name, child_account, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), real_node) for (first_line, cont_line, real_node) in lines]","title":"dump()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.dump_balances","text":"Dump a realization tree with balances. Parameters: real_root \u2013 An instance of RealAccount. dformat \u2013 An instance of DisplayFormatter to format the numbers with. at_cost \u2013 A boolean, if true, render the values at cost. fullnames \u2013 A boolean, if true, don't render a tree of accounts and render the full account names. file \u2013 A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. Source code in beancount/core/realization.py def dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None): \"\"\"Dump a realization tree with balances. Args: real_root: An instance of RealAccount. dformat: An instance of DisplayFormatter to format the numbers with. at_cost: A boolean, if true, render the values at cost. fullnames: A boolean, if true, don't render a tree of accounts and render the full account names. file: A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. \"\"\" if fullnames: # Compute the maximum account name length; maxlen = max(len(real_child.account) for real_child in iter_children(real_root, leaf_only=True)) line_format = '{{:{width}}} {{}}\\n'.format(width=maxlen) else: line_format = '{} {}\\n' output = file or io.StringIO() for first_line, cont_line, real_account in dump(real_root): if not real_account.balance.is_empty(): if at_cost: rinv = real_account.balance.reduce(convert.get_cost) else: rinv = real_account.balance.reduce(convert.get_units) amounts = [position.units for position in rinv.get_positions()] positions = [amount_.to_string(dformat) for amount_ in sorted(amounts, key=amount.sortkey)] else: positions = [''] if fullnames: for position in positions: if not position and len(real_account) > 0: continue # Skip parent accounts with no position to render. output.write(line_format.format(real_account.account, position)) else: line = first_line for position in positions: output.write(line_format.format(line, position)) line = cont_line if file is None: return output.getvalue()","title":"dump_balances()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.filter","text":"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Parameters: real_account \u2013 An instance of RealAccount. predicate \u2013 A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. Source code in beancount/core/realization.py def filter(real_account, predicate): \"\"\"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Args: real_account: An instance of RealAccount. predicate: A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. \"\"\" assert isinstance(real_account, RealAccount) real_copy = RealAccount(real_account.account) real_copy.balance = real_account.balance real_copy.txn_postings = real_account.txn_postings for child_name, real_child in real_account.items(): real_child_copy = filter(real_child, predicate) if real_child_copy is not None: real_copy[child_name] = real_child_copy if len(real_copy) > 0 or predicate(real_account): return real_copy","title":"filter()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.find_last_active_posting","text":"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Parameters: txn_postings \u2013 a list of postings or entries. Returns: An entry, or None, if the input list was empty. Source code in beancount/core/realization.py def find_last_active_posting(txn_postings): \"\"\"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Args: txn_postings: a list of postings or entries. Returns: An entry, or None, if the input list was empty. \"\"\" for txn_posting in reversed(txn_postings): assert not isinstance(txn_posting, Posting) if not isinstance(txn_posting, (TxnPosting, Open, Close, Pad, Balance, Note)): continue # pylint: disable=bad-continuation if (isinstance(txn_posting, TxnPosting) and txn_posting.txn.flag == flags.FLAG_UNREALIZED): continue return txn_posting","title":"find_last_active_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get","text":"Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of. account_name \u2013 A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default \u2013 The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get(real_account, account_name, default=None): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of. account_name: A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default: The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) for component in components: real_child = real_account.get(component, default) if real_child is default: return default real_account = real_child return real_account","title":"get()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get_or_create","text":"Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of, or create under. account_name \u2013 A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get_or_create(real_account, account_name): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of, or create under. account_name: A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) path = [] for component in components: path.append(component) real_child = real_account.get(component, None) if real_child is None: real_child = RealAccount(account.join(*path)) real_account[component] = real_child real_account = real_child return real_account","title":"get_or_create()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get_postings","text":"Return a sorted list a RealAccount's postings and children. Parameters: real_account \u2013 An instance of RealAccount. Returns: A list of Posting or directories. Source code in beancount/core/realization.py def get_postings(real_account): \"\"\"Return a sorted list a RealAccount's postings and children. Args: real_account: An instance of RealAccount. Returns: A list of Posting or directories. \"\"\" # We accumulate all the postings at once here instead of incrementally # because we need to return them sorted. accumulator = [] for real_child in iter_children(real_account): accumulator.extend(real_child.txn_postings) accumulator.sort(key=data.posting_sortkey) return accumulator","title":"get_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.index_key","text":"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Parameters: sequence \u2013 The sequence to search. value \u2013 The value to search for. key \u2013 A predicate to call to obtain the value to compare against. cmp \u2013 A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. Source code in beancount/core/realization.py def index_key(sequence, value, key, cmp): \"\"\"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Args: sequence: The sequence to search. value: The value to search for. key: A predicate to call to obtain the value to compare against. cmp: A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. \"\"\" for index, element in enumerate(sequence): if cmp(key(element), value): return index return","title":"index_key()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.iter_children","text":"Iterate this account node and all its children, depth-first. Parameters: real_account \u2013 An instance of RealAccount. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. Source code in beancount/core/realization.py def iter_children(real_account, leaf_only=False): \"\"\"Iterate this account node and all its children, depth-first. Args: real_account: An instance of RealAccount. leaf_only: A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. \"\"\" if leaf_only: if len(real_account) == 0: yield real_account else: for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child, leaf_only): yield real_subchild else: yield real_account for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child): yield real_subchild","title":"iter_children()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.iterate_with_balance","text":"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance after adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Parameters: txn_postings \u2013 A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. Source code in beancount/core/realization.py def iterate_with_balance(txn_postings): \"\"\"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance *after* adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Args: txn_postings: A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. \"\"\" # The running balance. running_balance = inventory.Inventory() # Previous date. prev_date = None # A list of entries at the current date. date_entries = [] first = lambda pair: pair[0] for txn_posting in txn_postings: # Get the posting if we are dealing with one. assert not isinstance(txn_posting, Posting) if isinstance(txn_posting, TxnPosting): posting = txn_posting.posting entry = txn_posting.txn else: posting = None entry = txn_posting if entry.date != prev_date: assert prev_date is None or entry.date > prev_date, ( \"Invalid date order for postings: {} > {}\".format(prev_date, entry.date)) prev_date = entry.date # Flush the dated entries. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: # Compute the change due to this transaction and update the # total balance at the same time. for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() assert not date_entries if posting is not None: # De-dup multiple postings on the same transaction entry by # grouping their positions together. index = index_key(date_entries, entry, first, operator.is_) if index is None: date_entries.append((entry, [posting])) else: # We are indeed de-duping! postings = date_entries[index][1] postings.append(posting) else: # This is a regular entry; nothing to add/remove. date_entries.append((entry, [])) # Flush the final dated entries if any, same as above. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear()","title":"iterate_with_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.postings_by_account","text":"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Parameters: entries \u2013 A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. Source code in beancount/core/realization.py def postings_by_account(entries): \"\"\"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Args: entries: A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. \"\"\" txn_postings_map = collections.defaultdict(list) for entry in entries: if isinstance(entry, Transaction): # Insert an entry for each of the postings. for posting in entry.postings: txn_postings_map[posting.account].append( TxnPosting(entry, posting)) elif isinstance(entry, (Open, Close, Balance, Note, Document)): # Append some other entries in the realized list. txn_postings_map[entry.account].append(entry) elif isinstance(entry, Pad): # Insert the pad entry in both realized accounts. txn_postings_map[entry.account].append(entry) txn_postings_map[entry.source_account].append(entry) elif isinstance(entry, Custom): # Insert custom entry for each account in its values. for custom_value in entry.values: if custom_value.dtype == account.TYPE: txn_postings_map[custom_value.value].append(entry) return txn_postings_map","title":"postings_by_account()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.realize","text":"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\ v \\.__ +---------+ +-----+ -------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Parameters: entries \u2013 A list of directives. min_accounts \u2013 A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance \u2013 A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. Source code in beancount/core/realization.py def realize(entries, min_accounts=None, compute_balance=True): r\"\"\"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\\\ v `\\.__ +---------+ +-----+ `-------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Args: entries: A list of directives. min_accounts: A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance: A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. \"\"\" # Create lists of the entries by account. txn_postings_map = postings_by_account(entries) # Create a RealAccount tree and compute the balance for each. real_root = RealAccount('') for account_name, txn_postings in txn_postings_map.items(): real_account = get_or_create(real_root, account_name) real_account.txn_postings = txn_postings if compute_balance: real_account.balance = compute_postings_balance(txn_postings) # Ensure a minimum set of accounts that should exist. This is typically # called with an instance of AccountTypes to make sure that those exist. if min_accounts: for account_name in min_accounts: get_or_create(real_root, account_name) return real_root","title":"realize()"},{"location":"api_reference/beancount.loader.html","text":"beancount.loader \uf0c1 Loader code. This is the main entry point to load up a file. beancount.loader.LoadError ( tuple ) \uf0c1 LoadError(source, message, entry) beancount.loader.LoadError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/loader.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.loader.LoadError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LoadError(source, message, entry) beancount.loader.LoadError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/loader.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.loader.aggregate_options_map(options_map, src_options_map) \uf0c1 Aggregate some of the attributes of options map. Parameters: options_map \u2013 The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map \u2013 A source map whose values we'd like to see aggregated. Source code in beancount/loader.py def aggregate_options_map(options_map, src_options_map): \"\"\"Aggregate some of the attributes of options map. Args: options_map: The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map: A source map whose values we'd like to see aggregated. \"\"\" op_currencies = options_map[\"operating_currency\"] for currency in src_options_map[\"operating_currency\"]: if currency not in op_currencies: op_currencies.append(currency) commodities = options_map[\"commodities\"] for currency in src_options_map[\"commodities\"]: commodities.add(currency) beancount.loader.combine_plugins(*plugin_modules) \uf0c1 Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Parameters: *plugins_modules \u2013 A sequence of module objects. Returns: A list that can be assigned to the new module's plugins attribute. Source code in beancount/loader.py def combine_plugins(*plugin_modules): \"\"\"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Args: *plugins_modules: A sequence of module objects. Returns: A list that can be assigned to the new module's __plugins__ attribute. \"\"\" modules = [] for module in plugin_modules: modules.extend([getattr(module, name) for name in module.__plugins__]) return modules beancount.loader.compute_input_hash(filenames) \uf0c1 Compute a hash of the input data. Parameters: filenames \u2013 A list of input files. Order is not relevant. Source code in beancount/loader.py def compute_input_hash(filenames): \"\"\"Compute a hash of the input data. Args: filenames: A list of input files. Order is not relevant. \"\"\" md5 = hashlib.md5() for filename in sorted(filenames): md5.update(filename.encode('utf8')) if not path.exists(filename): continue stat = os.stat(filename) md5.update(struct.pack('dd', stat.st_mtime_ns, stat.st_size)) return md5.hexdigest() beancount.loader.delete_cache_function(cache_getter, function) \uf0c1 A wrapper that removes the cached filename. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function \u2013 A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. Source code in beancount/loader.py def delete_cache_function(cache_getter, function): \"\"\"A wrapper that removes the cached filename. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function: A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): # Delete the cache. cache_filename = cache_getter(toplevel_filename) if path.exists(cache_filename): os.remove(cache_filename) # Invoke the original function. return function(toplevel_filename, *args, **kw) return wrapped beancount.loader.get_cache_filename(pattern, filename) \uf0c1 Compute the cache filename from a given pattern and the top-level filename. Parameters: pattern ( str ) \u2013 A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename ( str ) \u2013 The top-level filename. Returns: str \u2013 The resolved cache filename. Source code in beancount/loader.py def get_cache_filename(pattern: str, filename: str) -> str: \"\"\"Compute the cache filename from a given pattern and the top-level filename. Args: pattern: A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename: The top-level filename. Returns: The resolved cache filename. \"\"\" abs_filename = path.abspath(filename) if path.isabs(pattern): abs_pattern = pattern else: abs_pattern = path.join(path.dirname(abs_filename), pattern) return abs_pattern.format(filename=path.basename(filename)) beancount.loader.initialize(use_cache, cache_filename=None) \uf0c1 Initialize the loader. Source code in beancount/loader.py def initialize(use_cache: bool, cache_filename: Optional[str] = None): \"\"\"Initialize the loader.\"\"\" # Unless an environment variable disables it, use the pickle load cache # automatically. Note that this works across all Python programs running the # loader which is why it's located here. # pylint: disable=invalid-name global _load_file # Make a function to compute the cache filename. cache_pattern = (cache_filename or os.getenv('BEANCOUNT_LOAD_CACHE_FILENAME') or PICKLE_CACHE_FILENAME) cache_getter = functools.partial(get_cache_filename, cache_pattern) if use_cache: _load_file = pickle_cache_function(cache_getter, PICKLE_CACHE_THRESHOLD, _uncached_load_file) else: if cache_filename is not None: logging.warning(\"Cache disabled; \" \"Explicitly overridden cache filename %s will be ignored.\", cache_filename) _load_file = delete_cache_function(cache_getter, _uncached_load_file) beancount.loader.load_doc(expect_errors=False) \uf0c1 A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. Source code in beancount/loader.py def load_doc(expect_errors=False): \"\"\"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: A callable method, that accepts the three return arguments that load() returns. Returns: A decorated test function. \"\"\" @functools.wraps(fun) def wrapper(self): entries, errors, options_map = load_string(fun.__doc__, dedent=True) if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") # Note: Even if we expected no errors, we call this function with an # empty 'errors' list. This is so that the interface does not change # based on the arguments to the decorator, which would be somewhat # ugly and which would require explanation. return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator beancount.loader.load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None) \uf0c1 Load an encrypted Beancount input file. Parameters: filename \u2013 The name of an encrypted file to be parsed. log_timings \u2013 See load_string(). log_errors \u2013 See load_string(). extra_validations \u2013 See load_string(). dedent \u2013 See load_string(). encoding \u2013 See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Load an encrypted Beancount input file. Args: filename: The name of an encrypted file to be parsed. log_timings: See load_string(). log_errors: See load_string(). extra_validations: See load_string(). dedent: See load_string(). encoding: See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" contents = encryption.read_encrypted_file(filename) return load_string(contents, log_timings=log_timings, log_errors=log_errors, extra_validations=extra_validations, encoding=encoding) beancount.loader.load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None) \uf0c1 Open a Beancount input file, parse it, run transformations and validate. Parameters: filename \u2013 The name of the file to be parsed. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. encoding \u2013 A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None): \"\"\"Open a Beancount input file, parse it, run transformations and validate. Args: filename: The name of the file to be parsed. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. encoding: A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" filename = path.expandvars(path.expanduser(filename)) if not path.isabs(filename): filename = path.normpath(path.join(os.getcwd(), filename)) if encryption.is_encrypted_file(filename): # Note: Caching is not supported for encrypted files. entries, errors, options_map = load_encrypted_file( filename, log_timings, log_errors, extra_validations, False, encoding) else: entries, errors, options_map = _load_file( filename, log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map beancount.loader.load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None) \uf0c1 Open a Beancount input string, parse it, run transformations and validate. Parameters: string \u2013 A Beancount input string. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. dedent \u2013 A boolean, if set, remove the whitespace in front of the lines. encoding \u2013 A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. Source code in beancount/loader.py def load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Open a Beancount input string, parse it, run transformations and validate. Args: string: A Beancount input string. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. dedent: A boolean, if set, remove the whitespace in front of the lines. encoding: A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. \"\"\" if dedent: string = textwrap.dedent(string) entries, errors, options_map = _load([(string, False)], log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map beancount.loader.needs_refresh(options_map) \uf0c1 Predicate that returns true if at least one of the input files may have changed. Parameters: options_map \u2013 An options dict as per the parser. mtime \u2013 A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. Source code in beancount/loader.py def needs_refresh(options_map): \"\"\"Predicate that returns true if at least one of the input files may have changed. Args: options_map: An options dict as per the parser. mtime: A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. \"\"\" if options_map is None: return True input_hash = compute_input_hash(options_map['include']) return 'input_hash' not in options_map or input_hash != options_map['input_hash'] beancount.loader.pickle_cache_function(cache_getter, time_threshold, function) \uf0c1 Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold \u2013 A float, the number of seconds below which we don't bother caching. function \u2013 A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. Source code in beancount/loader.py def pickle_cache_function(cache_getter, time_threshold, function): \"\"\"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold: A float, the number of seconds below which we don't bother caching. function: A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): cache_filename = cache_getter(toplevel_filename) # Read the cache if it exists in order to get the list of files whose # timestamps to check. exists = path.exists(cache_filename) if exists: with open(cache_filename, 'rb') as file: try: result = pickle.load(file) except Exception as exc: # Note: Not a big fan of doing this, but here we handle all # possible exceptions because unpickling of an old or # corrupted pickle file manifests as a variety of different # exception types. # The cache file is corrupted; ignore it and recompute. logging.error(\"Cache file is corrupted: %s; recomputing.\", exc) result = None else: # Check that the latest timestamp has not been written after the # cache file. entries, errors, options_map = result if not needs_refresh(options_map): # All timestamps are legit; cache hit. return result # We failed; recompute the value. if exists: try: os.remove(cache_filename) except OSError as exc: # Warn for errors on read-only filesystems. logging.warning(\"Could not remove picklecache file %s: %s\", cache_filename, exc) time_before = time.time() result = function(toplevel_filename, *args, **kw) time_after = time.time() # Overwrite the cache file if the time it takes to compute it # justifies it. if time_after - time_before > time_threshold: try: with open(cache_filename, 'wb') as file: pickle.dump(result, file) except Exception as exc: logging.warning(\"Could not write to picklecache file %s: %s\", cache_filename, exc) return result return wrapped beancount.loader.run_transformations(entries, parse_errors, options_map, log_timings) \uf0c1 Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Parameters: entries \u2013 A list of directives as read from the parser. parse_errors \u2013 A list of errors so far. options_map \u2013 An options dict as read from the parser. log_timings \u2013 A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. Source code in beancount/loader.py def run_transformations(entries, parse_errors, options_map, log_timings): \"\"\"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Args: entries: A list of directives as read from the parser. parse_errors: A list of errors so far. options_map: An options dict as read from the parser. log_timings: A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. \"\"\" # A list of errors to extend (make a copy to avoid modifying the input). errors = list(parse_errors) # Process the plugins. if options_map['plugin_processing_mode'] == 'raw': plugins_iter = options_map[\"plugin\"] elif options_map['plugin_processing_mode'] == 'default': plugins_iter = itertools.chain(DEFAULT_PLUGINS_PRE, options_map[\"plugin\"], DEFAULT_PLUGINS_POST) else: assert \"Invalid value for plugin_processing_mode: {}\".format( options_map['plugin_processing_mode']) for plugin_name, plugin_config in plugins_iter: # Issue a warning on a renamed module. renamed_name = RENAMED_MODULES.get(plugin_name, None) if renamed_name: warnings.warn(\"Deprecation notice: Module '{}' has been renamed to '{}'; \" \"please adjust your plugin directive.\".format( plugin_name, renamed_name)) plugin_name = renamed_name # Try to import the module. try: module = importlib.import_module(plugin_name) if not hasattr(module, '__plugins__'): continue with misc_utils.log_time(plugin_name, log_timings, indent=2): # Run each transformer function in the plugin. for function_name in module.__plugins__: if isinstance(function_name, str): # Support plugin functions provided by name. callback = getattr(module, function_name) else: # Support function types directly, not just names. callback = function_name if plugin_config is not None: entries, plugin_errors = callback(entries, options_map, plugin_config) else: entries, plugin_errors = callback(entries, options_map) errors.extend(plugin_errors) # Ensure that the entries are sorted. Don't trust the plugins # themselves. entries.sort(key=data.entry_sortkey) except (ImportError, TypeError) as exc: # Upon failure, just issue an error. errors.append(LoadError(data.new_metadata(\"\", 0), 'Error importing \"{}\": {}'.format( plugin_name, str(exc)), None)) return entries, errors","title":"beancount.loader"},{"location":"api_reference/beancount.loader.html#beancountloader","text":"Loader code. This is the main entry point to load up a file.","title":"beancount.loader"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError","text":"LoadError(source, message, entry)","title":"LoadError"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/loader.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__new__","text":"Create new instance of LoadError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/loader.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.aggregate_options_map","text":"Aggregate some of the attributes of options map. Parameters: options_map \u2013 The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map \u2013 A source map whose values we'd like to see aggregated. Source code in beancount/loader.py def aggregate_options_map(options_map, src_options_map): \"\"\"Aggregate some of the attributes of options map. Args: options_map: The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map: A source map whose values we'd like to see aggregated. \"\"\" op_currencies = options_map[\"operating_currency\"] for currency in src_options_map[\"operating_currency\"]: if currency not in op_currencies: op_currencies.append(currency) commodities = options_map[\"commodities\"] for currency in src_options_map[\"commodities\"]: commodities.add(currency)","title":"aggregate_options_map()"},{"location":"api_reference/beancount.loader.html#beancount.loader.combine_plugins","text":"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Parameters: *plugins_modules \u2013 A sequence of module objects. Returns: A list that can be assigned to the new module's plugins attribute. Source code in beancount/loader.py def combine_plugins(*plugin_modules): \"\"\"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Args: *plugins_modules: A sequence of module objects. Returns: A list that can be assigned to the new module's __plugins__ attribute. \"\"\" modules = [] for module in plugin_modules: modules.extend([getattr(module, name) for name in module.__plugins__]) return modules","title":"combine_plugins()"},{"location":"api_reference/beancount.loader.html#beancount.loader.compute_input_hash","text":"Compute a hash of the input data. Parameters: filenames \u2013 A list of input files. Order is not relevant. Source code in beancount/loader.py def compute_input_hash(filenames): \"\"\"Compute a hash of the input data. Args: filenames: A list of input files. Order is not relevant. \"\"\" md5 = hashlib.md5() for filename in sorted(filenames): md5.update(filename.encode('utf8')) if not path.exists(filename): continue stat = os.stat(filename) md5.update(struct.pack('dd', stat.st_mtime_ns, stat.st_size)) return md5.hexdigest()","title":"compute_input_hash()"},{"location":"api_reference/beancount.loader.html#beancount.loader.delete_cache_function","text":"A wrapper that removes the cached filename. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function \u2013 A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. Source code in beancount/loader.py def delete_cache_function(cache_getter, function): \"\"\"A wrapper that removes the cached filename. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function: A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): # Delete the cache. cache_filename = cache_getter(toplevel_filename) if path.exists(cache_filename): os.remove(cache_filename) # Invoke the original function. return function(toplevel_filename, *args, **kw) return wrapped","title":"delete_cache_function()"},{"location":"api_reference/beancount.loader.html#beancount.loader.get_cache_filename","text":"Compute the cache filename from a given pattern and the top-level filename. Parameters: pattern ( str ) \u2013 A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename ( str ) \u2013 The top-level filename. Returns: str \u2013 The resolved cache filename. Source code in beancount/loader.py def get_cache_filename(pattern: str, filename: str) -> str: \"\"\"Compute the cache filename from a given pattern and the top-level filename. Args: pattern: A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename: The top-level filename. Returns: The resolved cache filename. \"\"\" abs_filename = path.abspath(filename) if path.isabs(pattern): abs_pattern = pattern else: abs_pattern = path.join(path.dirname(abs_filename), pattern) return abs_pattern.format(filename=path.basename(filename))","title":"get_cache_filename()"},{"location":"api_reference/beancount.loader.html#beancount.loader.initialize","text":"Initialize the loader. Source code in beancount/loader.py def initialize(use_cache: bool, cache_filename: Optional[str] = None): \"\"\"Initialize the loader.\"\"\" # Unless an environment variable disables it, use the pickle load cache # automatically. Note that this works across all Python programs running the # loader which is why it's located here. # pylint: disable=invalid-name global _load_file # Make a function to compute the cache filename. cache_pattern = (cache_filename or os.getenv('BEANCOUNT_LOAD_CACHE_FILENAME') or PICKLE_CACHE_FILENAME) cache_getter = functools.partial(get_cache_filename, cache_pattern) if use_cache: _load_file = pickle_cache_function(cache_getter, PICKLE_CACHE_THRESHOLD, _uncached_load_file) else: if cache_filename is not None: logging.warning(\"Cache disabled; \" \"Explicitly overridden cache filename %s will be ignored.\", cache_filename) _load_file = delete_cache_function(cache_getter, _uncached_load_file)","title":"initialize()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_doc","text":"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. Source code in beancount/loader.py def load_doc(expect_errors=False): \"\"\"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: A callable method, that accepts the three return arguments that load() returns. Returns: A decorated test function. \"\"\" @functools.wraps(fun) def wrapper(self): entries, errors, options_map = load_string(fun.__doc__, dedent=True) if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") # Note: Even if we expected no errors, we call this function with an # empty 'errors' list. This is so that the interface does not change # based on the arguments to the decorator, which would be somewhat # ugly and which would require explanation. return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator","title":"load_doc()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_encrypted_file","text":"Load an encrypted Beancount input file. Parameters: filename \u2013 The name of an encrypted file to be parsed. log_timings \u2013 See load_string(). log_errors \u2013 See load_string(). extra_validations \u2013 See load_string(). dedent \u2013 See load_string(). encoding \u2013 See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Load an encrypted Beancount input file. Args: filename: The name of an encrypted file to be parsed. log_timings: See load_string(). log_errors: See load_string(). extra_validations: See load_string(). dedent: See load_string(). encoding: See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" contents = encryption.read_encrypted_file(filename) return load_string(contents, log_timings=log_timings, log_errors=log_errors, extra_validations=extra_validations, encoding=encoding)","title":"load_encrypted_file()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_file","text":"Open a Beancount input file, parse it, run transformations and validate. Parameters: filename \u2013 The name of the file to be parsed. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. encoding \u2013 A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None): \"\"\"Open a Beancount input file, parse it, run transformations and validate. Args: filename: The name of the file to be parsed. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. encoding: A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" filename = path.expandvars(path.expanduser(filename)) if not path.isabs(filename): filename = path.normpath(path.join(os.getcwd(), filename)) if encryption.is_encrypted_file(filename): # Note: Caching is not supported for encrypted files. entries, errors, options_map = load_encrypted_file( filename, log_timings, log_errors, extra_validations, False, encoding) else: entries, errors, options_map = _load_file( filename, log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map","title":"load_file()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_string","text":"Open a Beancount input string, parse it, run transformations and validate. Parameters: string \u2013 A Beancount input string. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. dedent \u2013 A boolean, if set, remove the whitespace in front of the lines. encoding \u2013 A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. Source code in beancount/loader.py def load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Open a Beancount input string, parse it, run transformations and validate. Args: string: A Beancount input string. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. dedent: A boolean, if set, remove the whitespace in front of the lines. encoding: A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. \"\"\" if dedent: string = textwrap.dedent(string) entries, errors, options_map = _load([(string, False)], log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map","title":"load_string()"},{"location":"api_reference/beancount.loader.html#beancount.loader.needs_refresh","text":"Predicate that returns true if at least one of the input files may have changed. Parameters: options_map \u2013 An options dict as per the parser. mtime \u2013 A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. Source code in beancount/loader.py def needs_refresh(options_map): \"\"\"Predicate that returns true if at least one of the input files may have changed. Args: options_map: An options dict as per the parser. mtime: A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. \"\"\" if options_map is None: return True input_hash = compute_input_hash(options_map['include']) return 'input_hash' not in options_map or input_hash != options_map['input_hash']","title":"needs_refresh()"},{"location":"api_reference/beancount.loader.html#beancount.loader.pickle_cache_function","text":"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold \u2013 A float, the number of seconds below which we don't bother caching. function \u2013 A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. Source code in beancount/loader.py def pickle_cache_function(cache_getter, time_threshold, function): \"\"\"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold: A float, the number of seconds below which we don't bother caching. function: A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): cache_filename = cache_getter(toplevel_filename) # Read the cache if it exists in order to get the list of files whose # timestamps to check. exists = path.exists(cache_filename) if exists: with open(cache_filename, 'rb') as file: try: result = pickle.load(file) except Exception as exc: # Note: Not a big fan of doing this, but here we handle all # possible exceptions because unpickling of an old or # corrupted pickle file manifests as a variety of different # exception types. # The cache file is corrupted; ignore it and recompute. logging.error(\"Cache file is corrupted: %s; recomputing.\", exc) result = None else: # Check that the latest timestamp has not been written after the # cache file. entries, errors, options_map = result if not needs_refresh(options_map): # All timestamps are legit; cache hit. return result # We failed; recompute the value. if exists: try: os.remove(cache_filename) except OSError as exc: # Warn for errors on read-only filesystems. logging.warning(\"Could not remove picklecache file %s: %s\", cache_filename, exc) time_before = time.time() result = function(toplevel_filename, *args, **kw) time_after = time.time() # Overwrite the cache file if the time it takes to compute it # justifies it. if time_after - time_before > time_threshold: try: with open(cache_filename, 'wb') as file: pickle.dump(result, file) except Exception as exc: logging.warning(\"Could not write to picklecache file %s: %s\", cache_filename, exc) return result return wrapped","title":"pickle_cache_function()"},{"location":"api_reference/beancount.loader.html#beancount.loader.run_transformations","text":"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Parameters: entries \u2013 A list of directives as read from the parser. parse_errors \u2013 A list of errors so far. options_map \u2013 An options dict as read from the parser. log_timings \u2013 A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. Source code in beancount/loader.py def run_transformations(entries, parse_errors, options_map, log_timings): \"\"\"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Args: entries: A list of directives as read from the parser. parse_errors: A list of errors so far. options_map: An options dict as read from the parser. log_timings: A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. \"\"\" # A list of errors to extend (make a copy to avoid modifying the input). errors = list(parse_errors) # Process the plugins. if options_map['plugin_processing_mode'] == 'raw': plugins_iter = options_map[\"plugin\"] elif options_map['plugin_processing_mode'] == 'default': plugins_iter = itertools.chain(DEFAULT_PLUGINS_PRE, options_map[\"plugin\"], DEFAULT_PLUGINS_POST) else: assert \"Invalid value for plugin_processing_mode: {}\".format( options_map['plugin_processing_mode']) for plugin_name, plugin_config in plugins_iter: # Issue a warning on a renamed module. renamed_name = RENAMED_MODULES.get(plugin_name, None) if renamed_name: warnings.warn(\"Deprecation notice: Module '{}' has been renamed to '{}'; \" \"please adjust your plugin directive.\".format( plugin_name, renamed_name)) plugin_name = renamed_name # Try to import the module. try: module = importlib.import_module(plugin_name) if not hasattr(module, '__plugins__'): continue with misc_utils.log_time(plugin_name, log_timings, indent=2): # Run each transformer function in the plugin. for function_name in module.__plugins__: if isinstance(function_name, str): # Support plugin functions provided by name. callback = getattr(module, function_name) else: # Support function types directly, not just names. callback = function_name if plugin_config is not None: entries, plugin_errors = callback(entries, options_map, plugin_config) else: entries, plugin_errors = callback(entries, options_map) errors.extend(plugin_errors) # Ensure that the entries are sorted. Don't trust the plugins # themselves. entries.sort(key=data.entry_sortkey) except (ImportError, TypeError) as exc: # Upon failure, just issue an error. errors.append(LoadError(data.new_metadata(\"\", 0), 'Error importing \"{}\": {}'.format( plugin_name, str(exc)), None)) return entries, errors","title":"run_transformations()"},{"location":"api_reference/beancount.ops.html","text":"beancount.ops \uf0c1 Operations on the entries defined in the core modules. This package contains various functions which operate on lists of entries. beancount.ops.balance \uf0c1 Automatic padding of gaps between entries. beancount.ops.balance.BalanceError ( tuple ) \uf0c1 BalanceError(source, message, entry) beancount.ops.balance.BalanceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/balance.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.balance.BalanceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BalanceError(source, message, entry) beancount.ops.balance.BalanceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/balance.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.balance.check(entries, options_map) \uf0c1 Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Parameters: entries \u2013 A list of directives. options_map \u2013 A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. Source code in beancount/ops/balance.py def check(entries, options_map): \"\"\"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Args: entries: A list of directives. options_map: A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. \"\"\" new_entries = [] check_errors = [] # This is similar to realization, but performed in a different order, and # where we only accumulate inventories for accounts that have balance # assertions in them (this saves on time). Here we process the entries one # by one along with the balance checks. We use a temporary realization in # order to hold the incremental tree of balances, so that we can easily get # the amounts of an account's subaccounts for making checks on parent # accounts. real_root = realization.RealAccount('') # Figure out the set of accounts for which we need to compute a running # inventory balance. asserted_accounts = {entry.account for entry in entries if isinstance(entry, Balance)} # Add all children accounts of an asserted account to be calculated as well, # and pre-create these accounts, and only those (we're just being tight to # make sure). asserted_match_list = [account.parent_matcher(account_) for account_ in asserted_accounts] for account_ in getters.get_accounts(entries): if (account_ in asserted_accounts or any(match(account_) for match in asserted_match_list)): realization.get_or_create(real_root, account_) # Get the Open directives for each account. open_close_map = getters.get_account_open_close(entries) for entry in entries: if isinstance(entry, Transaction): # For each of the postings' accounts, update the balance inventory. for posting in entry.postings: real_account = realization.get(real_root, posting.account) # The account will have been created only if we're meant to track it. if real_account is not None: # Note: Always allow negative lots for the purpose of balancing. # This error should show up somewhere else than here. real_account.balance.add_position(posting) elif isinstance(entry, Balance): # Check that the currency of the balance check is one of the allowed # currencies for that account. expected_amount = entry.amount try: open, _ = open_close_map[entry.account] except KeyError: check_errors.append( BalanceError(entry.meta, \"Account '{}' does not exist: \".format(entry.account), entry)) continue if (expected_amount is not None and open and open.currencies and expected_amount.currency not in open.currencies): check_errors.append( BalanceError(entry.meta, \"Invalid currency '{}' for Balance directive: \".format( expected_amount.currency), entry)) # Sum up the current balances for this account and its # sub-accounts. We want to support checks for parent accounts # for the total sum of their subaccounts. # # FIXME: Improve the performance further by computing the balance # for the desired currency only. This won't allow us to cache in # this way but may be faster, if we're not asserting all the # currencies. Furthermore, we could probably avoid recomputing the # balance if a subtree of positions hasn't been invalidated by a new # position added to the realization. Do this. real_account = realization.get(real_root, entry.account) assert real_account is not None, \"Missing {}\".format(entry.account) subtree_balance = realization.compute_balance(real_account, leaf_only=False) # Get only the amount in the desired currency. balance_amount = subtree_balance.get_currency_units(expected_amount.currency) # Check if the amount is within bounds of the expected amount. diff_amount = amount.sub(balance_amount, expected_amount) # Use the specified tolerance or automatically infer it. tolerance = get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: check_errors.append( BalanceError(entry.meta, (\"Balance failed for '{}': \" \"expected {} != accumulated {} ({} {})\").format( entry.account, expected_amount, balance_amount, abs(diff_amount.number), ('too much' if diff_amount.number > 0 else 'too little')), entry)) # Substitute the entry by a failing entry, with the diff_amount # field set on it. I'm not entirely sure that this is the best # of ideas, maybe leaving the original check intact and insert a # new error entry might be more functional or easier to # understand. entry = entry._replace( meta=entry.meta.copy(), diff_amount=diff_amount) new_entries.append(entry) return new_entries, check_errors beancount.ops.balance.get_balance_tolerance(balance_entry, options_map) \uf0c1 Get the tolerance amount for a single entry. Parameters: balance_entry \u2013 An instance of data.Balance options_map \u2013 An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. Source code in beancount/ops/balance.py def get_balance_tolerance(balance_entry, options_map): \"\"\"Get the tolerance amount for a single entry. Args: balance_entry: An instance of data.Balance options_map: An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. \"\"\" if balance_entry.tolerance is not None: # Use the balance-specific tolerance override if it is provided. tolerance = balance_entry.tolerance else: expo = balance_entry.amount.number.as_tuple().exponent if expo < 0: # Be generous and always allow twice the multiplier on Balance and # Pad because the user creates these and the rounding of those # balances may often be further off than those used within a single # transaction. tolerance = options_map[\"inferred_tolerance_multiplier\"] * 2 tolerance = ONE.scaleb(expo) * tolerance else: tolerance = ZERO return tolerance beancount.ops.basicops \uf0c1 Basic filtering and aggregation operations on lists of entries. This module contains some common basic operations on entries that are complex enough not to belong in core/data.py. beancount.ops.basicops.filter_link(link, entries) \uf0c1 Yield all the entries which have the given link. Parameters: link \u2013 A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. Source code in beancount/ops/basicops.py def filter_link(link, entries): \"\"\"Yield all the entries which have the given link. Args: link: A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.links and link in entry.links): yield entry beancount.ops.basicops.filter_tag(tag, entries) \uf0c1 Yield all the entries which have the given tag. Parameters: tag \u2013 A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. Source code in beancount/ops/basicops.py def filter_tag(tag, entries): \"\"\"Yield all the entries which have the given tag. Args: tag: A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.tags and tag in entry.tags): yield entry beancount.ops.basicops.get_common_accounts(entries) \uf0c1 Compute the intersection of the accounts on the given entries. Parameters: entries \u2013 A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. Source code in beancount/ops/basicops.py def get_common_accounts(entries): \"\"\"Compute the intersection of the accounts on the given entries. Args: entries: A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. \"\"\" assert all(isinstance(entry, data.Transaction) for entry in entries) # If there is a single entry, the common accounts to it is all its accounts. # Note that this also works with no entries (yields an empty set). if len(entries) < 2: if entries: intersection = {posting.account for posting in entries[0].postings} else: intersection = set() else: entries_iter = iter(entries) intersection = set(posting.account for posting in next(entries_iter).postings) for entry in entries_iter: accounts = set(posting.account for posting in entry.postings) intersection &= accounts if not intersection: break return intersection beancount.ops.basicops.group_entries_by_link(entries) \uf0c1 Group the list of entries by link. Parameters: entries \u2013 A list of directives/transactions to process. Returns: A dict of link-name to list of entries. Source code in beancount/ops/basicops.py def group_entries_by_link(entries): \"\"\"Group the list of entries by link. Args: entries: A list of directives/transactions to process. Returns: A dict of link-name to list of entries. \"\"\" link_groups = defaultdict(list) for entry in entries: if not (isinstance(entry, data.Transaction) and entry.links): continue for link in entry.links: link_groups[link].append(entry) return link_groups beancount.ops.compress \uf0c1 Compress multiple entries into a single one. This can be used during import to compress the effective output, for accounts with a large number of similar entries. For example, I had a trading account which would pay out interest every single day. I have no desire to import the full detail of these daily interests, and compressing these interest-only entries to monthly ones made sense. This is the code that was used to carry this out. beancount.ops.compress.compress(entries, predicate) \uf0c1 Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Parameters: entries \u2013 A list of directives. predicate \u2013 A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. Source code in beancount/ops/compress.py def compress(entries, predicate): \"\"\"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Args: entries: A list of directives. predicate: A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. \"\"\" new_entries = [] pending = [] for entry in entries: if isinstance(entry, data.Transaction) and predicate(entry): # Save for compressing later. pending.append(entry) else: # Compress and output all the pending entries. if pending: new_entries.append(merge(pending, pending[-1])) pending.clear() # Output the differing entry. new_entries.append(entry) if pending: new_entries.append(merge(pending, pending[-1])) return new_entries beancount.ops.compress.merge(entries, prototype_txn) \uf0c1 Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Parameters: entries \u2013 A list of directives. prototype_txn \u2013 A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. Source code in beancount/ops/compress.py def merge(entries, prototype_txn): \"\"\"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Args: entries: A list of directives. prototype_txn: A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. \"\"\" # Aggregate the postings together. This is a mapping of numberless postings # to their number of units. postings_map = collections.defaultdict(Decimal) for entry in data.filter_txns(entries): for posting in entry.postings: # We strip the number off the posting to act as an aggregation key. key = data.Posting(posting.account, Amount(None, posting.units.currency), posting.cost, posting.price, posting.flag, None) postings_map[key] += posting.units.number # Create a new transaction with the aggregated postings. new_entry = data.Transaction(prototype_txn.meta, prototype_txn.date, prototype_txn.flag, prototype_txn.payee, prototype_txn.narration, data.EMPTY_SET, data.EMPTY_SET, []) # Sort for at least some stability of output. sorted_items = sorted(postings_map.items(), key=lambda item: (item[0].account, item[0].units.currency, item[1])) # Issue the merged postings. for posting, number in sorted_items: units = Amount(number, posting.units.currency) new_entry.postings.append( data.Posting(posting.account, units, posting.cost, posting.price, posting.flag, posting.meta)) return new_entry beancount.ops.documents \uf0c1 Everything that relates to creating the Document directives. beancount.ops.documents.DocumentError ( tuple ) \uf0c1 DocumentError(source, message, entry) beancount.ops.documents.DocumentError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/documents.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.documents.DocumentError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of DocumentError(source, message, entry) beancount.ops.documents.DocumentError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/documents.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.documents.find_documents(directory, input_filename, accounts_only=None, strict=False) \uf0c1 Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Parameters: directory \u2013 A string, the name of the root of the directory hierarchy to be searched. input_filename \u2013 The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only \u2013 A set of valid accounts strings to search for. strict \u2013 A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. Source code in beancount/ops/documents.py def find_documents(directory, input_filename, accounts_only=None, strict=False): \"\"\"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Args: directory: A string, the name of the root of the directory hierarchy to be searched. input_filename: The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only: A set of valid accounts strings to search for. strict: A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. \"\"\" errors = [] # Compute the documents directory name relative to the beancount input # file itself. if not path.isabs(directory): input_directory = path.dirname(input_filename) directory = path.abspath(path.normpath(path.join(input_directory, directory))) # If the directory does not exist, just generate an error and return. if not path.exists(directory): meta = data.new_metadata(input_filename, 0) error = DocumentError( meta, \"Document root '{}' does not exist\".format(directory), None) return ([], [error]) # Walk the hierarchy of files. entries = [] for root, account_name, dirs, files in account.walk(directory): # Look for files that have a dated filename. for filename in files: match = re.match(r'(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d).(.*)', filename) if not match: continue # If a restricting set of accounts was specified, skip document # directives found in accounts with no corresponding account name. if accounts_only is not None and not account_name in accounts_only: if strict: if any(account_name.startswith(account) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in child account {}\".format( filename, account_name), None)) elif any(account.startswith(account_name) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in parent account {}\".format( filename, account_name), None)) continue # Create a new directive. meta = data.new_metadata(input_filename, 0) try: date = datetime.date(*map(int, match.group(1, 2, 3))) except ValueError as exc: errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Invalid date on document file '{}': {}\".format( filename, exc), None)) else: entry = data.Document(meta, date, account_name, path.join(root, filename), data.EMPTY_SET, data.EMPTY_SET) entries.append(entry) return (entries, errors) beancount.ops.documents.process_documents(entries, options_map) \uf0c1 Check files for document directives and create documents directives automatically. Parameters: entries \u2013 A list of all directives parsed from the file. options_map \u2013 An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. Source code in beancount/ops/documents.py def process_documents(entries, options_map): \"\"\"Check files for document directives and create documents directives automatically. Args: entries: A list of all directives parsed from the file. options_map: An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. \"\"\" filename = options_map[\"filename\"] # Detect filenames that should convert into entries. autodoc_entries = [] autodoc_errors = [] document_dirs = options_map['documents'] if document_dirs: # Restrict to the list of valid accounts only. accounts = getters.get_accounts(entries) # Accumulate all the entries. for directory in map(path.normpath, document_dirs): new_entries, new_errors = find_documents(directory, filename, accounts) autodoc_entries.extend(new_entries) autodoc_errors.extend(new_errors) # Merge the two lists of entries and errors. Keep the entries sorted. entries.extend(autodoc_entries) entries.sort(key=data.entry_sortkey) return (entries, autodoc_errors) beancount.ops.documents.verify_document_files_exist(entries, unused_options_map) \uf0c1 Verify that the document entries point to existing files. Parameters: entries \u2013 a list of directives whose documents need to be validated. unused_options_map \u2013 A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. Source code in beancount/ops/documents.py def verify_document_files_exist(entries, unused_options_map): \"\"\"Verify that the document entries point to existing files. Args: entries: a list of directives whose documents need to be validated. unused_options_map: A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. \"\"\" errors = [] for entry in entries: if not isinstance(entry, data.Document): continue if not path.exists(entry.filename): errors.append( DocumentError(entry.meta, 'File does not exist: \"{}\"'.format(entry.filename), entry)) return entries, errors beancount.ops.holdings \uf0c1 Compute final holdings for a list of entries. beancount.ops.holdings.Holding ( tuple ) \uf0c1 Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) beancount.ops.holdings.Holding.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/holdings.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.holdings.Holding.__new__(_cls, account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) special staticmethod \uf0c1 Create new instance of Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) beancount.ops.holdings.Holding.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/holdings.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.holdings.aggregate_holdings_by(holdings, keyfun) \uf0c1 Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Parameters: keyfun \u2013 A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. Source code in beancount/ops/holdings.py def aggregate_holdings_by(holdings, keyfun): \"\"\"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Args: keyfun: A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. \"\"\" # Aggregate the groups of holdings. grouped = collections.defaultdict(list) for holding in holdings: key = (keyfun(holding), holding.cost_currency) grouped[key].append(holding) grouped_holdings = (aggregate_holdings_list(key_holdings) for key_holdings in grouped.values()) # We could potentially filter out holdings with zero units here. These types # of holdings might occur on a group with leaked (i.e., non-zero) cost basis # and zero units. However, sometimes are valid merging of multiple # currencies may occur, and the number value will be legitimately set to # ZERO (for various reasons downstream), so we prefer not to ignore the # holding. Callers must be prepared to deal with a holding of ZERO units and # a non-zero cost basis. {0ed05c502e63, b/16} ## nonzero_holdings = (holding ## for holding in grouped_holdings ## if holding.number != ZERO) # Return the holdings in order. return sorted(grouped_holdings, key=lambda holding: (holding.account, holding.currency)) beancount.ops.holdings.aggregate_holdings_list(holdings) \uf0c1 Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Parameters: holdings \u2013 A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Exceptions: ValueError \u2013 If multiple cost currencies encountered. Source code in beancount/ops/holdings.py def aggregate_holdings_list(holdings): \"\"\"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Args: holdings: A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Raises: ValueError: If multiple cost currencies encountered. \"\"\" if not holdings: return None # Note: Holding is a bit overspecified with book and market values. We # recompute them from cost and price numbers here anyhow. units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() price_dates = set() book_value_seen = False market_value_seen = False for holding in holdings: units += holding.number accounts.add(holding.account) price_dates.add(holding.price_date) currencies.add(holding.currency) cost_currencies.add(holding.cost_currency) if holding.book_value is not None: total_book_value += holding.book_value book_value_seen = True elif holding.cost_number is not None: total_book_value += holding.number * holding.cost_number book_value_seen = True if holding.market_value is not None: total_market_value += holding.market_value market_value_seen = True elif holding.price_number is not None: total_market_value += holding.number * holding.price_number market_value_seen = True if book_value_seen: average_cost = total_book_value / units if units else None else: total_book_value = None average_cost = None if market_value_seen: average_price = total_market_value / units if units else None else: total_market_value = None average_price = None if len(cost_currencies) != 1: raise ValueError(\"Cost currencies are not homogeneous for aggregation: {}\".format( ','.join(map(str, cost_currencies)))) units = units if len(currencies) == 1 else ZERO currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) price_date = price_dates.pop() if len(price_dates) == 1 else None return Holding(account_, units, currency, average_cost, cost_currency, total_book_value, total_market_value, average_price, price_date) beancount.ops.holdings.convert_to_currency(price_map, target_currency, holdings_list) \uf0c1 Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Parameters: price_map \u2013 A price-map, as built by prices.build_price_map(). target_currency \u2013 The target common currency to convert amounts to. holdings_list \u2013 A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. Source code in beancount/ops/holdings.py def convert_to_currency(price_map, target_currency, holdings_list): \"\"\"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Args: price_map: A price-map, as built by prices.build_price_map(). target_currency: The target common currency to convert amounts to. holdings_list: A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. \"\"\" # A list of the fields we should convert. convert_fields = ('cost_number', 'book_value', 'market_value', 'price_number') new_holdings = [] for holding in holdings_list: if holding.cost_currency == target_currency: # The holding is already priced in the target currency; do nothing. new_holding = holding else: if holding.cost_currency is None: # There is no cost currency; make the holding priced in its own # units. The price-map should yield a rate of 1.0 and everything # else works out. if holding.currency is None: raise ValueError(\"Invalid currency '{}'\".format(holding.currency)) holding = holding._replace(cost_currency=holding.currency) # Fill in with book and market value as well. if holding.book_value is None: holding = holding._replace(book_value=holding.number) if holding.market_value is None: holding = holding._replace(market_value=holding.number) assert holding.cost_currency, \"Missing cost currency: {}\".format(holding) base_quote = (holding.cost_currency, target_currency) # Get the conversion rate and replace the required numerical # fields.. _, rate = prices.get_latest_price(price_map, base_quote) if rate is not None: new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number, r=rate: number if number is None else number * r, holding) # Ensure we set the new cost currency after conversion. new_holding = new_holding._replace(cost_currency=target_currency) else: # Could not get the rate... clear every field and set the cost # currency to None. This enough marks the holding conversion as # a failure. new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number: None, holding) new_holding = new_holding._replace(cost_currency=None) new_holdings.append(new_holding) return new_holdings beancount.ops.holdings.get_commodities_at_date(entries, options_map, date=None) \uf0c1 Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency \u2013 The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). Source code in beancount/ops/holdings.py def get_commodities_at_date(entries, options_map, date=None): \"\"\"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: * The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. * The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Args: entries: A list of directives. date: A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency: The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). \"\"\" # Remove all the entries after the given date, if requested. if date is not None: entries = summarize.truncate(entries, date) # Get the list of holdings at the particular date. holdings_list = get_final_holdings(entries) # Obtain the unique list of currencies we need to fetch. commodities_list = {(holding.currency, holding.cost_currency) for holding in holdings_list} # Add in the associated ticker symbols. commodities_map = getters.get_commodity_map(entries) commodities_symbols_list = [] for currency, cost_currency in sorted(commodities_list): try: commodity_entry = commodities_map[currency] ticker = commodity_entry.meta.get('ticker', None) quote_currency = commodity_entry.meta.get('quote', None) except KeyError: ticker = None quote_currency = None commodities_symbols_list.append( (currency, cost_currency, quote_currency, ticker)) return commodities_symbols_list beancount.ops.holdings.get_final_holdings(entries, included_account_types=None, price_map=None, date=None) \uf0c1 Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Parameters: entries \u2013 A list of directives. included_account_types \u2013 A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields \u2013 Source code in beancount/ops/holdings.py def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): \"\"\"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Args: entries: A list of directives. included_account_types: A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields: \"\"\" # Remove the entries inserted by unrealized gains/losses. Those entries do # affect asset accounts, and we don't want them to appear in holdings. # # Note: Perhaps it would make sense to generalize this concept of \"inserted # unrealized gains.\" simple_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or entry.flag != flags.FLAG_UNREALIZED)] # Realize the accounts into a tree (because we want the positions by-account). root_account = realization.realize(simple_entries) # For each account, look at the list of positions and build a list. holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): if included_account_types: # Skip accounts of invalid types, we only want to reflect the requested # account types, typically assets and liabilities. account_type = account_types.get_account_type(real_account.account) if account_type not in included_account_types: continue for pos in real_account.balance.get_positions(): if pos.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (pos.units.currency, pos.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = pos.units.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.units.number, pos.units.currency, pos.cost.number, pos.cost.currency, pos.units.number * pos.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.units.number, pos.units.currency, None, pos.units.currency, pos.units.number, pos.units.number, None, None) holdings.append(holding) return holdings beancount.ops.holdings.holding_to_position(holding) \uf0c1 Convert the holding to a position. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_position(holding): \"\"\"Convert the holding to a position. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" return position.Position( amount.Amount(holding.number, holding.currency), (position.Cost(holding.cost_number, holding.cost_currency, None, None) if holding.cost_number else None)) beancount.ops.holdings.holding_to_posting(holding) \uf0c1 Convert the holding to an instance of Posting. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_posting(holding): \"\"\"Convert the holding to an instance of Posting. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" position_ = holding_to_position(holding) price = (amount.Amount(holding.price_number, holding.cost_currency) if holding.price_number else None) return data.Posting(holding.account, position_.units, position_.cost, price, None, None) beancount.ops.holdings.reduce_relative(holdings) \uf0c1 Convert the market and book values of the given list of holdings to relative data. Parameters: holdings \u2013 A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. Source code in beancount/ops/holdings.py def reduce_relative(holdings): \"\"\"Convert the market and book values of the given list of holdings to relative data. Args: holdings: A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. \"\"\" # Group holdings by value currency. by_currency = collections.defaultdict(list) ordering = {} for index, holding in enumerate(holdings): ordering.setdefault(holding.cost_currency, index) by_currency[holding.cost_currency].append(holding) fractional_holdings = [] for currency in sorted(by_currency, key=ordering.get): currency_holdings = by_currency[currency] # Compute total market value for that currency. total_book_value = ZERO total_market_value = ZERO for holding in currency_holdings: if holding.book_value: total_book_value += holding.book_value if holding.market_value: total_market_value += holding.market_value # Sort the currency's holdings with decreasing values of market value. currency_holdings.sort( key=lambda holding: holding.market_value or ZERO, reverse=True) # Output new holdings with the relevant values replaced. for holding in currency_holdings: fractional_holdings.append( holding._replace(book_value=(holding.book_value / total_book_value if holding.book_value is not None else None), market_value=(holding.market_value / total_market_value if holding.market_value is not None else None))) return fractional_holdings beancount.ops.holdings.scale_holding(holding, scale_factor) \uf0c1 Scale the values of a holding. Parameters: holding \u2013 An instance of Holding. scale_factor \u2013 A float or Decimal number. Returns: A scaled copy of the holding. Source code in beancount/ops/holdings.py def scale_holding(holding, scale_factor): \"\"\"Scale the values of a holding. Args: holding: An instance of Holding. scale_factor: A float or Decimal number. Returns: A scaled copy of the holding. \"\"\" return Holding( holding.account, holding.number * scale_factor if holding.number else None, holding.currency, holding.cost_number, holding.cost_currency, holding.book_value * scale_factor if holding.book_value else None, holding.market_value * scale_factor if holding.market_value else None, holding.price_number, holding.price_date) beancount.ops.lifetimes \uf0c1 Given a Beancount ledger, compute time intervals where we hold each commodity. This script computes, for each commodity, which time intervals it is required at. This can then be used to identify a list of dates at which we need to fetch prices in order to properly fill the price database. beancount.ops.lifetimes.compress_intervals_days(intervals, num_days) \uf0c1 Compress a list of date pairs to ignore short stretches of unused days. Parameters: intervals \u2013 A list of pairs of datetime.date instances. num_days \u2013 An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_intervals_days(intervals, num_days): \"\"\"Compress a list of date pairs to ignore short stretches of unused days. Args: intervals: A list of pairs of datetime.date instances. num_days: An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" ignore_interval = datetime.timedelta(days=num_days) new_intervals = [] iter_intervals = iter(intervals) last_begin, last_end = next(iter_intervals) for date_begin, date_end in iter_intervals: if date_begin - last_end < ignore_interval: # Compress. last_end = date_end continue new_intervals.append((last_begin, last_end)) last_begin, last_end = date_begin, date_end new_intervals.append((last_begin, last_end)) return new_intervals beancount.ops.lifetimes.compress_lifetimes_days(lifetimes_map, num_days) \uf0c1 Compress a lifetimes map to ignore short stretches of unused days. Parameters: lifetimes_map \u2013 A dict of currency intervals as returned by get_commodity_lifetimes. num_days \u2013 An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_lifetimes_days(lifetimes_map, num_days): \"\"\"Compress a lifetimes map to ignore short stretches of unused days. Args: lifetimes_map: A dict of currency intervals as returned by get_commodity_lifetimes. num_days: An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" return {currency_pair: compress_intervals_days(intervals, num_days) for currency_pair, intervals in lifetimes_map.items()} beancount.ops.lifetimes.get_commodity_lifetimes(entries) \uf0c1 Given a list of directives, figure out the life of each commodity. Parameters: entries \u2013 A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day after the last date seen. Source code in beancount/ops/lifetimes.py def get_commodity_lifetimes(entries): \"\"\"Given a list of directives, figure out the life of each commodity. Args: entries: A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day _after_ the last date seen. \"\"\" lifetimes = collections.defaultdict(list) # The current set of active commodities. commodities = set() # The current balances across all accounts. balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Process only transaction entries. if not isinstance(entry, data.Transaction): continue # Update the balance of affected accounts and check locally whether that # triggered a change in the set of commodities. commodities_changed = False for posting in entry.postings: balance = balances[posting.account] commodities_before = balance.currency_pairs() balance.add_position(posting) commodities_after = balance.currency_pairs() if commodities_after != commodities_before: commodities_changed = True # If there was a change in one of the affected account's list of # commodities, recompute the total set globally. This should not # occur very frequently. if commodities_changed: new_commodities = set( itertools.chain(*(inv.currency_pairs() for inv in balances.values()))) if new_commodities != commodities: # The new global set of commodities has changed; update our # the dictionary of intervals. for currency in new_commodities - commodities: lifetimes[currency].append((entry.date, None)) for currency in commodities - new_commodities: lifetime = lifetimes[currency] begin_date, end_date = lifetime.pop(-1) assert end_date is None lifetime.append((begin_date, entry.date + ONEDAY)) # Update our current set. commodities = new_commodities return lifetimes beancount.ops.lifetimes.required_weekly_prices(lifetimes_map, date_last) \uf0c1 Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Parameters: lifetimes_map \u2013 A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last \u2013 A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). Source code in beancount/ops/lifetimes.py def required_weekly_prices(lifetimes_map, date_last): \"\"\"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Args: lifetimes_map: A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last: A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). \"\"\" results = [] for currency_pair, intervals in lifetimes_map.items(): if currency_pair[1] is None: continue for date_begin, date_end in intervals: # Find first Friday before the minimum date. diff_days = 4 - date_begin.weekday() if diff_days > 1: diff_days -= 7 date = date_begin + datetime.timedelta(days=diff_days) # Iterate over all Fridays. if date_end is None: date_end = date_last while date < date_end: results.append((date, currency_pair[0], currency_pair[1])) date += ONE_WEEK return sorted(results) beancount.ops.pad \uf0c1 Automatic padding of gaps between entries. beancount.ops.pad.PadError ( tuple ) \uf0c1 PadError(source, message, entry) beancount.ops.pad.PadError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/pad.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.pad.PadError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of PadError(source, message, entry) beancount.ops.pad.PadError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/pad.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.pad.pad(entries, options_map) \uf0c1 Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Parameters: entries \u2013 A list of directives. options_map \u2013 A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. Source code in beancount/ops/pad.py def pad(entries, options_map): \"\"\"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Args: entries: A list of directives. options_map: A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. \"\"\" pad_errors = [] # Find all the pad entries and group them by account. pads = list(misc_utils.filter_type(entries, data.Pad)) pad_dict = misc_utils.groupby(lambda x: x.account, pads) # Partially realize the postings, so we can iterate them by account. by_account = realization.postings_by_account(entries) # A dict of pad -> list of entries to be inserted. new_entries = {id(pad): [] for pad in pads} # Process each account that has a padding group. for account_, pad_list in sorted(pad_dict.items()): # Last encountered / currency active pad entry. active_pad = None # Gather all the postings for the account and its children. postings = [] is_child = account.parent_matcher(account_) for item_account, item_postings in by_account.items(): if is_child(item_account): postings.extend(item_postings) postings.sort(key=data.posting_sortkey) # A set of currencies already padded so far in this account. padded_lots = set() pad_balance = inventory.Inventory() for entry in postings: assert not isinstance(entry, data.Posting) if isinstance(entry, data.TxnPosting): # This is a transaction; update the running balance for this # account. pad_balance.add_position(entry.posting) elif isinstance(entry, data.Pad): if entry.account == account_: # Mark this newly encountered pad as active and allow all lots # to be padded heretofore. active_pad = entry padded_lots = set() elif isinstance(entry, data.Balance): check_amount = entry.amount # Compare the current balance amount to the expected one from # the check entry. IMPORTANT: You need to understand that this # does not check a single position, but rather checks that the # total amount for a particular currency (which itself is # distinct from the cost). balance_amount = pad_balance.get_currency_units(check_amount.currency) diff_amount = amount.sub(balance_amount, check_amount) # Use the specified tolerance or automatically infer it. tolerance = balance.get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: # The check fails; we need to pad. # Pad only if pad entry is active and we haven't already # padded that lot since it was last encountered. if active_pad and (check_amount.currency not in padded_lots): # Note: we decide that it's an error to try to pad # positions at cost; we check here that all the existing # positions with that currency have no cost. positions = [pos for pos in pad_balance.get_positions() if pos.units.currency == check_amount.currency] for position_ in positions: if position_.cost is not None: pad_errors.append( PadError(entry.meta, (\"Attempt to pad an entry with cost for \" \"balance: {}\".format(pad_balance)), active_pad)) # Thus our padding lot is without cost by default. diff_position = position.Position.from_amounts( amount.Amount(check_amount.number - balance_amount.number, check_amount.currency)) # Synthesize a new transaction entry for the difference. narration = ('(Padding inserted for Balance of {} for ' 'difference {})').format(check_amount, diff_position) new_entry = data.Transaction( active_pad.meta.copy(), active_pad.date, flags.FLAG_PADDING, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) new_entry.postings.append( data.Posting(active_pad.account, diff_position.units, diff_position.cost, None, None, None)) neg_diff_position = -diff_position new_entry.postings.append( data.Posting(active_pad.source_account, neg_diff_position.units, neg_diff_position.cost, None, None, None)) # Save it for later insertion after the active pad. new_entries[id(active_pad)].append(new_entry) # Fixup the running balance. pos, _ = pad_balance.add_position(diff_position) if pos is not None and pos.is_negative_at_cost(): raise ValueError( \"Position held at cost goes negative: {}\".format(pos)) # Mark this lot as padded. Further checks should not pad this lot. padded_lots.add(check_amount.currency) # Insert the newly created entries right after the pad entries that created them. padded_entries = [] for entry in entries: padded_entries.append(entry) if isinstance(entry, data.Pad): entry_list = new_entries[id(entry)] if entry_list: padded_entries.extend(entry_list) else: # Generate errors on unused pad entries. pad_errors.append( PadError(entry.meta, \"Unused Pad entry\", entry)) return padded_entries, pad_errors beancount.ops.summarize \uf0c1 Summarization of entries. This code is used to summarize a sequence of entries (e.g. during a time period) into a few \"opening balance\" entries. This is when computing a balance sheet for a specific time period: we don't want to see the entries from before some period of time, so we fold them into a single transaction per account that has the sum total amount of that account. beancount.ops.summarize.balance_by_account(entries, date=None) \uf0c1 Sum up the balance per account for all entries strictly before 'date'. Parameters: entries \u2013 A list of directives. date \u2013 An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. Source code in beancount/ops/summarize.py def balance_by_account(entries, date=None): \"\"\"Sum up the balance per account for all entries strictly before 'date'. Args: entries: A list of directives. date: An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. \"\"\" balances = collections.defaultdict(inventory.Inventory) for index, entry in enumerate(entries): if date and entry.date >= date: break if isinstance(entry, Transaction): for posting in entry.postings: account_balance = balances[posting.account] # Note: We must allow negative lots at cost, because this may be # used to reduce a filtered list of entries which may not # include the entries necessary to keep units at cost always # above zero. The only summation that is guaranteed to be above # zero is if all the entries are being summed together, no # entries are filtered, at least for a particular account's # postings. account_balance.add_position(posting) else: index = len(entries) return balances, index beancount.ops.summarize.cap(entries, account_types, conversion_currency, account_earnings, account_conversions) \uf0c1 Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Parameters: entries \u2013 A list of directives. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions \u2013 A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. Source code in beancount/ops/summarize.py def cap(entries, account_types, conversion_currency, account_earnings, account_conversions): \"\"\"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Args: entries: A list of directives. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions: A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. \"\"\" # Transfer the balances of income and expense accounts as earnings / net # income. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, None, income_statement_account_pred, account_earnings) # Insert final conversion entries. entries = conversions(entries, account_conversions, conversion_currency, None) return entries beancount.ops.summarize.cap_opt(entries, options_map) \uf0c1 Close by getting all the parameters from an options map. See cap() for details. Parameters: entries \u2013 See cap(). options_map \u2013 A parser's option_map. Returns: Same as close(). Source code in beancount/ops/summarize.py def cap_opt(entries, options_map): \"\"\"Close by getting all the parameters from an options map. See cap() for details. Args: entries: See cap(). options_map: A parser's option_map. Returns: Same as close(). \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) conversion_currency = options_map['conversion_currency'] return cap(entries, account_types, conversion_currency, *current_accounts) beancount.ops.summarize.clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions) \uf0c1 Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Parameters: entries \u2013 A list of directive tuples. begin_date \u2013 A datetime.date instance, the beginning of the period. end_date \u2013 A datetime.date instance, one day beyond the end of the period. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Args: entries: A list of directive tuples. begin_date: A datetime.date instance, the beginning of the period. end_date: A datetime.date instance, one day beyond the end of the period. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, begin_date, income_statement_account_pred, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, begin_date, account_opening) # Truncate the entries after this. entries = truncate(entries, end_date) # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, end_date) return entries, index beancount.ops.summarize.clamp_opt(entries, begin_date, end_date, options_map) \uf0c1 Clamp by getting all the parameters from an options map. See clamp() for details. Parameters: entries \u2013 See clamp(). begin_date \u2013 See clamp(). end_date \u2013 See clamp(). options_map \u2013 A parser's option_map. Returns: Same as clamp(). Source code in beancount/ops/summarize.py def clamp_opt(entries, begin_date, end_date, options_map): \"\"\"Clamp by getting all the parameters from an options map. See clamp() for details. Args: entries: See clamp(). begin_date: See clamp(). end_date: See clamp(). options_map: A parser's option_map. Returns: Same as clamp(). \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return clamp(entries, begin_date, end_date, account_types, conversion_currency, *previous_accounts) beancount.ops.summarize.clear(entries, date, account_types, account_earnings) \uf0c1 Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types \u2013 An instance of AccountTypes. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. Source code in beancount/ops/summarize.py def clear(entries, date, account_types, account_earnings): \"\"\"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types: An instance of AccountTypes. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. \"\"\" index = len(entries) # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) new_entries = transfer_balances(entries, date, income_statement_account_pred, account_earnings) return new_entries, index beancount.ops.summarize.clear_opt(entries, date, options_map) \uf0c1 Convenience function to clear() using an options map. Source code in beancount/ops/summarize.py def clear_opt(entries, date, options_map): \"\"\"Convenience function to clear() using an options map. \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) return clear(entries, date, account_types, current_accounts[0]) beancount.ops.summarize.close(entries, date, conversion_currency, account_conversions) \uf0c1 Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will Remove all entries which occur after 'date', if given. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. Source code in beancount/ops/summarize.py def close(entries, date, conversion_currency, account_conversions): \"\"\"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will 1. Remove all entries which occur after 'date', if given. 2. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. \"\"\" # Truncate the entries after the date, if a date has been provided. if date is not None: entries = truncate(entries, date) # Keep an index to the truncated list of entries (before conversions). index = len(entries) # Insert a conversions entry to ensure the total balance of all accounts is # flush zero. entries = conversions(entries, account_conversions, conversion_currency, date) return entries, index beancount.ops.summarize.close_opt(entries, date, options_map) \uf0c1 Convenience function to close() using an options map. Source code in beancount/ops/summarize.py def close_opt(entries, date, options_map): \"\"\"Convenience function to close() using an options map. \"\"\" conversion_currency = options_map['conversion_currency'] current_accounts = options.get_current_accounts(options_map) return close(entries, date, conversion_currency, current_accounts[1]) beancount.ops.summarize.conversions(entries, conversion_account, conversion_currency, date=None) \uf0c1 Insert a conversion entry at date 'date' at the given account. Parameters: entries \u2013 A list of entries. conversion_account \u2013 A string, the account to book against. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. date \u2013 The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. Source code in beancount/ops/summarize.py def conversions(entries, conversion_account, conversion_currency, date=None): \"\"\"Insert a conversion entry at date 'date' at the given account. Args: entries: A list of entries. conversion_account: A string, the account to book against. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. date: The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. \"\"\" # Compute the balance at the given date. conversion_balance = interpolate.compute_entries_balance(entries, date=date) # Early exit if there is nothing to do. conversion_cost_balance = conversion_balance.reduce(convert.get_cost) if conversion_cost_balance.is_empty(): return entries # Calculate the index and the date for the new entry. We want to store it as # the last transaction of the day before. if date is not None: index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) last_date = date - datetime.timedelta(days=1) else: index = len(entries) last_date = entries[-1].date meta = data.new_metadata('', -1) narration = 'Conversion for {}'.format(conversion_balance) conversion_entry = Transaction(meta, last_date, flags.FLAG_CONVERSIONS, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) for position in conversion_cost_balance.get_positions(): # Important note: Set the cost to zero here to maintain the balance # invariant. (This is the only single place we cheat on the balance rule # in the entire system and this is necessary; see documentation on # Conversions.) price = amount.Amount(ZERO, conversion_currency) neg_pos = -position conversion_entry.postings.append( data.Posting(conversion_account, neg_pos.units, neg_pos.cost, price, None, None)) # Make a copy of the list of entries and insert the new transaction into it. new_entries = list(entries) new_entries.insert(index, conversion_entry) return new_entries beancount.ops.summarize.create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template) \uf0c1 \"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Parameters: balances \u2013 A dict of account name strings to Inventory instances. date \u2013 A datetime.date object, the date at which to create the transaction. source_account \u2013 A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction \u2013 If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta \u2013 A dict to use as metadata for the transactions. flag \u2013 A string, the flag to use for the transactions. narration_template \u2013 A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. Source code in beancount/ops/summarize.py def create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template): \"\"\"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Args: balances: A dict of account name strings to Inventory instances. date: A datetime.date object, the date at which to create the transaction. source_account: A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction: If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta: A dict to use as metadata for the transactions. flag: A string, the flag to use for the transactions. narration_template: A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. \"\"\" new_entries = [] for account, account_balance in sorted(balances.items()): # Don't create new entries where there is no balance. if account_balance.is_empty(): continue narration = narration_template.format(account=account, date=date) if not direction: account_balance = -account_balance postings = [] new_entry = Transaction( meta, date, flag, None, narration, data.EMPTY_SET, data.EMPTY_SET, postings) for position in account_balance.get_positions(): postings.append(data.Posting(account, position.units, position.cost, None, None, None)) cost = -convert.get_cost(position) postings.append(data.Posting(source_account, cost, None, None, None, None)) new_entries.append(new_entry) return new_entries beancount.ops.summarize.get_open_entries(entries, date) \uf0c1 Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Parameters: entries \u2013 A list of directives. date \u2013 The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. Source code in beancount/ops/summarize.py def get_open_entries(entries, date): \"\"\"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Args: entries: A list of directives. date: The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. \"\"\" open_entries = {} for index, entry in enumerate(entries): if date is not None and entry.date >= date: break if isinstance(entry, Open): try: ex_index, ex_entry = open_entries[entry.account] if entry.date < ex_entry.date: open_entries[entry.account] = (index, entry) except KeyError: open_entries[entry.account] = (index, entry) elif isinstance(entry, Close): # If there is no corresponding open, don't raise an error. open_entries.pop(entry.account, None) return [entry for (index, entry) in sorted(open_entries.values())] beancount.ops.summarize.open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions) \uf0c1 Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will Insert conversion transactions at the given open date, then Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, the date at which to do this. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will 1. Insert conversion transactions at the given open date, then 2. Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally 3. It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Args: entries: A list of directive tuples. date: A datetime.date instance, the date at which to do this. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, date) # Transfer income and expenses before the period to equity. entries, _ = clear(entries, date, account_types, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, date, account_opening) return entries, index beancount.ops.summarize.open_opt(entries, date, options_map) \uf0c1 Convenience function to open() using an options map. Source code in beancount/ops/summarize.py def open_opt(entries, date, options_map): \"\"\"Convenience function to open() using an options map. \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return open(entries, date, account_types, conversion_currency, *previous_accounts) beancount.ops.summarize.summarize(entries, date, account_opening) \uf0c1 Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the cutoff date before which to summarize. account_opening \u2013 A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. Source code in beancount/ops/summarize.py def summarize(entries, date, account_opening): \"\"\"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Args: entries: A list of directives. date: A datetime.date instance, the cutoff date before which to summarize. account_opening: A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. \"\"\" # Compute balances at date. balances, index = balance_by_account(entries, date) # We need to insert the entries with a date previous to subsequent checks, # to maintain ensure the open directives show up before any transaction. summarize_date = date - datetime.timedelta(days=1) # Create summarization / opening balance entries. summarizing_entries = create_entries_from_balances( balances, summarize_date, account_opening, True, data.new_metadata('', 0), flags.FLAG_SUMMARIZE, \"Opening balance for '{account}' (Summarization)\") # Insert the last price entry for each commodity from before the date. price_entries = prices.get_last_price_entries(entries, date) # Gather the list of active open entries at date. open_entries = get_open_entries(entries, date) # Compute entries before the date and preserve the entries after the date. before_entries = sorted(open_entries + price_entries + summarizing_entries, key=data.entry_sortkey) after_entries = entries[index:] # Return a new list of entries and the index that points after the entries # were inserted. return (before_entries + after_entries), len(before_entries) beancount.ops.summarize.transfer_balances(entries, date, account_pred, transfer_account) \uf0c1 Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to make the transfer. account_pred \u2013 A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account \u2013 A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. Source code in beancount/ops/summarize.py def transfer_balances(entries, date, account_pred, transfer_account): \"\"\"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Args: entries: A list of directives. date: A datetime.date instance, the date at which to make the transfer. account_pred: A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account: A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. \"\"\" # Don't bother doing anything if there are no entries. if not entries: return entries # Compute balances at date. balances, index = balance_by_account(entries, date) # Filter out to keep only the accounts we want. transfer_balances = {account: balance for account, balance in balances.items() if account_pred(account)} # We need to insert the entries at the end of the previous day. if date: transfer_date = date - datetime.timedelta(days=1) else: transfer_date = entries[-1].date # Create transfer entries. transfer_entries = create_entries_from_balances( transfer_balances, transfer_date, transfer_account, False, data.new_metadata('', 0), flags.FLAG_TRANSFER, \"Transfer balance for '{account}' (Transfer balance)\") # Remove balance assertions that occur after a transfer on an account that # has been transferred away; they would break. after_entries = [entry for entry in entries[index:] if not (isinstance(entry, balance.Balance) and entry.account in transfer_balances)] # Split the new entries in a new list. return (entries[:index] + transfer_entries + after_entries) beancount.ops.summarize.truncate(entries, date) \uf0c1 Filter out all the entries at and after date. Returns a new list of entries. Parameters: entries \u2013 A sorted list of directives. date \u2013 A datetime.date instance. Returns: A truncated list of directives. Source code in beancount/ops/summarize.py def truncate(entries, date): \"\"\"Filter out all the entries at and after date. Returns a new list of entries. Args: entries: A sorted list of directives. date: A datetime.date instance. Returns: A truncated list of directives. \"\"\" index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) return entries[:index] beancount.ops.validation \uf0c1 Validation checks. These checks are intended to be run after all the plugins have transformed the list of entries, just before serving them or generating reports from them. The idea is to ensure a reasonable set of invariants and generate errors if those invariants are violated. They are not sanity checks--user data is subject to constraints which are hopefully detected here and which will result in errors trickled up to the user. beancount.ops.validation.ValidationError ( tuple ) \uf0c1 ValidationError(source, message, entry) beancount.ops.validation.ValidationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/validation.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.validation.ValidationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ValidationError(source, message, entry) beancount.ops.validation.ValidationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/validation.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.validation.validate(entries, options_map, log_timings=None, extra_validations=None) \uf0c1 Perform all the standard checks on parsed contents. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. log_timings \u2013 An optional function to use for logging the time of individual operations. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate(entries, options_map, log_timings=None, extra_validations=None): \"\"\"Perform all the standard checks on parsed contents. Args: entries: A list of directives. unused_options_map: An options map. log_timings: An optional function to use for logging the time of individual operations. extra_validations: A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. \"\"\" validation_tests = VALIDATIONS if extra_validations: validation_tests += extra_validations # Run various validation routines define above. errors = [] for validation_function in validation_tests: with misc_utils.log_time('function: {}'.format(validation_function.__name__), log_timings, indent=2): new_errors = validation_function(entries, options_map) errors.extend(new_errors) return errors beancount.ops.validation.validate_active_accounts(entries, unused_options_map) \uf0c1 Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_active_accounts(entries, unused_options_map): \"\"\"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" error_pairs = [] active_set = set() opened_accounts = set() for entry in entries: if isinstance(entry, data.Open): active_set.add(entry.account) opened_accounts.add(entry.account) elif isinstance(entry, data.Close): active_set.discard(entry.account) else: for account in getters.get_entry_accounts(entry): if account not in active_set: # Allow document and note directives that occur after an # account is closed. if (isinstance(entry, ALLOW_AFTER_CLOSE) and account in opened_accounts): continue # Register an error to be logged later, with an appropriate # message. error_pairs.append((account, entry)) # Refine the error message to disambiguate between the case of an account # that has never been seen and one that was simply not active at the time. errors = [] for account, entry in error_pairs: if account in opened_accounts: message = \"Invalid reference to inactive account '{}'\".format(account) else: message = \"Invalid reference to unknown account '{}'\".format(account) errors.append(ValidationError(entry.meta, message, entry)) return errors beancount.ops.validation.validate_check_transaction_balances(entries, options_map) \uf0c1 Check again that all transaction postings balance, as users may have transformed transactions. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_check_transaction_balances(entries, options_map): \"\"\"Check again that all transaction postings balance, as users may have transformed transactions. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Note: this is a bit slow; we could limit our checks to the original # transactions by using the hash function in the loader. errors = [] for entry in entries: if isinstance(entry, Transaction): # IMPORTANT: This validation is _crucial_ and cannot be skipped. # This is where we actually detect and warn on unbalancing # transactions. This _must_ come after the user routines, because # unbalancing input is legal, as those types of transactions may be # \"fixed up\" by a user-plugin. In other words, we want to allow # users to input unbalancing transactions as long as the final # transactions objects that appear on the stream (after processing # the plugins) are balanced. See {9e6c14b51a59}. # # Detect complete sets of postings that have residual balance; residual = interpolate.compute_residual(entry.postings) tolerances = interpolate.infer_tolerances(entry.postings, options_map) if not residual.is_small(tolerances): errors.append( ValidationError(entry.meta, \"Transaction does not balance: {}\".format(residual), entry)) return errors beancount.ops.validation.validate_currency_constraints(entries, options_map) \uf0c1 Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_currency_constraints(entries, options_map): \"\"\"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Get all the open entries with currency constraints. open_map = {entry.account: entry for entry in entries if isinstance(entry, Open) and entry.currencies} errors = [] for entry in entries: if not isinstance(entry, Transaction): continue for posting in entry.postings: # Look up the corresponding account's valid currencies; skip the # check if there are none specified. try: open_entry = open_map[posting.account] valid_currencies = open_entry.currencies if not valid_currencies: continue except KeyError: continue # Perform the check. if posting.units.currency not in valid_currencies: errors.append( ValidationError( entry.meta, \"Invalid currency {} for account '{}'\".format( posting.units.currency, posting.account), entry)) return errors beancount.ops.validation.validate_data_types(entries, options_map) \uf0c1 Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_data_types(entries, options_map): \"\"\"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] for entry in entries: try: data.sanity_check_types( entry, options_map[\"allow_deprecated_none_for_tags_and_links\"]) except AssertionError as exc: errors.append( ValidationError(entry.meta, \"Invalid data types: {}\".format(exc), entry)) return errors beancount.ops.validation.validate_documents_paths(entries, options_map) \uf0c1 Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_documents_paths(entries, options_map): \"\"\"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" return [ValidationError(entry.meta, \"Invalid relative path for entry\", entry) for entry in entries if (isinstance(entry, Document) and not path.isabs(entry.filename))] beancount.ops.validation.validate_duplicate_balances(entries, unused_options_map) \uf0c1 Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_balances(entries, unused_options_map): \"\"\"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. balance_entries = {} for entry in entries: if not isinstance(entry, data.Balance): continue key = (entry.account, entry.amount.currency, entry.date) try: previous_entry = balance_entries[key] if entry.amount != previous_entry.amount: errors.append( ValidationError( entry.meta, \"Duplicate balance assertion with different amounts\", entry)) except KeyError: balance_entries[key] = entry return errors beancount.ops.validation.validate_duplicate_commodities(entries, unused_options_map) \uf0c1 Check that commodity entries are unique for each commodity. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_commodities(entries, unused_options_map): \"\"\"Check that commodity entries are unique for each commodity. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. commodity_entries = {} for entry in entries: if not isinstance(entry, data.Commodity): continue key = entry.currency try: previous_entry = commodity_entries[key] if previous_entry: errors.append( ValidationError( entry.meta, \"Duplicate commodity directives for '{}'\".format(key), entry)) except KeyError: commodity_entries[key] = entry return errors beancount.ops.validation.validate_open_close(entries, unused_options_map) \uf0c1 Check constraints on open and close directives themselves. This method checks two kinds of constraints: An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. Close directives may only appears if an open directive has been seen previous (chronologically). The date of close directives must be strictly greater than their corresponding open directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_open_close(entries, unused_options_map): \"\"\"Check constraints on open and close directives themselves. This method checks two kinds of constraints: 1. An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. 2. Close directives may only appears if an open directive has been seen previous (chronologically). 3. The date of close directives must be strictly greater than their corresponding open directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] open_map = {} close_map = {} for entry in entries: if isinstance(entry, Open): if entry.account in open_map: errors.append( ValidationError( entry.meta, \"Duplicate open directive for {}\".format(entry.account), entry)) else: open_map[entry.account] = entry elif isinstance(entry, Close): if entry.account in close_map: errors.append( ValidationError( entry.meta, \"Duplicate close directive for {}\".format(entry.account), entry)) else: try: open_entry = open_map[entry.account] if entry.date <= open_entry.date: errors.append( ValidationError( entry.meta, \"Internal error: closing date for {} \" \"appears before opening date\".format(entry.account), entry)) except KeyError: errors.append( ValidationError( entry.meta, \"Unopened account {} is being closed\".format(entry.account), entry)) close_map[entry.account] = entry return errors","title":"beancount.ops"},{"location":"api_reference/beancount.ops.html#beancountops","text":"Operations on the entries defined in the core modules. This package contains various functions which operate on lists of entries.","title":"beancount.ops"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance","text":"Automatic padding of gaps between entries.","title":"balance"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError","text":"BalanceError(source, message, entry)","title":"BalanceError"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/balance.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__new__","text":"Create new instance of BalanceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/balance.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.check","text":"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Parameters: entries \u2013 A list of directives. options_map \u2013 A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. Source code in beancount/ops/balance.py def check(entries, options_map): \"\"\"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Args: entries: A list of directives. options_map: A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. \"\"\" new_entries = [] check_errors = [] # This is similar to realization, but performed in a different order, and # where we only accumulate inventories for accounts that have balance # assertions in them (this saves on time). Here we process the entries one # by one along with the balance checks. We use a temporary realization in # order to hold the incremental tree of balances, so that we can easily get # the amounts of an account's subaccounts for making checks on parent # accounts. real_root = realization.RealAccount('') # Figure out the set of accounts for which we need to compute a running # inventory balance. asserted_accounts = {entry.account for entry in entries if isinstance(entry, Balance)} # Add all children accounts of an asserted account to be calculated as well, # and pre-create these accounts, and only those (we're just being tight to # make sure). asserted_match_list = [account.parent_matcher(account_) for account_ in asserted_accounts] for account_ in getters.get_accounts(entries): if (account_ in asserted_accounts or any(match(account_) for match in asserted_match_list)): realization.get_or_create(real_root, account_) # Get the Open directives for each account. open_close_map = getters.get_account_open_close(entries) for entry in entries: if isinstance(entry, Transaction): # For each of the postings' accounts, update the balance inventory. for posting in entry.postings: real_account = realization.get(real_root, posting.account) # The account will have been created only if we're meant to track it. if real_account is not None: # Note: Always allow negative lots for the purpose of balancing. # This error should show up somewhere else than here. real_account.balance.add_position(posting) elif isinstance(entry, Balance): # Check that the currency of the balance check is one of the allowed # currencies for that account. expected_amount = entry.amount try: open, _ = open_close_map[entry.account] except KeyError: check_errors.append( BalanceError(entry.meta, \"Account '{}' does not exist: \".format(entry.account), entry)) continue if (expected_amount is not None and open and open.currencies and expected_amount.currency not in open.currencies): check_errors.append( BalanceError(entry.meta, \"Invalid currency '{}' for Balance directive: \".format( expected_amount.currency), entry)) # Sum up the current balances for this account and its # sub-accounts. We want to support checks for parent accounts # for the total sum of their subaccounts. # # FIXME: Improve the performance further by computing the balance # for the desired currency only. This won't allow us to cache in # this way but may be faster, if we're not asserting all the # currencies. Furthermore, we could probably avoid recomputing the # balance if a subtree of positions hasn't been invalidated by a new # position added to the realization. Do this. real_account = realization.get(real_root, entry.account) assert real_account is not None, \"Missing {}\".format(entry.account) subtree_balance = realization.compute_balance(real_account, leaf_only=False) # Get only the amount in the desired currency. balance_amount = subtree_balance.get_currency_units(expected_amount.currency) # Check if the amount is within bounds of the expected amount. diff_amount = amount.sub(balance_amount, expected_amount) # Use the specified tolerance or automatically infer it. tolerance = get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: check_errors.append( BalanceError(entry.meta, (\"Balance failed for '{}': \" \"expected {} != accumulated {} ({} {})\").format( entry.account, expected_amount, balance_amount, abs(diff_amount.number), ('too much' if diff_amount.number > 0 else 'too little')), entry)) # Substitute the entry by a failing entry, with the diff_amount # field set on it. I'm not entirely sure that this is the best # of ideas, maybe leaving the original check intact and insert a # new error entry might be more functional or easier to # understand. entry = entry._replace( meta=entry.meta.copy(), diff_amount=diff_amount) new_entries.append(entry) return new_entries, check_errors","title":"check()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.get_balance_tolerance","text":"Get the tolerance amount for a single entry. Parameters: balance_entry \u2013 An instance of data.Balance options_map \u2013 An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. Source code in beancount/ops/balance.py def get_balance_tolerance(balance_entry, options_map): \"\"\"Get the tolerance amount for a single entry. Args: balance_entry: An instance of data.Balance options_map: An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. \"\"\" if balance_entry.tolerance is not None: # Use the balance-specific tolerance override if it is provided. tolerance = balance_entry.tolerance else: expo = balance_entry.amount.number.as_tuple().exponent if expo < 0: # Be generous and always allow twice the multiplier on Balance and # Pad because the user creates these and the rounding of those # balances may often be further off than those used within a single # transaction. tolerance = options_map[\"inferred_tolerance_multiplier\"] * 2 tolerance = ONE.scaleb(expo) * tolerance else: tolerance = ZERO return tolerance","title":"get_balance_tolerance()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops","text":"Basic filtering and aggregation operations on lists of entries. This module contains some common basic operations on entries that are complex enough not to belong in core/data.py.","title":"basicops"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.filter_link","text":"Yield all the entries which have the given link. Parameters: link \u2013 A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. Source code in beancount/ops/basicops.py def filter_link(link, entries): \"\"\"Yield all the entries which have the given link. Args: link: A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.links and link in entry.links): yield entry","title":"filter_link()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.filter_tag","text":"Yield all the entries which have the given tag. Parameters: tag \u2013 A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. Source code in beancount/ops/basicops.py def filter_tag(tag, entries): \"\"\"Yield all the entries which have the given tag. Args: tag: A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.tags and tag in entry.tags): yield entry","title":"filter_tag()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.get_common_accounts","text":"Compute the intersection of the accounts on the given entries. Parameters: entries \u2013 A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. Source code in beancount/ops/basicops.py def get_common_accounts(entries): \"\"\"Compute the intersection of the accounts on the given entries. Args: entries: A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. \"\"\" assert all(isinstance(entry, data.Transaction) for entry in entries) # If there is a single entry, the common accounts to it is all its accounts. # Note that this also works with no entries (yields an empty set). if len(entries) < 2: if entries: intersection = {posting.account for posting in entries[0].postings} else: intersection = set() else: entries_iter = iter(entries) intersection = set(posting.account for posting in next(entries_iter).postings) for entry in entries_iter: accounts = set(posting.account for posting in entry.postings) intersection &= accounts if not intersection: break return intersection","title":"get_common_accounts()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.group_entries_by_link","text":"Group the list of entries by link. Parameters: entries \u2013 A list of directives/transactions to process. Returns: A dict of link-name to list of entries. Source code in beancount/ops/basicops.py def group_entries_by_link(entries): \"\"\"Group the list of entries by link. Args: entries: A list of directives/transactions to process. Returns: A dict of link-name to list of entries. \"\"\" link_groups = defaultdict(list) for entry in entries: if not (isinstance(entry, data.Transaction) and entry.links): continue for link in entry.links: link_groups[link].append(entry) return link_groups","title":"group_entries_by_link()"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress","text":"Compress multiple entries into a single one. This can be used during import to compress the effective output, for accounts with a large number of similar entries. For example, I had a trading account which would pay out interest every single day. I have no desire to import the full detail of these daily interests, and compressing these interest-only entries to monthly ones made sense. This is the code that was used to carry this out.","title":"compress"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress.compress","text":"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Parameters: entries \u2013 A list of directives. predicate \u2013 A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. Source code in beancount/ops/compress.py def compress(entries, predicate): \"\"\"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Args: entries: A list of directives. predicate: A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. \"\"\" new_entries = [] pending = [] for entry in entries: if isinstance(entry, data.Transaction) and predicate(entry): # Save for compressing later. pending.append(entry) else: # Compress and output all the pending entries. if pending: new_entries.append(merge(pending, pending[-1])) pending.clear() # Output the differing entry. new_entries.append(entry) if pending: new_entries.append(merge(pending, pending[-1])) return new_entries","title":"compress()"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress.merge","text":"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Parameters: entries \u2013 A list of directives. prototype_txn \u2013 A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. Source code in beancount/ops/compress.py def merge(entries, prototype_txn): \"\"\"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Args: entries: A list of directives. prototype_txn: A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. \"\"\" # Aggregate the postings together. This is a mapping of numberless postings # to their number of units. postings_map = collections.defaultdict(Decimal) for entry in data.filter_txns(entries): for posting in entry.postings: # We strip the number off the posting to act as an aggregation key. key = data.Posting(posting.account, Amount(None, posting.units.currency), posting.cost, posting.price, posting.flag, None) postings_map[key] += posting.units.number # Create a new transaction with the aggregated postings. new_entry = data.Transaction(prototype_txn.meta, prototype_txn.date, prototype_txn.flag, prototype_txn.payee, prototype_txn.narration, data.EMPTY_SET, data.EMPTY_SET, []) # Sort for at least some stability of output. sorted_items = sorted(postings_map.items(), key=lambda item: (item[0].account, item[0].units.currency, item[1])) # Issue the merged postings. for posting, number in sorted_items: units = Amount(number, posting.units.currency) new_entry.postings.append( data.Posting(posting.account, units, posting.cost, posting.price, posting.flag, posting.meta)) return new_entry","title":"merge()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents","text":"Everything that relates to creating the Document directives.","title":"documents"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError","text":"DocumentError(source, message, entry)","title":"DocumentError"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/documents.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__new__","text":"Create new instance of DocumentError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/documents.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.find_documents","text":"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Parameters: directory \u2013 A string, the name of the root of the directory hierarchy to be searched. input_filename \u2013 The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only \u2013 A set of valid accounts strings to search for. strict \u2013 A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. Source code in beancount/ops/documents.py def find_documents(directory, input_filename, accounts_only=None, strict=False): \"\"\"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Args: directory: A string, the name of the root of the directory hierarchy to be searched. input_filename: The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only: A set of valid accounts strings to search for. strict: A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. \"\"\" errors = [] # Compute the documents directory name relative to the beancount input # file itself. if not path.isabs(directory): input_directory = path.dirname(input_filename) directory = path.abspath(path.normpath(path.join(input_directory, directory))) # If the directory does not exist, just generate an error and return. if not path.exists(directory): meta = data.new_metadata(input_filename, 0) error = DocumentError( meta, \"Document root '{}' does not exist\".format(directory), None) return ([], [error]) # Walk the hierarchy of files. entries = [] for root, account_name, dirs, files in account.walk(directory): # Look for files that have a dated filename. for filename in files: match = re.match(r'(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d).(.*)', filename) if not match: continue # If a restricting set of accounts was specified, skip document # directives found in accounts with no corresponding account name. if accounts_only is not None and not account_name in accounts_only: if strict: if any(account_name.startswith(account) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in child account {}\".format( filename, account_name), None)) elif any(account.startswith(account_name) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in parent account {}\".format( filename, account_name), None)) continue # Create a new directive. meta = data.new_metadata(input_filename, 0) try: date = datetime.date(*map(int, match.group(1, 2, 3))) except ValueError as exc: errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Invalid date on document file '{}': {}\".format( filename, exc), None)) else: entry = data.Document(meta, date, account_name, path.join(root, filename), data.EMPTY_SET, data.EMPTY_SET) entries.append(entry) return (entries, errors)","title":"find_documents()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.process_documents","text":"Check files for document directives and create documents directives automatically. Parameters: entries \u2013 A list of all directives parsed from the file. options_map \u2013 An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. Source code in beancount/ops/documents.py def process_documents(entries, options_map): \"\"\"Check files for document directives and create documents directives automatically. Args: entries: A list of all directives parsed from the file. options_map: An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. \"\"\" filename = options_map[\"filename\"] # Detect filenames that should convert into entries. autodoc_entries = [] autodoc_errors = [] document_dirs = options_map['documents'] if document_dirs: # Restrict to the list of valid accounts only. accounts = getters.get_accounts(entries) # Accumulate all the entries. for directory in map(path.normpath, document_dirs): new_entries, new_errors = find_documents(directory, filename, accounts) autodoc_entries.extend(new_entries) autodoc_errors.extend(new_errors) # Merge the two lists of entries and errors. Keep the entries sorted. entries.extend(autodoc_entries) entries.sort(key=data.entry_sortkey) return (entries, autodoc_errors)","title":"process_documents()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.verify_document_files_exist","text":"Verify that the document entries point to existing files. Parameters: entries \u2013 a list of directives whose documents need to be validated. unused_options_map \u2013 A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. Source code in beancount/ops/documents.py def verify_document_files_exist(entries, unused_options_map): \"\"\"Verify that the document entries point to existing files. Args: entries: a list of directives whose documents need to be validated. unused_options_map: A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. \"\"\" errors = [] for entry in entries: if not isinstance(entry, data.Document): continue if not path.exists(entry.filename): errors.append( DocumentError(entry.meta, 'File does not exist: \"{}\"'.format(entry.filename), entry)) return entries, errors","title":"verify_document_files_exist()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings","text":"Compute final holdings for a list of entries.","title":"holdings"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding","text":"Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date)","title":"Holding"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/holdings.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__new__","text":"Create new instance of Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/holdings.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.aggregate_holdings_by","text":"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Parameters: keyfun \u2013 A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. Source code in beancount/ops/holdings.py def aggregate_holdings_by(holdings, keyfun): \"\"\"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Args: keyfun: A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. \"\"\" # Aggregate the groups of holdings. grouped = collections.defaultdict(list) for holding in holdings: key = (keyfun(holding), holding.cost_currency) grouped[key].append(holding) grouped_holdings = (aggregate_holdings_list(key_holdings) for key_holdings in grouped.values()) # We could potentially filter out holdings with zero units here. These types # of holdings might occur on a group with leaked (i.e., non-zero) cost basis # and zero units. However, sometimes are valid merging of multiple # currencies may occur, and the number value will be legitimately set to # ZERO (for various reasons downstream), so we prefer not to ignore the # holding. Callers must be prepared to deal with a holding of ZERO units and # a non-zero cost basis. {0ed05c502e63, b/16} ## nonzero_holdings = (holding ## for holding in grouped_holdings ## if holding.number != ZERO) # Return the holdings in order. return sorted(grouped_holdings, key=lambda holding: (holding.account, holding.currency))","title":"aggregate_holdings_by()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.aggregate_holdings_list","text":"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Parameters: holdings \u2013 A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Exceptions: ValueError \u2013 If multiple cost currencies encountered. Source code in beancount/ops/holdings.py def aggregate_holdings_list(holdings): \"\"\"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Args: holdings: A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Raises: ValueError: If multiple cost currencies encountered. \"\"\" if not holdings: return None # Note: Holding is a bit overspecified with book and market values. We # recompute them from cost and price numbers here anyhow. units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() price_dates = set() book_value_seen = False market_value_seen = False for holding in holdings: units += holding.number accounts.add(holding.account) price_dates.add(holding.price_date) currencies.add(holding.currency) cost_currencies.add(holding.cost_currency) if holding.book_value is not None: total_book_value += holding.book_value book_value_seen = True elif holding.cost_number is not None: total_book_value += holding.number * holding.cost_number book_value_seen = True if holding.market_value is not None: total_market_value += holding.market_value market_value_seen = True elif holding.price_number is not None: total_market_value += holding.number * holding.price_number market_value_seen = True if book_value_seen: average_cost = total_book_value / units if units else None else: total_book_value = None average_cost = None if market_value_seen: average_price = total_market_value / units if units else None else: total_market_value = None average_price = None if len(cost_currencies) != 1: raise ValueError(\"Cost currencies are not homogeneous for aggregation: {}\".format( ','.join(map(str, cost_currencies)))) units = units if len(currencies) == 1 else ZERO currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) price_date = price_dates.pop() if len(price_dates) == 1 else None return Holding(account_, units, currency, average_cost, cost_currency, total_book_value, total_market_value, average_price, price_date)","title":"aggregate_holdings_list()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.convert_to_currency","text":"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Parameters: price_map \u2013 A price-map, as built by prices.build_price_map(). target_currency \u2013 The target common currency to convert amounts to. holdings_list \u2013 A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. Source code in beancount/ops/holdings.py def convert_to_currency(price_map, target_currency, holdings_list): \"\"\"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Args: price_map: A price-map, as built by prices.build_price_map(). target_currency: The target common currency to convert amounts to. holdings_list: A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. \"\"\" # A list of the fields we should convert. convert_fields = ('cost_number', 'book_value', 'market_value', 'price_number') new_holdings = [] for holding in holdings_list: if holding.cost_currency == target_currency: # The holding is already priced in the target currency; do nothing. new_holding = holding else: if holding.cost_currency is None: # There is no cost currency; make the holding priced in its own # units. The price-map should yield a rate of 1.0 and everything # else works out. if holding.currency is None: raise ValueError(\"Invalid currency '{}'\".format(holding.currency)) holding = holding._replace(cost_currency=holding.currency) # Fill in with book and market value as well. if holding.book_value is None: holding = holding._replace(book_value=holding.number) if holding.market_value is None: holding = holding._replace(market_value=holding.number) assert holding.cost_currency, \"Missing cost currency: {}\".format(holding) base_quote = (holding.cost_currency, target_currency) # Get the conversion rate and replace the required numerical # fields.. _, rate = prices.get_latest_price(price_map, base_quote) if rate is not None: new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number, r=rate: number if number is None else number * r, holding) # Ensure we set the new cost currency after conversion. new_holding = new_holding._replace(cost_currency=target_currency) else: # Could not get the rate... clear every field and set the cost # currency to None. This enough marks the holding conversion as # a failure. new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number: None, holding) new_holding = new_holding._replace(cost_currency=None) new_holdings.append(new_holding) return new_holdings","title":"convert_to_currency()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.get_commodities_at_date","text":"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency \u2013 The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). Source code in beancount/ops/holdings.py def get_commodities_at_date(entries, options_map, date=None): \"\"\"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: * The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. * The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Args: entries: A list of directives. date: A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency: The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). \"\"\" # Remove all the entries after the given date, if requested. if date is not None: entries = summarize.truncate(entries, date) # Get the list of holdings at the particular date. holdings_list = get_final_holdings(entries) # Obtain the unique list of currencies we need to fetch. commodities_list = {(holding.currency, holding.cost_currency) for holding in holdings_list} # Add in the associated ticker symbols. commodities_map = getters.get_commodity_map(entries) commodities_symbols_list = [] for currency, cost_currency in sorted(commodities_list): try: commodity_entry = commodities_map[currency] ticker = commodity_entry.meta.get('ticker', None) quote_currency = commodity_entry.meta.get('quote', None) except KeyError: ticker = None quote_currency = None commodities_symbols_list.append( (currency, cost_currency, quote_currency, ticker)) return commodities_symbols_list","title":"get_commodities_at_date()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.get_final_holdings","text":"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Parameters: entries \u2013 A list of directives. included_account_types \u2013 A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields \u2013 Source code in beancount/ops/holdings.py def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): \"\"\"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Args: entries: A list of directives. included_account_types: A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields: \"\"\" # Remove the entries inserted by unrealized gains/losses. Those entries do # affect asset accounts, and we don't want them to appear in holdings. # # Note: Perhaps it would make sense to generalize this concept of \"inserted # unrealized gains.\" simple_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or entry.flag != flags.FLAG_UNREALIZED)] # Realize the accounts into a tree (because we want the positions by-account). root_account = realization.realize(simple_entries) # For each account, look at the list of positions and build a list. holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): if included_account_types: # Skip accounts of invalid types, we only want to reflect the requested # account types, typically assets and liabilities. account_type = account_types.get_account_type(real_account.account) if account_type not in included_account_types: continue for pos in real_account.balance.get_positions(): if pos.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (pos.units.currency, pos.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = pos.units.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.units.number, pos.units.currency, pos.cost.number, pos.cost.currency, pos.units.number * pos.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.units.number, pos.units.currency, None, pos.units.currency, pos.units.number, pos.units.number, None, None) holdings.append(holding) return holdings","title":"get_final_holdings()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.holding_to_position","text":"Convert the holding to a position. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_position(holding): \"\"\"Convert the holding to a position. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" return position.Position( amount.Amount(holding.number, holding.currency), (position.Cost(holding.cost_number, holding.cost_currency, None, None) if holding.cost_number else None))","title":"holding_to_position()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.holding_to_posting","text":"Convert the holding to an instance of Posting. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_posting(holding): \"\"\"Convert the holding to an instance of Posting. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" position_ = holding_to_position(holding) price = (amount.Amount(holding.price_number, holding.cost_currency) if holding.price_number else None) return data.Posting(holding.account, position_.units, position_.cost, price, None, None)","title":"holding_to_posting()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.reduce_relative","text":"Convert the market and book values of the given list of holdings to relative data. Parameters: holdings \u2013 A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. Source code in beancount/ops/holdings.py def reduce_relative(holdings): \"\"\"Convert the market and book values of the given list of holdings to relative data. Args: holdings: A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. \"\"\" # Group holdings by value currency. by_currency = collections.defaultdict(list) ordering = {} for index, holding in enumerate(holdings): ordering.setdefault(holding.cost_currency, index) by_currency[holding.cost_currency].append(holding) fractional_holdings = [] for currency in sorted(by_currency, key=ordering.get): currency_holdings = by_currency[currency] # Compute total market value for that currency. total_book_value = ZERO total_market_value = ZERO for holding in currency_holdings: if holding.book_value: total_book_value += holding.book_value if holding.market_value: total_market_value += holding.market_value # Sort the currency's holdings with decreasing values of market value. currency_holdings.sort( key=lambda holding: holding.market_value or ZERO, reverse=True) # Output new holdings with the relevant values replaced. for holding in currency_holdings: fractional_holdings.append( holding._replace(book_value=(holding.book_value / total_book_value if holding.book_value is not None else None), market_value=(holding.market_value / total_market_value if holding.market_value is not None else None))) return fractional_holdings","title":"reduce_relative()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.scale_holding","text":"Scale the values of a holding. Parameters: holding \u2013 An instance of Holding. scale_factor \u2013 A float or Decimal number. Returns: A scaled copy of the holding. Source code in beancount/ops/holdings.py def scale_holding(holding, scale_factor): \"\"\"Scale the values of a holding. Args: holding: An instance of Holding. scale_factor: A float or Decimal number. Returns: A scaled copy of the holding. \"\"\" return Holding( holding.account, holding.number * scale_factor if holding.number else None, holding.currency, holding.cost_number, holding.cost_currency, holding.book_value * scale_factor if holding.book_value else None, holding.market_value * scale_factor if holding.market_value else None, holding.price_number, holding.price_date)","title":"scale_holding()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes","text":"Given a Beancount ledger, compute time intervals where we hold each commodity. This script computes, for each commodity, which time intervals it is required at. This can then be used to identify a list of dates at which we need to fetch prices in order to properly fill the price database.","title":"lifetimes"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.compress_intervals_days","text":"Compress a list of date pairs to ignore short stretches of unused days. Parameters: intervals \u2013 A list of pairs of datetime.date instances. num_days \u2013 An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_intervals_days(intervals, num_days): \"\"\"Compress a list of date pairs to ignore short stretches of unused days. Args: intervals: A list of pairs of datetime.date instances. num_days: An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" ignore_interval = datetime.timedelta(days=num_days) new_intervals = [] iter_intervals = iter(intervals) last_begin, last_end = next(iter_intervals) for date_begin, date_end in iter_intervals: if date_begin - last_end < ignore_interval: # Compress. last_end = date_end continue new_intervals.append((last_begin, last_end)) last_begin, last_end = date_begin, date_end new_intervals.append((last_begin, last_end)) return new_intervals","title":"compress_intervals_days()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.compress_lifetimes_days","text":"Compress a lifetimes map to ignore short stretches of unused days. Parameters: lifetimes_map \u2013 A dict of currency intervals as returned by get_commodity_lifetimes. num_days \u2013 An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_lifetimes_days(lifetimes_map, num_days): \"\"\"Compress a lifetimes map to ignore short stretches of unused days. Args: lifetimes_map: A dict of currency intervals as returned by get_commodity_lifetimes. num_days: An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" return {currency_pair: compress_intervals_days(intervals, num_days) for currency_pair, intervals in lifetimes_map.items()}","title":"compress_lifetimes_days()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.get_commodity_lifetimes","text":"Given a list of directives, figure out the life of each commodity. Parameters: entries \u2013 A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day after the last date seen. Source code in beancount/ops/lifetimes.py def get_commodity_lifetimes(entries): \"\"\"Given a list of directives, figure out the life of each commodity. Args: entries: A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day _after_ the last date seen. \"\"\" lifetimes = collections.defaultdict(list) # The current set of active commodities. commodities = set() # The current balances across all accounts. balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Process only transaction entries. if not isinstance(entry, data.Transaction): continue # Update the balance of affected accounts and check locally whether that # triggered a change in the set of commodities. commodities_changed = False for posting in entry.postings: balance = balances[posting.account] commodities_before = balance.currency_pairs() balance.add_position(posting) commodities_after = balance.currency_pairs() if commodities_after != commodities_before: commodities_changed = True # If there was a change in one of the affected account's list of # commodities, recompute the total set globally. This should not # occur very frequently. if commodities_changed: new_commodities = set( itertools.chain(*(inv.currency_pairs() for inv in balances.values()))) if new_commodities != commodities: # The new global set of commodities has changed; update our # the dictionary of intervals. for currency in new_commodities - commodities: lifetimes[currency].append((entry.date, None)) for currency in commodities - new_commodities: lifetime = lifetimes[currency] begin_date, end_date = lifetime.pop(-1) assert end_date is None lifetime.append((begin_date, entry.date + ONEDAY)) # Update our current set. commodities = new_commodities return lifetimes","title":"get_commodity_lifetimes()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.required_weekly_prices","text":"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Parameters: lifetimes_map \u2013 A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last \u2013 A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). Source code in beancount/ops/lifetimes.py def required_weekly_prices(lifetimes_map, date_last): \"\"\"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Args: lifetimes_map: A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last: A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). \"\"\" results = [] for currency_pair, intervals in lifetimes_map.items(): if currency_pair[1] is None: continue for date_begin, date_end in intervals: # Find first Friday before the minimum date. diff_days = 4 - date_begin.weekday() if diff_days > 1: diff_days -= 7 date = date_begin + datetime.timedelta(days=diff_days) # Iterate over all Fridays. if date_end is None: date_end = date_last while date < date_end: results.append((date, currency_pair[0], currency_pair[1])) date += ONE_WEEK return sorted(results)","title":"required_weekly_prices()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad","text":"Automatic padding of gaps between entries.","title":"pad"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError","text":"PadError(source, message, entry)","title":"PadError"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/pad.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__new__","text":"Create new instance of PadError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/pad.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.pad","text":"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Parameters: entries \u2013 A list of directives. options_map \u2013 A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. Source code in beancount/ops/pad.py def pad(entries, options_map): \"\"\"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Args: entries: A list of directives. options_map: A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. \"\"\" pad_errors = [] # Find all the pad entries and group them by account. pads = list(misc_utils.filter_type(entries, data.Pad)) pad_dict = misc_utils.groupby(lambda x: x.account, pads) # Partially realize the postings, so we can iterate them by account. by_account = realization.postings_by_account(entries) # A dict of pad -> list of entries to be inserted. new_entries = {id(pad): [] for pad in pads} # Process each account that has a padding group. for account_, pad_list in sorted(pad_dict.items()): # Last encountered / currency active pad entry. active_pad = None # Gather all the postings for the account and its children. postings = [] is_child = account.parent_matcher(account_) for item_account, item_postings in by_account.items(): if is_child(item_account): postings.extend(item_postings) postings.sort(key=data.posting_sortkey) # A set of currencies already padded so far in this account. padded_lots = set() pad_balance = inventory.Inventory() for entry in postings: assert not isinstance(entry, data.Posting) if isinstance(entry, data.TxnPosting): # This is a transaction; update the running balance for this # account. pad_balance.add_position(entry.posting) elif isinstance(entry, data.Pad): if entry.account == account_: # Mark this newly encountered pad as active and allow all lots # to be padded heretofore. active_pad = entry padded_lots = set() elif isinstance(entry, data.Balance): check_amount = entry.amount # Compare the current balance amount to the expected one from # the check entry. IMPORTANT: You need to understand that this # does not check a single position, but rather checks that the # total amount for a particular currency (which itself is # distinct from the cost). balance_amount = pad_balance.get_currency_units(check_amount.currency) diff_amount = amount.sub(balance_amount, check_amount) # Use the specified tolerance or automatically infer it. tolerance = balance.get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: # The check fails; we need to pad. # Pad only if pad entry is active and we haven't already # padded that lot since it was last encountered. if active_pad and (check_amount.currency not in padded_lots): # Note: we decide that it's an error to try to pad # positions at cost; we check here that all the existing # positions with that currency have no cost. positions = [pos for pos in pad_balance.get_positions() if pos.units.currency == check_amount.currency] for position_ in positions: if position_.cost is not None: pad_errors.append( PadError(entry.meta, (\"Attempt to pad an entry with cost for \" \"balance: {}\".format(pad_balance)), active_pad)) # Thus our padding lot is without cost by default. diff_position = position.Position.from_amounts( amount.Amount(check_amount.number - balance_amount.number, check_amount.currency)) # Synthesize a new transaction entry for the difference. narration = ('(Padding inserted for Balance of {} for ' 'difference {})').format(check_amount, diff_position) new_entry = data.Transaction( active_pad.meta.copy(), active_pad.date, flags.FLAG_PADDING, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) new_entry.postings.append( data.Posting(active_pad.account, diff_position.units, diff_position.cost, None, None, None)) neg_diff_position = -diff_position new_entry.postings.append( data.Posting(active_pad.source_account, neg_diff_position.units, neg_diff_position.cost, None, None, None)) # Save it for later insertion after the active pad. new_entries[id(active_pad)].append(new_entry) # Fixup the running balance. pos, _ = pad_balance.add_position(diff_position) if pos is not None and pos.is_negative_at_cost(): raise ValueError( \"Position held at cost goes negative: {}\".format(pos)) # Mark this lot as padded. Further checks should not pad this lot. padded_lots.add(check_amount.currency) # Insert the newly created entries right after the pad entries that created them. padded_entries = [] for entry in entries: padded_entries.append(entry) if isinstance(entry, data.Pad): entry_list = new_entries[id(entry)] if entry_list: padded_entries.extend(entry_list) else: # Generate errors on unused pad entries. pad_errors.append( PadError(entry.meta, \"Unused Pad entry\", entry)) return padded_entries, pad_errors","title":"pad()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize","text":"Summarization of entries. This code is used to summarize a sequence of entries (e.g. during a time period) into a few \"opening balance\" entries. This is when computing a balance sheet for a specific time period: we don't want to see the entries from before some period of time, so we fold them into a single transaction per account that has the sum total amount of that account.","title":"summarize"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.balance_by_account","text":"Sum up the balance per account for all entries strictly before 'date'. Parameters: entries \u2013 A list of directives. date \u2013 An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. Source code in beancount/ops/summarize.py def balance_by_account(entries, date=None): \"\"\"Sum up the balance per account for all entries strictly before 'date'. Args: entries: A list of directives. date: An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. \"\"\" balances = collections.defaultdict(inventory.Inventory) for index, entry in enumerate(entries): if date and entry.date >= date: break if isinstance(entry, Transaction): for posting in entry.postings: account_balance = balances[posting.account] # Note: We must allow negative lots at cost, because this may be # used to reduce a filtered list of entries which may not # include the entries necessary to keep units at cost always # above zero. The only summation that is guaranteed to be above # zero is if all the entries are being summed together, no # entries are filtered, at least for a particular account's # postings. account_balance.add_position(posting) else: index = len(entries) return balances, index","title":"balance_by_account()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.cap","text":"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Parameters: entries \u2013 A list of directives. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions \u2013 A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. Source code in beancount/ops/summarize.py def cap(entries, account_types, conversion_currency, account_earnings, account_conversions): \"\"\"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Args: entries: A list of directives. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions: A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. \"\"\" # Transfer the balances of income and expense accounts as earnings / net # income. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, None, income_statement_account_pred, account_earnings) # Insert final conversion entries. entries = conversions(entries, account_conversions, conversion_currency, None) return entries","title":"cap()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.cap_opt","text":"Close by getting all the parameters from an options map. See cap() for details. Parameters: entries \u2013 See cap(). options_map \u2013 A parser's option_map. Returns: Same as close(). Source code in beancount/ops/summarize.py def cap_opt(entries, options_map): \"\"\"Close by getting all the parameters from an options map. See cap() for details. Args: entries: See cap(). options_map: A parser's option_map. Returns: Same as close(). \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) conversion_currency = options_map['conversion_currency'] return cap(entries, account_types, conversion_currency, *current_accounts)","title":"cap_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clamp","text":"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Parameters: entries \u2013 A list of directive tuples. begin_date \u2013 A datetime.date instance, the beginning of the period. end_date \u2013 A datetime.date instance, one day beyond the end of the period. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Args: entries: A list of directive tuples. begin_date: A datetime.date instance, the beginning of the period. end_date: A datetime.date instance, one day beyond the end of the period. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, begin_date, income_statement_account_pred, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, begin_date, account_opening) # Truncate the entries after this. entries = truncate(entries, end_date) # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, end_date) return entries, index","title":"clamp()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clamp_opt","text":"Clamp by getting all the parameters from an options map. See clamp() for details. Parameters: entries \u2013 See clamp(). begin_date \u2013 See clamp(). end_date \u2013 See clamp(). options_map \u2013 A parser's option_map. Returns: Same as clamp(). Source code in beancount/ops/summarize.py def clamp_opt(entries, begin_date, end_date, options_map): \"\"\"Clamp by getting all the parameters from an options map. See clamp() for details. Args: entries: See clamp(). begin_date: See clamp(). end_date: See clamp(). options_map: A parser's option_map. Returns: Same as clamp(). \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return clamp(entries, begin_date, end_date, account_types, conversion_currency, *previous_accounts)","title":"clamp_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clear","text":"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types \u2013 An instance of AccountTypes. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. Source code in beancount/ops/summarize.py def clear(entries, date, account_types, account_earnings): \"\"\"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types: An instance of AccountTypes. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. \"\"\" index = len(entries) # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) new_entries = transfer_balances(entries, date, income_statement_account_pred, account_earnings) return new_entries, index","title":"clear()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clear_opt","text":"Convenience function to clear() using an options map. Source code in beancount/ops/summarize.py def clear_opt(entries, date, options_map): \"\"\"Convenience function to clear() using an options map. \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) return clear(entries, date, account_types, current_accounts[0])","title":"clear_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.close","text":"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will Remove all entries which occur after 'date', if given. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. Source code in beancount/ops/summarize.py def close(entries, date, conversion_currency, account_conversions): \"\"\"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will 1. Remove all entries which occur after 'date', if given. 2. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. \"\"\" # Truncate the entries after the date, if a date has been provided. if date is not None: entries = truncate(entries, date) # Keep an index to the truncated list of entries (before conversions). index = len(entries) # Insert a conversions entry to ensure the total balance of all accounts is # flush zero. entries = conversions(entries, account_conversions, conversion_currency, date) return entries, index","title":"close()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.close_opt","text":"Convenience function to close() using an options map. Source code in beancount/ops/summarize.py def close_opt(entries, date, options_map): \"\"\"Convenience function to close() using an options map. \"\"\" conversion_currency = options_map['conversion_currency'] current_accounts = options.get_current_accounts(options_map) return close(entries, date, conversion_currency, current_accounts[1])","title":"close_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.conversions","text":"Insert a conversion entry at date 'date' at the given account. Parameters: entries \u2013 A list of entries. conversion_account \u2013 A string, the account to book against. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. date \u2013 The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. Source code in beancount/ops/summarize.py def conversions(entries, conversion_account, conversion_currency, date=None): \"\"\"Insert a conversion entry at date 'date' at the given account. Args: entries: A list of entries. conversion_account: A string, the account to book against. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. date: The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. \"\"\" # Compute the balance at the given date. conversion_balance = interpolate.compute_entries_balance(entries, date=date) # Early exit if there is nothing to do. conversion_cost_balance = conversion_balance.reduce(convert.get_cost) if conversion_cost_balance.is_empty(): return entries # Calculate the index and the date for the new entry. We want to store it as # the last transaction of the day before. if date is not None: index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) last_date = date - datetime.timedelta(days=1) else: index = len(entries) last_date = entries[-1].date meta = data.new_metadata('', -1) narration = 'Conversion for {}'.format(conversion_balance) conversion_entry = Transaction(meta, last_date, flags.FLAG_CONVERSIONS, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) for position in conversion_cost_balance.get_positions(): # Important note: Set the cost to zero here to maintain the balance # invariant. (This is the only single place we cheat on the balance rule # in the entire system and this is necessary; see documentation on # Conversions.) price = amount.Amount(ZERO, conversion_currency) neg_pos = -position conversion_entry.postings.append( data.Posting(conversion_account, neg_pos.units, neg_pos.cost, price, None, None)) # Make a copy of the list of entries and insert the new transaction into it. new_entries = list(entries) new_entries.insert(index, conversion_entry) return new_entries","title":"conversions()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.create_entries_from_balances","text":"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Parameters: balances \u2013 A dict of account name strings to Inventory instances. date \u2013 A datetime.date object, the date at which to create the transaction. source_account \u2013 A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction \u2013 If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta \u2013 A dict to use as metadata for the transactions. flag \u2013 A string, the flag to use for the transactions. narration_template \u2013 A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. Source code in beancount/ops/summarize.py def create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template): \"\"\"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Args: balances: A dict of account name strings to Inventory instances. date: A datetime.date object, the date at which to create the transaction. source_account: A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction: If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta: A dict to use as metadata for the transactions. flag: A string, the flag to use for the transactions. narration_template: A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. \"\"\" new_entries = [] for account, account_balance in sorted(balances.items()): # Don't create new entries where there is no balance. if account_balance.is_empty(): continue narration = narration_template.format(account=account, date=date) if not direction: account_balance = -account_balance postings = [] new_entry = Transaction( meta, date, flag, None, narration, data.EMPTY_SET, data.EMPTY_SET, postings) for position in account_balance.get_positions(): postings.append(data.Posting(account, position.units, position.cost, None, None, None)) cost = -convert.get_cost(position) postings.append(data.Posting(source_account, cost, None, None, None, None)) new_entries.append(new_entry) return new_entries","title":"create_entries_from_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.get_open_entries","text":"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Parameters: entries \u2013 A list of directives. date \u2013 The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. Source code in beancount/ops/summarize.py def get_open_entries(entries, date): \"\"\"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Args: entries: A list of directives. date: The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. \"\"\" open_entries = {} for index, entry in enumerate(entries): if date is not None and entry.date >= date: break if isinstance(entry, Open): try: ex_index, ex_entry = open_entries[entry.account] if entry.date < ex_entry.date: open_entries[entry.account] = (index, entry) except KeyError: open_entries[entry.account] = (index, entry) elif isinstance(entry, Close): # If there is no corresponding open, don't raise an error. open_entries.pop(entry.account, None) return [entry for (index, entry) in sorted(open_entries.values())]","title":"get_open_entries()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.open","text":"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will Insert conversion transactions at the given open date, then Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, the date at which to do this. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will 1. Insert conversion transactions at the given open date, then 2. Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally 3. It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Args: entries: A list of directive tuples. date: A datetime.date instance, the date at which to do this. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, date) # Transfer income and expenses before the period to equity. entries, _ = clear(entries, date, account_types, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, date, account_opening) return entries, index","title":"open()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.open_opt","text":"Convenience function to open() using an options map. Source code in beancount/ops/summarize.py def open_opt(entries, date, options_map): \"\"\"Convenience function to open() using an options map. \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return open(entries, date, account_types, conversion_currency, *previous_accounts)","title":"open_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.summarize","text":"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the cutoff date before which to summarize. account_opening \u2013 A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. Source code in beancount/ops/summarize.py def summarize(entries, date, account_opening): \"\"\"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Args: entries: A list of directives. date: A datetime.date instance, the cutoff date before which to summarize. account_opening: A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. \"\"\" # Compute balances at date. balances, index = balance_by_account(entries, date) # We need to insert the entries with a date previous to subsequent checks, # to maintain ensure the open directives show up before any transaction. summarize_date = date - datetime.timedelta(days=1) # Create summarization / opening balance entries. summarizing_entries = create_entries_from_balances( balances, summarize_date, account_opening, True, data.new_metadata('', 0), flags.FLAG_SUMMARIZE, \"Opening balance for '{account}' (Summarization)\") # Insert the last price entry for each commodity from before the date. price_entries = prices.get_last_price_entries(entries, date) # Gather the list of active open entries at date. open_entries = get_open_entries(entries, date) # Compute entries before the date and preserve the entries after the date. before_entries = sorted(open_entries + price_entries + summarizing_entries, key=data.entry_sortkey) after_entries = entries[index:] # Return a new list of entries and the index that points after the entries # were inserted. return (before_entries + after_entries), len(before_entries)","title":"summarize()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.transfer_balances","text":"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to make the transfer. account_pred \u2013 A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account \u2013 A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. Source code in beancount/ops/summarize.py def transfer_balances(entries, date, account_pred, transfer_account): \"\"\"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Args: entries: A list of directives. date: A datetime.date instance, the date at which to make the transfer. account_pred: A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account: A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. \"\"\" # Don't bother doing anything if there are no entries. if not entries: return entries # Compute balances at date. balances, index = balance_by_account(entries, date) # Filter out to keep only the accounts we want. transfer_balances = {account: balance for account, balance in balances.items() if account_pred(account)} # We need to insert the entries at the end of the previous day. if date: transfer_date = date - datetime.timedelta(days=1) else: transfer_date = entries[-1].date # Create transfer entries. transfer_entries = create_entries_from_balances( transfer_balances, transfer_date, transfer_account, False, data.new_metadata('', 0), flags.FLAG_TRANSFER, \"Transfer balance for '{account}' (Transfer balance)\") # Remove balance assertions that occur after a transfer on an account that # has been transferred away; they would break. after_entries = [entry for entry in entries[index:] if not (isinstance(entry, balance.Balance) and entry.account in transfer_balances)] # Split the new entries in a new list. return (entries[:index] + transfer_entries + after_entries)","title":"transfer_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.truncate","text":"Filter out all the entries at and after date. Returns a new list of entries. Parameters: entries \u2013 A sorted list of directives. date \u2013 A datetime.date instance. Returns: A truncated list of directives. Source code in beancount/ops/summarize.py def truncate(entries, date): \"\"\"Filter out all the entries at and after date. Returns a new list of entries. Args: entries: A sorted list of directives. date: A datetime.date instance. Returns: A truncated list of directives. \"\"\" index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) return entries[:index]","title":"truncate()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation","text":"Validation checks. These checks are intended to be run after all the plugins have transformed the list of entries, just before serving them or generating reports from them. The idea is to ensure a reasonable set of invariants and generate errors if those invariants are violated. They are not sanity checks--user data is subject to constraints which are hopefully detected here and which will result in errors trickled up to the user.","title":"validation"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError","text":"ValidationError(source, message, entry)","title":"ValidationError"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/validation.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__new__","text":"Create new instance of ValidationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/validation.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate","text":"Perform all the standard checks on parsed contents. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. log_timings \u2013 An optional function to use for logging the time of individual operations. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate(entries, options_map, log_timings=None, extra_validations=None): \"\"\"Perform all the standard checks on parsed contents. Args: entries: A list of directives. unused_options_map: An options map. log_timings: An optional function to use for logging the time of individual operations. extra_validations: A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. \"\"\" validation_tests = VALIDATIONS if extra_validations: validation_tests += extra_validations # Run various validation routines define above. errors = [] for validation_function in validation_tests: with misc_utils.log_time('function: {}'.format(validation_function.__name__), log_timings, indent=2): new_errors = validation_function(entries, options_map) errors.extend(new_errors) return errors","title":"validate()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_active_accounts","text":"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_active_accounts(entries, unused_options_map): \"\"\"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" error_pairs = [] active_set = set() opened_accounts = set() for entry in entries: if isinstance(entry, data.Open): active_set.add(entry.account) opened_accounts.add(entry.account) elif isinstance(entry, data.Close): active_set.discard(entry.account) else: for account in getters.get_entry_accounts(entry): if account not in active_set: # Allow document and note directives that occur after an # account is closed. if (isinstance(entry, ALLOW_AFTER_CLOSE) and account in opened_accounts): continue # Register an error to be logged later, with an appropriate # message. error_pairs.append((account, entry)) # Refine the error message to disambiguate between the case of an account # that has never been seen and one that was simply not active at the time. errors = [] for account, entry in error_pairs: if account in opened_accounts: message = \"Invalid reference to inactive account '{}'\".format(account) else: message = \"Invalid reference to unknown account '{}'\".format(account) errors.append(ValidationError(entry.meta, message, entry)) return errors","title":"validate_active_accounts()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_check_transaction_balances","text":"Check again that all transaction postings balance, as users may have transformed transactions. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_check_transaction_balances(entries, options_map): \"\"\"Check again that all transaction postings balance, as users may have transformed transactions. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Note: this is a bit slow; we could limit our checks to the original # transactions by using the hash function in the loader. errors = [] for entry in entries: if isinstance(entry, Transaction): # IMPORTANT: This validation is _crucial_ and cannot be skipped. # This is where we actually detect and warn on unbalancing # transactions. This _must_ come after the user routines, because # unbalancing input is legal, as those types of transactions may be # \"fixed up\" by a user-plugin. In other words, we want to allow # users to input unbalancing transactions as long as the final # transactions objects that appear on the stream (after processing # the plugins) are balanced. See {9e6c14b51a59}. # # Detect complete sets of postings that have residual balance; residual = interpolate.compute_residual(entry.postings) tolerances = interpolate.infer_tolerances(entry.postings, options_map) if not residual.is_small(tolerances): errors.append( ValidationError(entry.meta, \"Transaction does not balance: {}\".format(residual), entry)) return errors","title":"validate_check_transaction_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_currency_constraints","text":"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_currency_constraints(entries, options_map): \"\"\"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Get all the open entries with currency constraints. open_map = {entry.account: entry for entry in entries if isinstance(entry, Open) and entry.currencies} errors = [] for entry in entries: if not isinstance(entry, Transaction): continue for posting in entry.postings: # Look up the corresponding account's valid currencies; skip the # check if there are none specified. try: open_entry = open_map[posting.account] valid_currencies = open_entry.currencies if not valid_currencies: continue except KeyError: continue # Perform the check. if posting.units.currency not in valid_currencies: errors.append( ValidationError( entry.meta, \"Invalid currency {} for account '{}'\".format( posting.units.currency, posting.account), entry)) return errors","title":"validate_currency_constraints()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_data_types","text":"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_data_types(entries, options_map): \"\"\"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] for entry in entries: try: data.sanity_check_types( entry, options_map[\"allow_deprecated_none_for_tags_and_links\"]) except AssertionError as exc: errors.append( ValidationError(entry.meta, \"Invalid data types: {}\".format(exc), entry)) return errors","title":"validate_data_types()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_documents_paths","text":"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_documents_paths(entries, options_map): \"\"\"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" return [ValidationError(entry.meta, \"Invalid relative path for entry\", entry) for entry in entries if (isinstance(entry, Document) and not path.isabs(entry.filename))]","title":"validate_documents_paths()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_duplicate_balances","text":"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_balances(entries, unused_options_map): \"\"\"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. balance_entries = {} for entry in entries: if not isinstance(entry, data.Balance): continue key = (entry.account, entry.amount.currency, entry.date) try: previous_entry = balance_entries[key] if entry.amount != previous_entry.amount: errors.append( ValidationError( entry.meta, \"Duplicate balance assertion with different amounts\", entry)) except KeyError: balance_entries[key] = entry return errors","title":"validate_duplicate_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_duplicate_commodities","text":"Check that commodity entries are unique for each commodity. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_commodities(entries, unused_options_map): \"\"\"Check that commodity entries are unique for each commodity. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. commodity_entries = {} for entry in entries: if not isinstance(entry, data.Commodity): continue key = entry.currency try: previous_entry = commodity_entries[key] if previous_entry: errors.append( ValidationError( entry.meta, \"Duplicate commodity directives for '{}'\".format(key), entry)) except KeyError: commodity_entries[key] = entry return errors","title":"validate_duplicate_commodities()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_open_close","text":"Check constraints on open and close directives themselves. This method checks two kinds of constraints: An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. Close directives may only appears if an open directive has been seen previous (chronologically). The date of close directives must be strictly greater than their corresponding open directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_open_close(entries, unused_options_map): \"\"\"Check constraints on open and close directives themselves. This method checks two kinds of constraints: 1. An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. 2. Close directives may only appears if an open directive has been seen previous (chronologically). 3. The date of close directives must be strictly greater than their corresponding open directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] open_map = {} close_map = {} for entry in entries: if isinstance(entry, Open): if entry.account in open_map: errors.append( ValidationError( entry.meta, \"Duplicate open directive for {}\".format(entry.account), entry)) else: open_map[entry.account] = entry elif isinstance(entry, Close): if entry.account in close_map: errors.append( ValidationError( entry.meta, \"Duplicate close directive for {}\".format(entry.account), entry)) else: try: open_entry = open_map[entry.account] if entry.date <= open_entry.date: errors.append( ValidationError( entry.meta, \"Internal error: closing date for {} \" \"appears before opening date\".format(entry.account), entry)) except KeyError: errors.append( ValidationError( entry.meta, \"Unopened account {} is being closed\".format(entry.account), entry)) close_map[entry.account] = entry return errors","title":"validate_open_close()"},{"location":"api_reference/beancount.parser.html","text":"beancount.parser \uf0c1 Parser module for beancount input files. beancount.parser.booking \uf0c1 Algorithms for 'booking' inventory, that is, the process of finding a matching lot when reducing the content of an inventory. beancount.parser.booking.BookingError ( tuple ) \uf0c1 BookingError(source, message, entry) beancount.parser.booking.BookingError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking.BookingError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BookingError(source, message, entry) beancount.parser.booking.BookingError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking.book(incomplete_entries, options_map) \uf0c1 Book inventory lots and complete all positions with incomplete numbers. Parameters: incomplete_entries \u2013 A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map \u2013 An options dict as produced by the parser. Returns: A pair of entries \u2013 A list of completed entries with all their postings completed. errors: New errors produced during interpolation. Source code in beancount/parser/booking.py def book(incomplete_entries, options_map): \"\"\"Book inventory lots and complete all positions with incomplete numbers. Args: incomplete_entries: A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map: An options dict as produced by the parser. Returns: A pair of entries: A list of completed entries with all their postings completed. errors: New errors produced during interpolation. \"\"\" # Get the list of booking methods for each account. booking_methods = collections.defaultdict(lambda: options_map[\"booking_method\"]) for entry in incomplete_entries: if isinstance(entry, data.Open) and entry.booking: booking_methods[entry.account] = entry.booking # Do the booking here! entries, booking_errors = booking_full.book(incomplete_entries, options_map, booking_methods) # Check for MISSING elements remaining. missing_errors = validate_missing_eliminated(entries, options_map) return entries, (booking_errors + missing_errors) beancount.parser.booking.validate_inventory_booking(entries, unused_options_map, booking_methods) \uf0c1 Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. booking_methods \u2013 A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_inventory_booking(entries, unused_options_map, booking_methods): \"\"\"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Args: entries: A list of directives. unused_options_map: An options map. booking_methods: A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. \"\"\" errors = [] balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: # Update the balance of each posting on its respective account # without allowing booking to a negative position, and if an error # is encountered, catch it and return it. running_balance = balances[posting.account] position_, _ = running_balance.add_position(posting) # Skip this check if the booking method is set to ignore it. if booking_methods.get(posting.account, None) == data.Booking.NONE: continue # Check if the resulting inventory is mixed, which is not # allowed under the STRICT method. if running_balance.is_mixed(): errors.append( BookingError( entry.meta, (\"Reducing position results in inventory with positive \" \"and negative lots: {}\").format(position_), entry)) return errors beancount.parser.booking.validate_missing_eliminated(entries, unused_options_map) \uf0c1 Validate that all the missing bits of postings have been eliminated. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_missing_eliminated(entries, unused_options_map): \"\"\"Validate that all the missing bits of postings have been eliminated. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of errors. \"\"\" errors = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: units = posting.units cost = posting.cost if (MISSING in (units.number, units.currency) or cost is not None and MISSING in (cost.number, cost.currency, cost.date, cost.label)): errors.append( BookingError(entry.meta, \"Transaction has incomplete elements\", entry)) break return errors beancount.parser.booking_full \uf0c1 Full (new) booking implementation. Problem description: Interpolation and booking feed on each other, that is, numbers filled in from interpolation might affect the booking process, and numbers derived from the booking process may help carry out interpolation that would otherwise be under-defined. Here's an example of interpolation helping the booking process: Assume the ante-inventory of Assets:Investments contains two lots of shares of HOOL, one at 100.00 USD and the other at 101.00 USD and apply this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains -200 USD Interpolation is unambiguously able to back out a cost of 100 USD / HOOL, which would then result in an unambiguous booking result. On the other hand, consider this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains Now the interpolation cannot succeed. If the Assets:Investments account is configured to use the FIFO method, the 10 oldest shares would be selected for the cost, and we could then interpolate the capital gains correctly. First observation: The second case is much more frequent than the first, and the first is easily resolved manually by requiring a particular cost be specified. Moreover, in many cases there isn't just a single lot of shares to be reduced from and figuring out the correct set of shares given a target cost is an underspecified problem. Second observation: Booking can only be achieved for inventory reductions, not for augmentations. Therefore, we should carry out booking on inventory reductions and fail early if reduction is undefined there, and leave inventory augmentations with missing numbers undefined, so that interpolation can fill them in at a later stage. Note that one case we'd like to but may not be able to handle is of a reduction with interpolated price, like this: 2015-09-30 * Assets:Investments -10 HOOL {100.00 # USD} Expenses:Commission 9.95 USD Assets:Cash 990.05 USD Therefore we choose to 1) Carry out booking first, on inventory reductions only, and leave inventory augmentations as they are, possibly undefined. The 'cost' attributed of booked postings are converted from CostSpec to Cost. Augmented postings with missing amounts are left as CostSpec instances in order to allow for interpolation of total vs. per-unit amount. 2) Compute interpolations on the resulting postings. Undefined costs for inventory augmentations may be filled in by interpolations at this stage (if possible). 3) Finally, convert the interpolated CostSpec instances to Cost instances. Improving on this algorithm would require running a loop over the booking and interpolation steps until all numbers are resolved or no more inference can occur. We may consider that for later, as an experimental feature. My hunch is that there are so few cases for which this would be useful that we won't bother improving on the algorithm above. beancount.parser.booking_full.CategorizationError ( tuple ) \uf0c1 CategorizationError(source, message, entry) beancount.parser.booking_full.CategorizationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.CategorizationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CategorizationError(source, message, entry) beancount.parser.booking_full.CategorizationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.InterpolationError ( tuple ) \uf0c1 InterpolationError(source, message, entry) beancount.parser.booking_full.InterpolationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.InterpolationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of InterpolationError(source, message, entry) beancount.parser.booking_full.InterpolationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.MissingType ( Enum ) \uf0c1 The type of missing number. beancount.parser.booking_full.ReductionError ( tuple ) \uf0c1 ReductionError(source, message, entry) beancount.parser.booking_full.ReductionError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.ReductionError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ReductionError(source, message, entry) beancount.parser.booking_full.ReductionError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.Refer ( tuple ) \uf0c1 Refer(index, units_currency, cost_currency, price_currency) beancount.parser.booking_full.Refer.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.Refer.__new__(_cls, index, units_currency, cost_currency, price_currency) special staticmethod \uf0c1 Create new instance of Refer(index, units_currency, cost_currency, price_currency) beancount.parser.booking_full.Refer.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.SelfReduxError ( tuple ) \uf0c1 SelfReduxError(source, message, entry) beancount.parser.booking_full.SelfReduxError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.SelfReduxError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of SelfReduxError(source, message, entry) beancount.parser.booking_full.SelfReduxError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.book(entries, options_map, methods) \uf0c1 Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. Source code in beancount/parser/booking_full.py def book(entries, options_map, methods): \"\"\"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. \"\"\" entries, errors, _ = _book(entries, options_map, methods) return entries, errors beancount.parser.booking_full.book_reductions(entry, group_postings, balances, methods) \uf0c1 Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Parameters: entry \u2013 An instance of Transaction. This is only used to refer to when logging errors. group_postings \u2013 A list of Posting instances for the group. balances \u2013 A dict of account name to inventory contents. methods \u2013 A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings \u2013 A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. Source code in beancount/parser/booking_full.py def book_reductions(entry, group_postings, balances, methods): \"\"\"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. * For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. * For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Args: entry: An instance of Transaction. This is only used to refer to when logging errors. group_postings: A list of Posting instances for the group. balances: A dict of account name to inventory contents. methods: A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings: A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. \"\"\" errors = [] # A local copy of the balances dictionary which is updated just for the # duration of this function's updates, in order to take into account the # cumulative effect of all the postings inferred here local_balances = {} empty = inventory.Inventory() booked_postings = [] for posting in group_postings: # Process a single posting. units = posting.units costspec = posting.cost account = posting.account # Note: We ensure there is no mutation on 'balances' to keep this # function without side-effects. Note that we may be able to optimize # performance later on by giving up this property. # # Also note that if there is no existing balance, then won't be any lot # reduction because none of the postings will be able to match against # any currencies of the balance. previous_balance = balances.get(account, empty) balance = local_balances.setdefault(account, copy.copy(previous_balance)) # Check if this is a lot held at cost. if costspec is None or units.number is MISSING: # This posting is not held at cost; we do nothing. booked_postings.append(posting) else: # This posting is held at cost; figure out if it's a reduction or an # augmentation. method = methods[account] if (method is not Booking.NONE and balance is not None and balance.is_reduced_by(units)): # This posting is a reduction. # Match the positions. cost_number = compute_cost_number(costspec, units) matches = [] for position in balance: # Skip inventory contents of a different currency. if (units.currency and position.units.currency != units.currency): continue # Skip balance positions not held at cost. if position.cost is None: continue if (cost_number is not None and position.cost.number != cost_number): continue if (isinstance(costspec.currency, str) and position.cost.currency != costspec.currency): continue if (costspec.date and position.cost.date != costspec.date): continue if (costspec.label and position.cost.label != costspec.label): continue matches.append(position) # Check for ambiguous matches. if len(matches) == 0: errors.append( ReductionError(entry.meta, 'No position matches \"{}\" against balance {}'.format( posting, balance), entry)) return [], errors # This is irreconcilable, remove these postings. reduction_postings, matched_postings, ambi_errors = ( booking_method.handle_ambiguous_matches(entry, posting, matches, method)) if ambi_errors: errors.extend(ambi_errors) return [], errors # Add the reductions to the resulting list of booked postings. booked_postings.extend(reduction_postings) # Update the local balance in order to avoid matching against # the same postings twice when processing multiple postings in # the same transaction. Note that we only do this for postings # held at cost because the other postings may need interpolation # in order to be resolved properly. for posting in reduction_postings: balance.add_position(posting) else: # This posting is an augmentation. # # Note that we do not convert the CostSpec instances to Cost # instances, because we want to let the subsequent interpolation # process able to interpolate either the cost per-unit or the # total cost, separately. # Put in the date of the parent Transaction if there is no # explicit date specified on the spec. if costspec.date is None: dated_costspec = costspec._replace(date=entry.date) posting = posting._replace(cost=dated_costspec) # FIXME: Insert unique ids for trade tracking; right now this # creates ambiguous matches errors (and it shouldn't). # # Insert a unique label if there isn't one. # if posting.cost is not None and posting.cost.label is None: # posting = posting._replace( # cost=posting.cost._replace(label=unique_label())) booked_postings.append(posting) return booked_postings, errors beancount.parser.booking_full.categorize_by_currency(entry, balances) \uf0c1 Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. If the currency is explicitly specified, we put the posting in that currency's bucket. If not, we have a few methods left to disambiguate the currency: We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Parameters: postings \u2013 A list of incomplete postings to categorize. balances \u2013 A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains \u2013 index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. Source code in beancount/parser/booking_full.py def categorize_by_currency(entry, balances): \"\"\"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. - First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. - If the currency is explicitly specified, we put the posting in that currency's bucket. - If not, we have a few methods left to disambiguate the currency: 1. We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. 2. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Args: postings: A list of incomplete postings to categorize. balances: A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains: index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. \"\"\" errors = [] groups = collections.defaultdict(list) sortdict = {} auto_postings = [] unknown = [] for index, posting in enumerate(entry.postings): units = posting.units cost = posting.cost price = posting.price # Extract and override the currencies locally. units_currency = (units.currency if units is not MISSING and units is not None else None) cost_currency = (cost.currency if cost is not MISSING and cost is not None else None) price_currency = (price.currency if price is not MISSING and price is not None else None) # First we apply the constraint that cost-currency and price-currency # must match, if there is both a cost and a price. This reduces the # space of possibilities somewhat. if cost_currency is MISSING and isinstance(price_currency, str): cost_currency = price_currency if price_currency is MISSING and isinstance(cost_currency, str): price_currency = cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) if units is MISSING and price_currency is None: # Bucket auto-postings separately from unknown. auto_postings.append(refer) else: # Bucket with what we know so far. currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: # If we need to infer the currency, store in unknown. unknown.append(refer) # We look at the remaining postings... if they are all of a single currency, # the posting must be in that currency too. if unknown and len(unknown) == 1 and len(groups) == 1: (index, units_currency, cost_currency, price_currency) = unknown.pop() other_currency = next(iter(groups.keys())) if price_currency is None and cost_currency is None: # Infer to the units currency. units_currency = other_currency else: # Infer to the cost and price currencies. if price_currency is MISSING: price_currency = other_currency if cost_currency is MISSING: cost_currency = other_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) assert currency is not None sortdict.setdefault(currency, index) groups[currency].append(refer) # Finally, try to resolve all the unknown legs using the inventory contents # of each account. for refer in unknown: (index, units_currency, cost_currency, price_currency) = refer posting = entry.postings[index] balance = balances.get(posting.account, None) if balance is None: balance = inventory.Inventory() if units_currency is MISSING: balance_currencies = balance.currencies() if len(balance_currencies) == 1: units_currency = balance_currencies.pop() if cost_currency is MISSING or price_currency is MISSING: balance_cost_currencies = balance.cost_currencies() if len(balance_cost_currencies) == 1: balance_cost_currency = balance_cost_currencies.pop() if price_currency is MISSING: price_currency = balance_cost_currency if cost_currency is MISSING: cost_currency = balance_cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: errors.append( CategorizationError(posting.meta, \"Failed to categorize posting {}\".format(index + 1), entry)) # Fill in missing units currencies if some remain as missing. This may occur # if we used the cost or price to bucket the currency but the units currency # was missing. for currency, refers in groups.items(): for rindex, refer in enumerate(refers): if refer.units_currency is MISSING: posting = entry.postings[refer.index] balance = balances.get(posting.account, None) if balance is None: continue balance_currencies = balance.currencies() if len(balance_currencies) == 1: refers[rindex] = refer._replace(units_currency=balance_currencies.pop()) # Deal with auto-postings. if len(auto_postings) > 1: refer = auto_postings[-1] posting = entry.postings[refer.index] errors.append( CategorizationError(posting.meta, \"You may not have more than one auto-posting per currency\", entry)) auto_postings = auto_postings[0:1] for refer in auto_postings: for currency in groups.keys(): sortdict.setdefault(currency, refer.index) groups[currency].append(Refer(refer.index, currency, None, None)) # Issue error for all currencies which we could not resolve. for currency, refers in groups.items(): for refer in refers: posting = entry.postings[refer.index] for currency, name in [(refer.units_currency, 'units'), (refer.cost_currency, 'cost'), (refer.price_currency, 'price')]: if currency is MISSING: errors.append(CategorizationError( posting.meta, \"Could not resolve {} currency\".format(name), entry)) sorted_groups = sorted(groups.items(), key=lambda item: sortdict[item[0]]) return sorted_groups, errors beancount.parser.booking_full.compute_cost_number(costspec, units) \uf0c1 Given a CostSpec, return the cost number, if possible to compute. Parameters: costspec \u2013 A parsed instance of CostSpec. units \u2013 An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. Source code in beancount/parser/booking_full.py def compute_cost_number(costspec, units): \"\"\"Given a CostSpec, return the cost number, if possible to compute. Args: costspec: A parsed instance of CostSpec. units: An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. \"\"\" number_per = costspec.number_per number_total = costspec.number_total if MISSING in (number_per, number_total): return None if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total units_number = units.number if number_per is not None: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) elif number_per is None: return None else: unit_cost = number_per return unit_cost beancount.parser.booking_full.convert_costspec_to_cost(posting) \uf0c1 Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. Source code in beancount/parser/booking_full.py def convert_costspec_to_cost(posting): \"\"\"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Args: posting: An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. \"\"\" cost = posting.cost if isinstance(cost, position.CostSpec): if cost is not None: units_number = posting.units.number number_per = cost.number_per number_total = cost.number_total if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total if number_per is not MISSING: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) else: unit_cost = number_per new_cost = Cost(unit_cost, cost.currency, cost.date, cost.label) posting = posting._replace(units=posting.units, cost=new_cost) return posting beancount.parser.booking_full.get_bucket_currency(refer) \uf0c1 Given currency references for a posting, return the bucket currency. Parameters: refer \u2013 An instance of Refer. Returns: A currency string. Source code in beancount/parser/booking_full.py def get_bucket_currency(refer): \"\"\"Given currency references for a posting, return the bucket currency. Args: refer: An instance of Refer. Returns: A currency string. \"\"\" currency = None if isinstance(refer.cost_currency, str): currency = refer.cost_currency elif isinstance(refer.price_currency, str): currency = refer.price_currency elif (refer.cost_currency is None and refer.price_currency is None and isinstance(refer.units_currency, str)): currency = refer.units_currency return currency beancount.parser.booking_full.has_self_reduction(postings, methods) \uf0c1 Return true if the postings potentially reduce each other at cost. Parameters: postings \u2013 A list of postings with uninterpolated CostSpec cost instances. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. Source code in beancount/parser/booking_full.py def has_self_reduction(postings, methods): \"\"\"Return true if the postings potentially reduce each other at cost. Args: postings: A list of postings with uninterpolated CostSpec cost instances. methods: A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. \"\"\" # A mapping of (currency, cost-currency) and sign. cost_changes = {} for posting in postings: cost = posting.cost if cost is None: continue if methods[posting.account] is Booking.NONE: continue key = (posting.account, posting.units.currency) sign = 1 if posting.units.number > ZERO else -1 if cost_changes.setdefault(key, sign) != sign: return True return False beancount.parser.booking_full.interpolate_group(postings, balances, currency, tolerances) \uf0c1 Interpolate missing numbers in the set of postings. Parameters: postings \u2013 A list of Posting instances. balances \u2013 A dict of account to its ante-inventory. currency \u2013 The weight currency of this group, used for reporting errors. tolerances \u2013 A dict of currency to tolerance values. Returns: A tuple of postings \u2013 A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) Source code in beancount/parser/booking_full.py def interpolate_group(postings, balances, currency, tolerances): \"\"\"Interpolate missing numbers in the set of postings. Args: postings: A list of Posting instances. balances: A dict of account to its ante-inventory. currency: The weight currency of this group, used for reporting errors. tolerances: A dict of currency to tolerance values. Returns: A tuple of postings: A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) \"\"\" errors = [] # Figure out which type of amount is missing, by creating a list of # incomplete postings and which type of units is missing. incomplete = [] for index, posting in enumerate(postings): units = posting.units cost = posting.cost price = posting.price # Identify incomplete parts of the Posting components. if units.number is MISSING: incomplete.append((MissingType.UNITS, index)) if isinstance(cost, CostSpec): if cost and cost.number_per is MISSING: incomplete.append((MissingType.COST_PER, index)) if cost and cost.number_total is MISSING: incomplete.append((MissingType.COST_TOTAL, index)) else: # Check that a resolved instance of Cost never needs interpolation. # # Note that in theory we could support the interpolation of regular # per-unit costs in these if we wanted to; but because they're all # reducing postings that have been booked earlier, those never need # to be interpolated. if cost is not None: assert isinstance(cost.number, Decimal), ( \"Internal error: cost has no number: {}\".format(cost)) if price and price.number is MISSING: incomplete.append((MissingType.PRICE, index)) # The replacement posting for the incomplete posting of this group. new_posting = None if len(incomplete) == 0: # If there are no missing numbers, just convert the CostSpec to Cost and # return that. out_postings = [convert_costspec_to_cost(posting) for posting in postings] elif len(incomplete) > 1: # If there is more than a single value to be interpolated, generate an # error and return no postings. _, posting_index = incomplete[0] errors.append(InterpolationError( postings[posting_index].meta, \"Too many missing numbers for currency group '{}'\".format(currency), None)) out_postings = [] else: # If there is a single missing number, calculate it and fill it in here. missing, index = incomplete[0] incomplete_posting = postings[index] # Convert augmenting postings' costs from CostSpec to corresponding Cost # instances, except for the incomplete posting. new_postings = [(posting if posting is incomplete_posting else convert_costspec_to_cost(posting)) for posting in postings] # Compute the balance of the other postings. residual = interpolate.compute_residual(posting for posting in new_postings if posting is not incomplete_posting) assert len(residual) < 2, \"Internal error in grouping postings by currencies.\" if not residual.is_empty(): respos = next(iter(residual)) assert respos.cost is None, ( \"Internal error; cost appears in weight calculation.\") assert respos.units.currency == currency, ( \"Internal error; residual different than currency group.\") weight = -respos.units.number weight_currency = respos.units.currency else: weight = ZERO weight_currency = currency if missing == MissingType.UNITS: units = incomplete_posting.units cost = incomplete_posting.cost if cost: # Handle the special case where we only have total cost. if cost.number_per == ZERO: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer per-unit cost only from total\", None)) return postings, errors, True assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") cost_total = cost.number_total or ZERO units_number = (weight - cost_total) / cost.number_per elif incomplete_posting.price: assert incomplete_posting.price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight / incomplete_posting.price.number else: assert units.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight # Quantize the interpolated units if necessary. units_number = interpolate.quantize_with_tolerance(tolerances, units.currency, units_number) if weight != ZERO: new_pos = Position(Amount(units_number, units.currency), cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_PER: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") if units.number != ZERO: number_per = (weight - (cost.number_total or ZERO)) / units.number new_cost = cost._replace(number_per=number_per) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_TOTAL: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") number_total = (weight - cost.number_per * units.number) new_cost = cost._replace(number_total=number_total) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) elif missing == MissingType.PRICE: units = incomplete_posting.units cost = incomplete_posting.cost if cost is not None: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer price for postings with units held at cost\", None)) return postings, errors, True else: price = incomplete_posting.price assert price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") new_price_number = abs(weight / units.number) new_posting = incomplete_posting._replace(price=Amount(new_price_number, price.currency)) else: assert False, \"Internal error; Invalid missing type.\" # Replace the number in the posting. if new_posting is not None: # Set meta-data on the new posting to indicate it was interpolated. if new_posting.meta is None: new_posting = new_posting._replace(meta={}) new_posting.meta[interpolate.AUTOMATIC_META] = True # Convert augmenting posting costs from CostSpec to a corresponding # Cost instance. new_postings[index] = convert_costspec_to_cost(new_posting) else: del new_postings[index] out_postings = new_postings assert all(not isinstance(posting.cost, CostSpec) for posting in out_postings) # Check that units are non-zero and that no cost remains negative; issue an # error if this is the case. for posting in out_postings: if posting.cost is None: continue # If there is a cost, we don't allow either a cost value of zero, # nor a zero number of units. Note that we allow a price of zero as # the only special case allowed (for conversion entries), but never # for costs. if posting.units.number == ZERO: errors.append(InterpolationError( posting.meta, 'Amount is zero: \"{}\"'.format(posting.units), None)) if posting.cost.number < ZERO: errors.append(InterpolationError( posting.meta, 'Cost is negative: \"{}\"'.format(posting.cost), None)) return out_postings, errors, (new_posting is not None) beancount.parser.booking_full.replace_currencies(postings, refer_groups) \uf0c1 Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Parameters: postings \u2013 A list of Posting instances to replace. refer_groups \u2013 A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. Source code in beancount/parser/booking_full.py def replace_currencies(postings, refer_groups): \"\"\"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Args: postings: A list of Posting instances to replace. refer_groups: A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. \"\"\" new_groups = [] for currency, refers in refer_groups: new_postings = [] for refer in sorted(refers, key=lambda r: r.index): posting = postings[refer.index] units = posting.units if units is MISSING or units is None: posting = posting._replace(units=Amount(MISSING, refer.units_currency)) else: replace = False cost = posting.cost price = posting.price if units.currency is MISSING: units = Amount(units.number, refer.units_currency) replace = True if cost and cost.currency is MISSING: cost = cost._replace(currency=refer.cost_currency) replace = True if price and price.currency is MISSING: price = Amount(price.number, refer.price_currency) replace = True if replace: posting = posting._replace(units=units, cost=cost, price=price) new_postings.append(posting) new_groups.append((currency, new_postings)) return new_groups beancount.parser.booking_full.unique_label() \uf0c1 Return a globally unique label for cost entries. Source code in beancount/parser/booking_full.py def unique_label() -> Text: \"Return a globally unique label for cost entries.\" return str(uuid.uuid4()) beancount.parser.booking_method \uf0c1 Implementations of all the particular booking methods. This code is used by the full booking algorithm. beancount.parser.booking_method.AmbiguousMatchError ( tuple ) \uf0c1 AmbiguousMatchError(source, message, entry) beancount.parser.booking_method.AmbiguousMatchError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_method.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_method.AmbiguousMatchError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of AmbiguousMatchError(source, message, entry) beancount.parser.booking_method.AmbiguousMatchError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_method.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_method.booking_method_AVERAGE(entry, posting, matches) \uf0c1 AVERAGE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_AVERAGE(entry, posting, matches): \"\"\"AVERAGE booking method implementation.\"\"\" booked_reductions = [] booked_matches = [] errors = [AmbiguousMatchError(entry.meta, \"AVERAGE method is not supported\", entry)] return booked_reductions, booked_matches, errors, False # FIXME: Future implementation here. # pylint: disable=unreachable if False: # pylint: disable=using-constant-test # DISABLED - This is the code for AVERAGE, which is currently disabled. # If there is more than a single match we need to ultimately merge the # postings. Also, if the reducing posting provides a specific cost, we # need to update the cost basis as well. Both of these cases are carried # out by removing all the matches and readding them later on. if len(matches) == 1 and ( not isinstance(posting.cost.number_per, Decimal) and not isinstance(posting.cost.number_total, Decimal)): # There is no cost. Just reduce the one leg. This should be the # normal case if we always merge augmentations and the user lets # Beancount deal with the cost. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) insufficient = (match_units.number != posting.units.number) else: # Merge the matching postings to a single one. merged_units = inventory.Inventory() merged_cost = inventory.Inventory() for match in matches: merged_units.add_amount(match.units) merged_cost.add_amount(convert.get_weight(match)) if len(merged_units) != 1 or len(merged_cost) != 1: errors.append( AmbiguousMatchError( entry.meta, 'Cannot merge positions in multiple currencies: {}'.format( ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: if (isinstance(posting.cost.number_per, Decimal) or isinstance(posting.cost.number_total, Decimal)): errors.append( AmbiguousMatchError( entry.meta, \"Explicit cost reductions aren't supported yet: {}\".format( position.to_string(posting)), entry)) else: # Insert postings to remove all the matches. booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost, flag=flags.FLAG_MERGING) for match in matches) units = merged_units[0].units date = matches[0].cost.date ## FIXME: Select which one, ## oldest or latest. cost_units = merged_cost[0].units cost = Cost(cost_units.number/units.number, cost_units.currency, date, None) # Insert a posting to refill those with a replacement match. booked_reductions.append( posting._replace(units=units, cost=cost, flag=flags.FLAG_MERGING)) # Now, match the reducing request against this lot. booked_reductions.append( posting._replace(units=posting.units, cost=cost)) insufficient = abs(posting.units.number) > abs(units.number) beancount.parser.booking_method.booking_method_FIFO(entry, posting, matches) \uf0c1 FIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_FIFO(entry, posting, matches): \"\"\"FIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, False) beancount.parser.booking_method.booking_method_LIFO(entry, posting, matches) \uf0c1 LIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_LIFO(entry, posting, matches): \"\"\"LIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, True) beancount.parser.booking_method.booking_method_NONE(entry, posting, matches) \uf0c1 NONE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_NONE(entry, posting, matches): \"\"\"NONE booking method implementation.\"\"\" # This never needs to match against any existing positions... we # disregard the matches, there's never any error. Note that this never # gets called in practice, we want to treat NONE postings as # augmentations. Default behaviour is to return them with their original # CostSpec, and the augmentation code will handle signaling an error if # there is insufficient detail to carry out the conversion to an # instance of Cost. # Note that it's an interesting question whether a reduction on an # account with NONE method which happens to match a single position # ought to be matched against it. We don't allow it for now. return [posting], [], False beancount.parser.booking_method.booking_method_STRICT(entry, posting, matches) \uf0c1 Strict booking method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. Source code in beancount/parser/booking_method.py def booking_method_STRICT(entry, posting, matches): \"\"\"Strict booking method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. \"\"\" booked_reductions = [] booked_matches = [] errors = [] insufficient = False # In strict mode, we require at most a single matching posting. if len(matches) > 1: # If the total requested to reduce matches the sum of all the # ambiguous postings, match against all of them. sum_matches = sum(p.units.number for p in matches) if sum_matches == -posting.units.number: booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost) for match in matches) else: errors.append( AmbiguousMatchError(entry.meta, 'Ambiguous matches for \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: # Replace the posting's units and cost values. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) booked_matches.append(match) insufficient = (match_units.number != posting.units.number) return booked_reductions, booked_matches, errors, insufficient beancount.parser.booking_method.handle_ambiguous_matches(entry, posting, matches, method) \uf0c1 Handle ambiguous matches by dispatching to a particular method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. Source code in beancount/parser/booking_method.py def handle_ambiguous_matches(entry, posting, matches, method): \"\"\"Handle ambiguous matches by dispatching to a particular method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods: A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. \"\"\" assert isinstance(method, Booking), ( \"Invalid type: {}\".format(method)) assert matches, \"Internal error: Invalid call with no matches\" #method = globals()['booking_method_{}'.format(method.name)] method = _BOOKING_METHODS[method] (booked_reductions, booked_matches, errors, insufficient) = method(entry, posting, matches) if insufficient: errors.append( AmbiguousMatchError(entry.meta, 'Not enough lots to reduce \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) return booked_reductions, booked_matches, errors beancount.parser.cmptest \uf0c1 Support utilities for testing scripts. beancount.parser.cmptest.TestError ( Exception ) \uf0c1 Errors within the test implementation itself. These should never occur. beancount.parser.cmptest.assertEqualEntries(expected_entries, actual_entries, failfunc=, allow_incomplete=False) \uf0c1 Compare two lists of entries exactly and print missing entries verbosely if they occur. Parameters: expected_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries \u2013 Same treatment as expected_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertEqualEntries(expected_entries, actual_entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, \"Missing is missing: {}, {}\".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write(\"Present in expected set and not in actual set:\\n\\n\") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') if actual_missing: oss.write(\"Present in actual set and not in expected set:\\n\\n\") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.assertExcludesEntries(subset_entries, entries, failfunc=, allow_incomplete=False) \uf0c1 Check that subset_entries is not included in entries and print extra entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertExcludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is not included in entries and print extra entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) excludes, extra = compare.excludes_entries(subset_entries, entries) if not excludes: assert extra, \"Extra is empty: {}\".format(extra) oss = io.StringIO() if extra: oss.write(\"Extra from from first/excluded set:\\n\\n\") for entry in extra: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.assertIncludesEntries(subset_entries, entries, failfunc=, allow_incomplete=False) \uf0c1 Check that subset_entries is included in entries and print missing entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertIncludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is included in entries and print missing entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) includes, missing = compare.includes_entries(subset_entries, entries) if not includes: assert missing, \"Missing is empty: {}\".format(missing) oss = io.StringIO() if missing: oss.write(\"Missing from from expected set:\\n\\n\") for entry in missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.read_string_or_entries(entries_or_str, allow_incomplete=False) \uf0c1 Read a string of entries or just entries. Parameters: entries_or_str \u2013 Either a list of directives, or a string containing directives. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. Source code in beancount/parser/cmptest.py def read_string_or_entries(entries_or_str, allow_incomplete=False): \"\"\"Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. \"\"\" if isinstance(entries_or_str, str): entries, errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) if allow_incomplete: # Do a simplistic local conversion in order to call the comparison. entries = [_local_booking(entry) for entry in entries] else: # Don't accept incomplete entries either. if any(parser.is_entry_incomplete(entry) for entry in entries): raise TestError(\"Entries in assertions may not use interpolation.\") entries, booking_errors = booking.book(entries, options_map) errors = errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError(\"Unexpected errors in expected: {}\".format(oss.getvalue())) else: assert isinstance(entries_or_str, list), \"Expecting list: {}\".format(entries_or_str) entries = entries_or_str return entries beancount.parser.grammar \uf0c1 Builder for Beancount grammar. beancount.parser.grammar.Builder ( LexBuilder ) \uf0c1 A builder used by the lexer and grammar parser as callbacks to create the data objects corresponding to rules parsed from the input file. beancount.parser.grammar.Builder.amount(self, number, currency) \uf0c1 Process an amount grammar rule. Parameters: number \u2013 a Decimal instance, the number of the amount. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. Source code in beancount/parser/grammar.py def amount(self, number, currency): \"\"\"Process an amount grammar rule. Args: number: a Decimal instance, the number of the amount. currency: a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number, currency) return Amount(number, currency) beancount.parser.grammar.Builder.balance(self, filename, lineno, date, account, amount, tolerance, kvlist) \uf0c1 Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to balance. amount \u2013 The expected amount, to be checked. tolerance \u2013 The tolerance number. kvlist \u2013 a list of KeyValue instances. Returns: A new Balance object. Source code in beancount/parser/grammar.py def balance(self, filename, lineno, date, account, amount, tolerance, kvlist): \"\"\"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to balance. amount: The expected amount, to be checked. tolerance: The tolerance number. kvlist: a list of KeyValue instances. Returns: A new Balance object. \"\"\" diff_amount = None meta = new_metadata(filename, lineno, kvlist) return Balance(meta, date, account, amount, tolerance, diff_amount) beancount.parser.grammar.Builder.build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None) \uf0c1 Build a grammar error and appends it to the list of pending errors. Parameters: filename \u2013 The current filename lineno \u2013 The current line number excvalue \u2013 The exception value, or a str, the message of the error. exc_type \u2013 An exception type, if an exception occurred. exc_traceback \u2013 A traceback object. Source code in beancount/parser/grammar.py def build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None): \"\"\"Build a grammar error and appends it to the list of pending errors. Args: filename: The current filename lineno: The current line number excvalue: The exception value, or a str, the message of the error. exc_type: An exception type, if an exception occurred. exc_traceback: A traceback object. \"\"\" if exc_type is not None: assert not isinstance(exc_value, str) strings = traceback.format_exception_only(exc_type, exc_value) tblist = traceback.extract_tb(exc_traceback) filename, lineno, _, __ = tblist[0] message = '{} ({}:{})'.format(strings[0], filename, lineno) else: message = str(exc_value) meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, message, None)) beancount.parser.grammar.Builder.close(self, filename, lineno, date, account, kvlist) \uf0c1 Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def close(self, filename, lineno, date, account, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Close(meta, date, account) beancount.parser.grammar.Builder.commodity(self, filename, lineno, date, currency, kvlist) \uf0c1 Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. currency \u2013 A string, the commodity being declared. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def commodity(self, filename, lineno, date, currency, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. currency: A string, the commodity being declared. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Commodity(meta, date, currency) beancount.parser.grammar.Builder.compound_amount(self, number_per, number_total, currency) \uf0c1 Process an amount grammar rule. Parameters: number_per \u2013 a Decimal instance, the number of the cost per share. number_total \u2013 a Decimal instance, the number of the cost over all shares. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. Source code in beancount/parser/grammar.py def compound_amount(self, number_per, number_total, currency): \"\"\"Process an amount grammar rule. Args: number_per: a Decimal instance, the number of the cost per share. number_total: a Decimal instance, the number of the cost over all shares. currency: a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number_per, currency) self.dcupdate(number_total, currency) # Note that we are not able to reduce the value to a number per-share # here because we only get the number of units in the full lot spec. return CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.Builder.cost_merge(self, _) \uf0c1 Create a 'merge cost' token. Source code in beancount/parser/grammar.py def cost_merge(self, _): \"\"\"Create a 'merge cost' token.\"\"\" return MERGE_COST beancount.parser.grammar.Builder.cost_spec(self, cost_comp_list, is_total) \uf0c1 Process a cost_spec grammar rule. Parameters: cost_comp_list \u2013 A list of CompoundAmount, a datetime.date, or label ID strings. is_total \u2013 Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". Source code in beancount/parser/grammar.py def cost_spec(self, cost_comp_list, is_total): \"\"\"Process a cost_spec grammar rule. Args: cost_comp_list: A list of CompoundAmount, a datetime.date, or label ID strings. is_total: Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". \"\"\" if not cost_comp_list: return CostSpec(MISSING, None, MISSING, None, None, False) assert isinstance(cost_comp_list, list), ( \"Internal error in parser: {}\".format(cost_comp_list)) compound_cost = None date_ = None label = None merge = None for comp in cost_comp_list: if isinstance(comp, CompoundAmount): if compound_cost is None: compound_cost = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate cost: '{}'.\".format(comp), None)) elif isinstance(comp, date): if date_ is None: date_ = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate date: '{}'.\".format(comp), None)) elif comp is MERGE_COST: if merge is None: merge = True self.errors.append( ParserError(self.get_lexer_location(), \"Cost merging is not supported yet\", None)) else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate merge-cost spec\", None)) else: assert isinstance(comp, str), ( \"Currency component is not string: '{}'\".format(comp)) if label is None: label = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate label: '{}'.\".format(comp), None)) # If there was a cost_comp_list, thus a \"{...}\" cost basis spec, you must # indicate that by creating a CompoundAmount(), always. if compound_cost is None: number_per, number_total, currency = MISSING, None, MISSING else: number_per, number_total, currency = compound_cost if is_total: if number_total is not None: self.errors.append( ParserError( self.get_lexer_location(), (\"Per-unit cost may not be specified using total cost \" \"syntax: '{}'; ignoring per-unit cost\").format(compound_cost), None)) # Ignore per-unit number. number_per = ZERO else: # There's a single number specified; interpret it as a total cost. number_total = number_per number_per = ZERO if merge is None: merge = False return CostSpec(number_per, number_total, currency, date_, label, merge) beancount.parser.grammar.Builder.custom(self, filename, lineno, date, dir_type, custom_values, kvlist) \uf0c1 Process a custom directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. dir_type \u2013 A string, a type for the custom directive being parsed. custom_values \u2013 A list of the various tokens seen on the same line. kvlist \u2013 a list of KeyValue instances. Returns: A new Custom object. Source code in beancount/parser/grammar.py def custom(self, filename, lineno, date, dir_type, custom_values, kvlist): \"\"\"Process a custom directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. dir_type: A string, a type for the custom directive being parsed. custom_values: A list of the various tokens seen on the same line. kvlist: a list of KeyValue instances. Returns: A new Custom object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Custom(meta, date, dir_type, custom_values) beancount.parser.grammar.Builder.custom_value(self, value, dtype=None) \uf0c1 Create a custom value object, along with its type. Parameters: value \u2013 One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. Source code in beancount/parser/grammar.py def custom_value(self, value, dtype=None): \"\"\"Create a custom value object, along with its type. Args: value: One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. \"\"\" if dtype is None: dtype = type(value) return ValueType(value, dtype) beancount.parser.grammar.Builder.dcupdate(self, number, currency) \uf0c1 Update the display context. Source code in beancount/parser/grammar.py def dcupdate(self, number, currency): \"\"\"Update the display context.\"\"\" if isinstance(number, Decimal) and currency and currency is not MISSING: self._dcupdate(number, currency) beancount.parser.grammar.Builder.document(self, filename, lineno, date, account, document_filename, tags_links, kvlist) \uf0c1 Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. account \u2013 an Account instance. document_filename \u2013 a str, the name of the document file. tags_links \u2013 The current TagsLinks accumulator. kvlist \u2013 a list of KeyValue instances. Returns: A new Document object. Source code in beancount/parser/grammar.py def document(self, filename, lineno, date, account, document_filename, tags_links, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. account: an Account instance. document_filename: a str, the name of the document file. tags_links: The current TagsLinks accumulator. kvlist: a list of KeyValue instances. Returns: A new Document object. \"\"\" meta = new_metadata(filename, lineno, kvlist) if not path.isabs(document_filename): document_filename = path.abspath(path.join(path.dirname(filename), document_filename)) tags, links = self.finalize_tags_links(tags_links.tags, tags_links.links) return Document(meta, date, account, document_filename, tags, links) beancount.parser.grammar.Builder.event(self, filename, lineno, date, event_type, description, kvlist) \uf0c1 Process an event directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. event_type \u2013 a str, the name of the event type. description \u2013 a str, the event value, the contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Event object. Source code in beancount/parser/grammar.py def event(self, filename, lineno, date, event_type, description, kvlist): \"\"\"Process an event directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. event_type: a str, the name of the event type. description: a str, the event value, the contents. kvlist: a list of KeyValue instances. Returns: A new Event object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Event(meta, date, event_type, description) beancount.parser.grammar.Builder.finalize(self) \uf0c1 Finalize the parser, check for final errors and return the triple. Returns: A triple of entries \u2013 A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. Source code in beancount/parser/grammar.py def finalize(self): \"\"\"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries: A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. \"\"\" # If the user left some tags unbalanced, issue an error. for tag in self.tags: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Unbalanced pushed tag: '{}'\".format(tag), None)) # If the user left some metadata unpopped, issue an error. for key, value_list in self.meta.items(): meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, ( \"Unbalanced metadata key '{}'; leftover metadata '{}'\").format( key, ', '.join(value_list)), None)) # Weave the commas option in the DisplayContext itself, so it propagates # everywhere it is used automatically. self.dcontext.set_commas(self.options['render_commas']) return (self.get_entries(), self.errors, self.get_options()) beancount.parser.grammar.Builder.finalize_tags_links(self, tags, links) \uf0c1 Finally amend tags and links and return final objects to be inserted. Parameters: tags \u2013 A set of tag strings (warning: this gets mutated in-place). links \u2013 A set of link strings. Returns: A sanitized pair of (tags, links). Source code in beancount/parser/grammar.py def finalize_tags_links(self, tags, links): \"\"\"Finally amend tags and links and return final objects to be inserted. Args: tags: A set of tag strings (warning: this gets mutated in-place). links: A set of link strings. Returns: A sanitized pair of (tags, links). \"\"\" if self.tags: tags.update(self.tags) return (frozenset(tags) if tags else EMPTY_SET, frozenset(links) if links else EMPTY_SET) beancount.parser.grammar.Builder.get_entries(self) \uf0c1 Return the accumulated entries. Returns: A list of sorted directives. Source code in beancount/parser/grammar.py def get_entries(self): \"\"\"Return the accumulated entries. Returns: A list of sorted directives. \"\"\" return sorted(self.entries, key=data.entry_sortkey) beancount.parser.grammar.Builder.get_invalid_account(self) \uf0c1 See base class. Source code in beancount/parser/grammar.py def get_invalid_account(self): \"\"\"See base class.\"\"\" return account.join(self.options['name_equity'], 'InvalidAccountName') beancount.parser.grammar.Builder.get_long_string_maxlines(self) \uf0c1 See base class. Source code in beancount/parser/grammar.py def get_long_string_maxlines(self): \"\"\"See base class.\"\"\" return self.options['long_string_maxlines'] beancount.parser.grammar.Builder.get_options(self) \uf0c1 Return the final options map. Returns: A dict of option names to options. Source code in beancount/parser/grammar.py def get_options(self): \"\"\"Return the final options map. Returns: A dict of option names to options. \"\"\" # Build and store the inferred DisplayContext instance. self.options['dcontext'] = self.dcontext # Add the full list of seen commodities. # # IMPORTANT: This is currently where the list of all commodities seen # from the parser lives. The # beancount.core.getters.get_commodities_map() routine uses this to # automatically generate a full list of directives. An alternative would # be to implement a plugin that enforces the generate of these # post-parsing so that they are always guaranteed to live within the # flow of entries. This would allow us to keep all the data in that list # of entries and to avoid depending on the options to store that output. self.options['commodities'] = self.commodities return self.options beancount.parser.grammar.Builder.handle_list(self, object_list, new_object) \uf0c1 Handle a recursive list grammar rule, generically. Parameters: object_list \u2013 the current list of objects. new_object \u2013 the new object to be added. Returns: The new, updated list of objects. Source code in beancount/parser/grammar.py def handle_list(self, object_list, new_object): \"\"\"Handle a recursive list grammar rule, generically. Args: object_list: the current list of objects. new_object: the new object to be added. Returns: The new, updated list of objects. \"\"\" if object_list is None: object_list = [] if new_object is not None: object_list.append(new_object) return object_list beancount.parser.grammar.Builder.include(self, filename, lineno, include_filename) \uf0c1 Process an include directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. include_name \u2013 A string, the name of the file to include. Source code in beancount/parser/grammar.py def include(self, filename, lineno, include_filename): \"\"\"Process an include directive. Args: filename: current filename. lineno: current line number. include_name: A string, the name of the file to include. \"\"\" self.options['include'].append(include_filename) beancount.parser.grammar.Builder.key_value(self, key, value) \uf0c1 Process a document directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account the document relates to. document_filename \u2013 A str, the name of the document file. Returns: A new KeyValue object. Source code in beancount/parser/grammar.py def key_value(self, key, value): \"\"\"Process a document directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account the document relates to. document_filename: A str, the name of the document file. Returns: A new KeyValue object. \"\"\" return KeyValue(key, value) beancount.parser.grammar.Builder.note(self, filename, lineno, date, account, comment, kvlist) \uf0c1 Process a note directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to attach the note to. comment \u2013 A str, the note's comments contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Note object. Source code in beancount/parser/grammar.py def note(self, filename, lineno, date, account, comment, kvlist): \"\"\"Process a note directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to attach the note to. comment: A str, the note's comments contents. kvlist: a list of KeyValue instances. Returns: A new Note object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Note(meta, date, account, comment) beancount.parser.grammar.Builder.open(self, filename, lineno, date, account, currencies, booking_str, kvlist) \uf0c1 Process an open directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. currencies \u2013 A list of constraint currencies. booking_str \u2013 A string, the booking method, or None if none was specified. kvlist \u2013 a list of KeyValue instances. Returns: A new Open object. Source code in beancount/parser/grammar.py def open(self, filename, lineno, date, account, currencies, booking_str, kvlist): \"\"\"Process an open directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. currencies: A list of constraint currencies. booking_str: A string, the booking method, or None if none was specified. kvlist: a list of KeyValue instances. Returns: A new Open object. \"\"\" meta = new_metadata(filename, lineno, kvlist) error = False if booking_str: try: # Note: Somehow the 'in' membership operator is not defined on Enum. booking = Booking[booking_str] except KeyError: # If the per-account method is invalid, set it to the global # default method and continue. booking = self.options['booking_method'] error = True else: booking = None entry = Open(meta, date, account, currencies, booking) if error: self.errors.append(ParserError(meta, \"Invalid booking method: {}\".format(booking_str), entry)) return entry beancount.parser.grammar.Builder.option(self, filename, lineno, key, value) \uf0c1 Process an option directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. key \u2013 option's key (str) value \u2013 option's value Source code in beancount/parser/grammar.py def option(self, filename, lineno, key, value): \"\"\"Process an option directive. Args: filename: current filename. lineno: current line number. key: option's key (str) value: option's value \"\"\" if key not in self.options: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Invalid option: '{}'\".format(key), None)) elif key in options.READ_ONLY_OPTIONS: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Option '{}' may not be set\".format(key), None)) else: option_descriptor = options.OPTIONS[key] # Issue a warning if the option is deprecated. if option_descriptor.deprecated: assert isinstance(option_descriptor.deprecated, str), \"Internal error.\" meta = new_metadata(filename, lineno) self.errors.append( DeprecatedError(meta, option_descriptor.deprecated, None)) # Rename the option if it has an alias. if option_descriptor.alias: key = option_descriptor.alias option_descriptor = options.OPTIONS[key] # Convert the value, if necessary. if option_descriptor.converter: try: value = option_descriptor.converter(value) except ValueError as exc: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Error for option '{}': {}\".format(key, exc), None)) return option = self.options[key] if isinstance(option, list): # Append to a list of values. option.append(value) elif isinstance(option, dict): # Set to a dict of values. if not (isinstance(value, tuple) and len(value) == 2): self.errors.append( ParserError( meta, \"Error for option '{}': {}\".format(key, value), None)) return dict_key, dict_value = value option[dict_key] = dict_value elif isinstance(option, bool): # Convert to a boolean. if not isinstance(value, bool): value = (value.lower() in {'true', 'on'}) or (value == '1') self.options[key] = value else: # Set the value. self.options[key] = value # Refresh the list of valid account regexps as we go along. if key.startswith('name_'): # Update the set of valid account types. self.account_regexp = valid_account_regexp(self.options) elif key == 'insert_pythonpath': # Insert the PYTHONPATH to this file when and only if you # encounter this option. sys.path.insert(0, path.dirname(filename)) beancount.parser.grammar.Builder.pad(self, filename, lineno, date, account, source_account, kvlist) \uf0c1 Process a pad directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to be padded. source_account \u2013 A string, the account to pad from. kvlist \u2013 a list of KeyValue instances. Returns: A new Pad object. Source code in beancount/parser/grammar.py def pad(self, filename, lineno, date, account, source_account, kvlist): \"\"\"Process a pad directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to be padded. source_account: A string, the account to pad from. kvlist: a list of KeyValue instances. Returns: A new Pad object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Pad(meta, date, account, source_account) beancount.parser.grammar.Builder.pipe_deprecated_error(self, filename, lineno) \uf0c1 Issue a 'Pipe deprecated' error. Parameters: filename \u2013 The current filename lineno \u2013 The current line number Source code in beancount/parser/grammar.py def pipe_deprecated_error(self, filename, lineno): \"\"\"Issue a 'Pipe deprecated' error. Args: filename: The current filename lineno: The current line number \"\"\" if self.options['allow_pipe_separator']: return meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, \"Pipe symbol is deprecated.\", None)) beancount.parser.grammar.Builder.plugin(self, filename, lineno, plugin_name, plugin_config) \uf0c1 Process a plugin directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. plugin_name \u2013 A string, the name of the plugin module to import. plugin_config \u2013 A string or None, an optional configuration string to pass in to the plugin module. Source code in beancount/parser/grammar.py def plugin(self, filename, lineno, plugin_name, plugin_config): \"\"\"Process a plugin directive. Args: filename: current filename. lineno: current line number. plugin_name: A string, the name of the plugin module to import. plugin_config: A string or None, an optional configuration string to pass in to the plugin module. \"\"\" self.options['plugin'].append((plugin_name, plugin_config)) beancount.parser.grammar.Builder.popmeta(self, key) \uf0c1 Removed a key off the current set of stacks. Parameters: key \u2013 A string, a key to be removed from the meta dict. Source code in beancount/parser/grammar.py def popmeta(self, key): \"\"\"Removed a key off the current set of stacks. Args: key: A string, a key to be removed from the meta dict. \"\"\" try: if key not in self.meta: raise IndexError value_list = self.meta[key] value_list.pop(-1) if not value_list: self.meta.pop(key) except IndexError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent metadata key: '{}'\".format(key), None)) beancount.parser.grammar.Builder.poptag(self, tag) \uf0c1 Pop a tag off the current set of stacks. Parameters: tag \u2013 A string, a tag to be removed from the current set of tags. Source code in beancount/parser/grammar.py def poptag(self, tag): \"\"\"Pop a tag off the current set of stacks. Args: tag: A string, a tag to be removed from the current set of tags. \"\"\" try: self.tags.remove(tag) except ValueError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent tag: '{}'\".format(tag), None)) beancount.parser.grammar.Builder.posting(self, filename, lineno, account, units, cost, price, istotal, flag) \uf0c1 Process a posting grammar rule. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. account \u2013 A string, the account of the posting. units \u2013 An instance of Amount for the units. cost \u2013 An instance of CostSpec for the cost. price \u2013 Either None, or an instance of Amount that is the cost of the position. istotal \u2013 A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag \u2013 A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. Source code in beancount/parser/grammar.py def posting(self, filename, lineno, account, units, cost, price, istotal, flag): \"\"\"Process a posting grammar rule. Args: filename: the current filename. lineno: the current line number. account: A string, the account of the posting. units: An instance of Amount for the units. cost: An instance of CostSpec for the cost. price: Either None, or an instance of Amount that is the cost of the position. istotal: A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag: A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. \"\"\" meta = new_metadata(filename, lineno) # Prices may not be negative. if price and isinstance(price.number, Decimal) and price.number < ZERO: self.errors.append( ParserError(meta, ( \"Negative prices are not allowed: {} \" \"(see http://furius.ca/beancount/doc/bug-negative-prices \" \"for workaround)\" ).format(price), None)) # Fix it and continue. price = Amount(abs(price.number), price.currency) # If the price is specified for the entire amount, compute the effective # price here and forget about that detail of the input syntax. if istotal: if units.number == ZERO: number = ZERO else: number = price.number if number is not MISSING: number = number/abs(units.number) price = Amount(number, price.currency) # Note: Allow zero prices because we need them for round-trips for # conversion entries. # # if price is not None and price.number == ZERO: # self.errors.append( # ParserError(meta, \"Price is zero: {}\".format(price), None)) # If both cost and price are specified, the currencies must match, or # that is an error. if (cost is not None and price is not None and isinstance(cost.currency, str) and isinstance(price.currency, str) and cost.currency != price.currency): self.errors.append( ParserError(meta, \"Cost and price currencies must match: {} != {}\".format( cost.currency, price.currency), None)) return Posting(account, units, cost, price, chr(flag) if flag else None, meta) beancount.parser.grammar.Builder.price(self, filename, lineno, date, currency, amount, kvlist) \uf0c1 Process a price directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. currency \u2013 the currency to be priced. amount \u2013 an instance of Amount, that is the price of the currency. kvlist \u2013 a list of KeyValue instances. Returns: A new Price object. Source code in beancount/parser/grammar.py def price(self, filename, lineno, date, currency, amount, kvlist): \"\"\"Process a price directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. currency: the currency to be priced. amount: an instance of Amount, that is the price of the currency. kvlist: a list of KeyValue instances. Returns: A new Price object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Price(meta, date, currency, amount) beancount.parser.grammar.Builder.pushmeta(self, key, value) \uf0c1 Set a metadata field on the current key-value pairs to be added to transactions. Parameters: key_value \u2013 A KeyValue instance, to be added to the dict of metadata. Source code in beancount/parser/grammar.py def pushmeta(self, key, value): \"\"\"Set a metadata field on the current key-value pairs to be added to transactions. Args: key_value: A KeyValue instance, to be added to the dict of metadata. \"\"\" self.meta[key].append(value) beancount.parser.grammar.Builder.pushtag(self, tag) \uf0c1 Push a tag on the current set of tags. Note that this does not need to be stack ordered. Parameters: tag \u2013 A string, a tag to be added. Source code in beancount/parser/grammar.py def pushtag(self, tag): \"\"\"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Args: tag: A string, a tag to be added. \"\"\" self.tags.append(tag) beancount.parser.grammar.Builder.query(self, filename, lineno, date, query_name, query_string, kvlist) \uf0c1 Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. query_name \u2013 a str, the name of the query. query_string \u2013 a str, the SQL query itself. kvlist \u2013 a list of KeyValue instances. Returns: A new Query object. Source code in beancount/parser/grammar.py def query(self, filename, lineno, date, query_name, query_string, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. query_name: a str, the name of the query. query_string: a str, the SQL query itself. kvlist: a list of KeyValue instances. Returns: A new Query object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Query(meta, date, query_name, query_string) beancount.parser.grammar.Builder.store_result(self, entries) \uf0c1 Start rule stores the final result here. Parameters: entries \u2013 A list of entries to store. Source code in beancount/parser/grammar.py def store_result(self, entries): \"\"\"Start rule stores the final result here. Args: entries: A list of entries to store. \"\"\" if entries: self.entries = entries beancount.parser.grammar.Builder.tag_link_LINK(self, tags_links, link) \uf0c1 Add a link to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. link \u2013 A string, the new link to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_LINK(self, tags_links, link): \"\"\"Add a link to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. link: A string, the new link to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.links.add(link) return tags_links beancount.parser.grammar.Builder.tag_link_STRING(self, tags_links, string) \uf0c1 Add a string to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. string \u2013 A string, the new string to insert in the list. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_STRING(self, tags_links, string): \"\"\"Add a string to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. string: A string, the new string to insert in the list. Returns: An updated TagsLinks instance. \"\"\" tags_links.strings.append(string) return tags_links beancount.parser.grammar.Builder.tag_link_TAG(self, tags_links, tag) \uf0c1 Add a tag to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. tag \u2013 A string, the new tag to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_TAG(self, tags_links, tag): \"\"\"Add a tag to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. tag: A string, the new tag to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.tags.add(tag) return tags_links beancount.parser.grammar.Builder.tag_link_new(self, _) \uf0c1 Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. Source code in beancount/parser/grammar.py def tag_link_new(self, _): \"\"\"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. \"\"\" return TagsLinks(set(), set()) beancount.parser.grammar.Builder.transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list) \uf0c1 Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. flag \u2013 a str, one-character, the flag associated with this transaction. txn_strings \u2013 A list of strings, possibly empty, possibly longer. tags_links \u2013 A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list \u2013 a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. Source code in beancount/parser/grammar.py def transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list): \"\"\"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Args: filename: the current filename. lineno: the current line number. date: a datetime object. flag: a str, one-character, the flag associated with this transaction. txn_strings: A list of strings, possibly empty, possibly longer. tags_links: A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list: a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. \"\"\" meta = new_metadata(filename, lineno) # Separate postings and key-values. explicit_meta = {} postings = [] tags, links = tags_links.tags, tags_links.links if posting_or_kv_list: last_posting = None for posting_or_kv in posting_or_kv_list: if isinstance(posting_or_kv, Posting): postings.append(posting_or_kv) last_posting = posting_or_kv elif isinstance(posting_or_kv, TagsLinks): if postings: self.errors.append(ParserError( meta, \"Tags or links not allowed after first \" + \"Posting: {}\".format(posting_or_kv), None)) else: tags.update(posting_or_kv.tags) links.update(posting_or_kv.links) else: if last_posting is None: value = explicit_meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate metadata field on entry: {}\".format( posting_or_kv), None)) else: if last_posting.meta is None: last_posting = last_posting._replace(meta={}) postings.pop(-1) postings.append(last_posting) value = last_posting.meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate posting metadata field: {}\".format( posting_or_kv), None)) # Freeze the tags & links or set to default empty values. tags, links = self.finalize_tags_links(tags, links) # Initialize the metadata fields from the set of active values. if self.meta: for key, value_list in self.meta.items(): meta[key] = value_list[-1] # Add on explicitly defined values. if explicit_meta: meta.update(explicit_meta) # Unpack the transaction fields. payee_narration = self.unpack_txn_strings(txn_strings, meta) if payee_narration is None: return None payee, narration = payee_narration # We now allow a single posting when its balance is zero, so we # commented out the check below. If a transaction has a single posting # with a non-zero balance, it'll get caught below in the booking code. # # # Detect when a transaction does not have at least two legs. # if postings is None or len(postings) < 2: # self.errors.append( # ParserError(meta, # \"Transaction with only one posting: {}\".format(postings), # None)) # return None # If there are no postings, make sure we insert a list object. if postings is None: postings = [] # Create the transaction. return Transaction(meta, date, chr(flag), payee, narration, tags, links, postings) beancount.parser.grammar.Builder.unpack_txn_strings(self, txn_strings, meta) \uf0c1 Unpack a tags_links accumulator to its payee and narration fields. Parameters: txn_strings \u2013 A list of strings. meta \u2013 A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. Source code in beancount/parser/grammar.py def unpack_txn_strings(self, txn_strings, meta): \"\"\"Unpack a tags_links accumulator to its payee and narration fields. Args: txn_strings: A list of strings. meta: A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. \"\"\" num_strings = 0 if txn_strings is None else len(txn_strings) if num_strings == 1: payee, narration = None, txn_strings[0] elif num_strings == 2: payee, narration = txn_strings elif num_strings == 0: payee, narration = None, \"\" else: self.errors.append( ParserError(meta, \"Too many strings on transaction description: {}\".format( txn_strings), None)) return None return payee, narration beancount.parser.grammar.CompoundAmount ( tuple ) \uf0c1 CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.CompoundAmount.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.CompoundAmount.__new__(_cls, number_per, number_total, currency) special staticmethod \uf0c1 Create new instance of CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.CompoundAmount.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.DeprecatedError ( tuple ) \uf0c1 DeprecatedError(source, message, entry) beancount.parser.grammar.DeprecatedError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.DeprecatedError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of DeprecatedError(source, message, entry) beancount.parser.grammar.DeprecatedError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.KeyValue ( tuple ) \uf0c1 KeyValue(key, value) beancount.parser.grammar.KeyValue.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.KeyValue.__new__(_cls, key, value) special staticmethod \uf0c1 Create new instance of KeyValue(key, value) beancount.parser.grammar.KeyValue.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ParserError ( tuple ) \uf0c1 ParserError(source, message, entry) beancount.parser.grammar.ParserError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ParserError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ParserError(source, message, entry) beancount.parser.grammar.ParserError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ParserSyntaxError ( tuple ) \uf0c1 ParserSyntaxError(source, message, entry) beancount.parser.grammar.ParserSyntaxError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ParserSyntaxError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ParserSyntaxError(source, message, entry) beancount.parser.grammar.ParserSyntaxError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.TagsLinks ( tuple ) \uf0c1 TagsLinks(tags, links) beancount.parser.grammar.TagsLinks.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.TagsLinks.__new__(_cls, tags, links) special staticmethod \uf0c1 Create new instance of TagsLinks(tags, links) beancount.parser.grammar.TagsLinks.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ValueType ( tuple ) \uf0c1 ValueType(value, dtype) beancount.parser.grammar.ValueType.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ValueType.__new__(_cls, value, dtype) special staticmethod \uf0c1 Create new instance of ValueType(value, dtype) beancount.parser.grammar.ValueType.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.valid_account_regexp(options) \uf0c1 Build a regexp to validate account names from the options. Parameters: options \u2013 A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. Source code in beancount/parser/grammar.py def valid_account_regexp(options): \"\"\"Build a regexp to validate account names from the options. Args: options: A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. \"\"\" names = map(options.__getitem__, ('name_assets', 'name_liabilities', 'name_equity', 'name_income', 'name_expenses')) # Replace the first term of the account regular expression with the specific # names allowed under the options configuration. This code is kept in sync # with {5672c7270e1e}. return re.compile(\"(?:{})(?:{}{})+\".format('|'.join(names), account.sep, account.ACC_COMP_NAME_RE)) beancount.parser.hashsrc \uf0c1 Compute a hash of the source files in order to warn when the source goes out of date. beancount.parser.hashsrc.check_parser_source_files() \uf0c1 Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). Source code in beancount/parser/hashsrc.py def check_parser_source_files(): \"\"\"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). \"\"\" parser_source_hash = hash_parser_source_files() if parser_source_hash is None: return # pylint: disable=import-outside-toplevel from . import _parser if _parser.SOURCE_HASH and _parser.SOURCE_HASH != parser_source_hash: warnings.warn( (\"The Beancount parser C extension module is out-of-date ('{}' != '{}'). \" \"You need to rebuild.\").format(_parser.SOURCE_HASH, parser_source_hash)) beancount.parser.hashsrc.hash_parser_source_files() \uf0c1 Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. Source code in beancount/parser/hashsrc.py def hash_parser_source_files(): \"\"\"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. \"\"\" md5 = hashlib.md5() for filename in PARSER_SOURCE_FILES: fullname = path.join(path.dirname(__file__), filename) if not path.exists(fullname): return None with open(fullname, 'rb') as file: md5.update(file.read()) # Note: Prepend a character in front of the hash because under Windows MSDEV # removes escapes, and if the hash starts with a number it fails to # recognize this is a string. A small compromise for portability. return md5.hexdigest() beancount.parser.lexer \uf0c1 Beancount syntax lexer. beancount.parser.lexer.LexBuilder \uf0c1 A builder used only for building lexer objects. Attributes: Name Type Description long_string_maxlines_default Number of lines for a string to trigger a warning. This is meant to help users detecting dangling quotes in their source. beancount.parser.lexer.LexBuilder.ACCOUNT(self, account_name) \uf0c1 Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Parameters: account_name \u2013 a str, the valid name of an account. Returns: A string, the name of the account. Source code in beancount/parser/lexer.py def ACCOUNT(self, account_name): \"\"\"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Args: account_name: a str, the valid name of an account. Returns: A string, the name of the account. \"\"\" # Check account name validity. if not self.account_regexp.match(account_name): raise ValueError(\"Invalid account name: {}\".format(account_name)) # Reuse (intern) account strings as much as possible. This potentially # reduces memory usage a fair bit, because these strings are repeated # liberally. return self.accounts.setdefault(account_name, account_name) beancount.parser.lexer.LexBuilder.CURRENCY(self, currency_name) \uf0c1 Process a CURRENCY token. Parameters: currency_name \u2013 the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. Source code in beancount/parser/lexer.py def CURRENCY(self, currency_name): \"\"\"Process a CURRENCY token. Args: currency_name: the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. \"\"\" self.commodities.add(currency_name) return currency_name beancount.parser.lexer.LexBuilder.DATE(self, year, month, day) \uf0c1 Process a DATE token. Parameters: year \u2013 integer year. month \u2013 integer month. day \u2013 integer day Returns: A new datetime object. Source code in beancount/parser/lexer.py def DATE(self, year, month, day): \"\"\"Process a DATE token. Args: year: integer year. month: integer month. day: integer day Returns: A new datetime object. \"\"\" return datetime.date(year, month, day) beancount.parser.lexer.LexBuilder.KEY(self, ident) \uf0c1 Process an identifier token. Parameters: ident \u2013 a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def KEY(self, ident): \"\"\"Process an identifier token. Args: ident: a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return ident beancount.parser.lexer.LexBuilder.LINK(self, link) \uf0c1 Process a LINK token. Parameters: link \u2013 a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def LINK(self, link): \"\"\"Process a LINK token. Args: link: a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return link beancount.parser.lexer.LexBuilder.NUMBER(self, number) \uf0c1 Process a NUMBER token. Convert into Decimal. Parameters: number \u2013 a str, the number to be converted. Returns: A Decimal instance built of the number string. Source code in beancount/parser/lexer.py def NUMBER(self, number): \"\"\"Process a NUMBER token. Convert into Decimal. Args: number: a str, the number to be converted. Returns: A Decimal instance built of the number string. \"\"\" # Note: We don't use D() for efficiency here. # The lexer will only yield valid number strings. if ',' in number: # Extract the integer part and check the commas match the # locale-aware formatted version. This match = re.match(r\"([\\d,]*)(\\.\\d*)?$\", number) if not match: # This path is never taken because the lexer will parse a comma # in the fractional part as two NUMBERs with a COMMA token in # between. self.errors.append( LexerError(self.get_lexer_location(), \"Invalid number format: '{}'\".format(number), None)) else: int_string, float_string = match.groups() reformatted_number = r\"{:,.0f}\".format(int(int_string.replace(\",\", \"\"))) if int_string != reformatted_number: self.errors.append( LexerError(self.get_lexer_location(), \"Invalid commas: '{}'\".format(number), None)) number = number.replace(',', '') return Decimal(number) beancount.parser.lexer.LexBuilder.STRING(self, string) \uf0c1 Process a STRING token. Parameters: string \u2013 the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. Source code in beancount/parser/lexer.py def STRING(self, string): \"\"\"Process a STRING token. Args: string: the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. \"\"\" # If a multiline string, warm over a certain number of lines. if '\\n' in string: num_lines = string.count('\\n') + 1 if num_lines > self.long_string_maxlines_default: # This is just a warning; accept the string anyhow. self.errors.append( LexerError( self.get_lexer_location(), \"String too long ({} lines); possible error\".format(num_lines), None)) return string beancount.parser.lexer.LexBuilder.TAG(self, tag) \uf0c1 Process a TAG token. Parameters: tag \u2013 a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. Source code in beancount/parser/lexer.py def TAG(self, tag): \"\"\"Process a TAG token. Args: tag: a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. \"\"\" return tag beancount.parser.lexer.LexBuilder.build_lexer_error(self, message, exc_type=None) \uf0c1 Build a lexer error and appends it to the list of pending errors. Parameters: message \u2013 The message of the error. exc_type \u2013 An exception type, if an exception occurred. Source code in beancount/parser/lexer.py def build_lexer_error(self, message, exc_type=None): # {0e31aeca3363} \"\"\"Build a lexer error and appends it to the list of pending errors. Args: message: The message of the error. exc_type: An exception type, if an exception occurred. \"\"\" if not isinstance(message, str): message = str(message) if exc_type is not None: message = '{}: {}'.format(exc_type.__name__, message) self.errors.append( LexerError(self.get_lexer_location(), message, None)) beancount.parser.lexer.LexBuilder.get_invalid_account(self) \uf0c1 Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. Source code in beancount/parser/lexer.py def get_invalid_account(self): \"\"\"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. \"\"\" return 'Equity:InvalidAccountName' beancount.parser.lexer.LexerError ( tuple ) \uf0c1 LexerError(source, message, entry) beancount.parser.lexer.LexerError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/lexer.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.lexer.LexerError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LexerError(source, message, entry) beancount.parser.lexer.LexerError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/lexer.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.lexer.lex_iter(file, builder=None, encoding=None) \uf0c1 An iterator that yields all the tokens in the given file. Parameters: file \u2013 A string, the filename to run the lexer on, or a file object. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). Source code in beancount/parser/lexer.py def lex_iter(file, builder=None, encoding=None): \"\"\"An iterator that yields all the tokens in the given file. Args: file: A string, the filename to run the lexer on, or a file object. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). \"\"\" if isinstance(file, str): filename = file else: filename = file.name if builder is None: builder = LexBuilder() _parser.lexer_initialize(filename, builder, encoding) try: while 1: token_tuple = _parser.lexer_next() if token_tuple is None: break yield token_tuple finally: _parser.lexer_finalize() beancount.parser.lexer.lex_iter_string(string, builder=None, encoding=None) \uf0c1 Parse an input string and print the tokens to an output file. Parameters: input_string \u2013 a str or bytes, the contents of the ledger to be parsed. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. Source code in beancount/parser/lexer.py def lex_iter_string(string, builder=None, encoding=None): \"\"\"Parse an input string and print the tokens to an output file. Args: input_string: a str or bytes, the contents of the ledger to be parsed. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. \"\"\" tmp_file = tempfile.NamedTemporaryFile('w' if isinstance(string, str) else 'wb') tmp_file.write(string) tmp_file.flush() # Note: We pass in the file object in order to keep it alive during parsing. return lex_iter(tmp_file, builder, encoding) beancount.parser.options \uf0c1 Declaration of options and their default values. beancount.parser.options.OptDesc ( tuple ) \uf0c1 OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.OptDesc.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.options.OptDesc.__new__(_cls, name, default_value, example_value, converter, deprecated, alias) special staticmethod \uf0c1 Create new instance of OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.OptDesc.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.options.OptGroup ( tuple ) \uf0c1 OptGroup(description, options) beancount.parser.options.OptGroup.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.options.OptGroup.__new__(_cls, description, options) special staticmethod \uf0c1 Create new instance of OptGroup(description, options) beancount.parser.options.OptGroup.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.options.Opt(name, default_value, example_value=, converter=None, deprecated=False, alias=None) \uf0c1 Alternative constructor for OptDesc, with default values. Parameters: name \u2013 See OptDesc. default_value \u2013 See OptDesc. example_value \u2013 See OptDesc. converter \u2013 See OptDesc. deprecated \u2013 See OptDesc. alias \u2013 See OptDesc. Returns: An instance of OptDesc. Source code in beancount/parser/options.py def Opt(name, default_value, example_value=UNSET, converter=None, deprecated=False, alias=None): \"\"\"Alternative constructor for OptDesc, with default values. Args: name: See OptDesc. default_value: See OptDesc. example_value: See OptDesc. converter: See OptDesc. deprecated: See OptDesc. alias: See OptDesc. Returns: An instance of OptDesc. \"\"\" if example_value is UNSET: example_value = default_value return OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.get_account_types(options) \uf0c1 Extract the account type names from the parser's options. Parameters: options \u2013 a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. Source code in beancount/parser/options.py def get_account_types(options): \"\"\"Extract the account type names from the parser's options. Args: options: a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. \"\"\" return account_types.AccountTypes( *[options[key] for key in (\"name_assets\", \"name_liabilities\", \"name_equity\", \"name_income\", \"name_expenses\")]) beancount.parser.options.get_current_accounts(options) \uf0c1 Return account names for the current earnings and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. Source code in beancount/parser/options.py def get_current_accounts(options): \"\"\"Return account names for the current earnings and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. \"\"\" equity = options['name_equity'] account_current_earnings = account.join(equity, options['account_current_earnings']) account_current_conversions = account.join(equity, options['account_current_conversions']) return (account_current_earnings, account_current_conversions) beancount.parser.options.get_previous_accounts(options) \uf0c1 Return account names for the previous earnings, balances and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. Source code in beancount/parser/options.py def get_previous_accounts(options): \"\"\"Return account names for the previous earnings, balances and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. \"\"\" equity = options['name_equity'] account_previous_earnings = account.join(equity, options['account_previous_earnings']) account_previous_balances = account.join(equity, options['account_previous_balances']) account_previous_conversions = account.join(equity, options['account_previous_conversions']) return (account_previous_earnings, account_previous_balances, account_previous_conversions) beancount.parser.options.list_options() \uf0c1 Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. Source code in beancount/parser/options.py def list_options(): \"\"\"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. \"\"\" oss = io.StringIO() for group in PUBLIC_OPTION_GROUPS: for desc in group.options: oss.write('option \"{}\" \"{}\"\\n'.format(desc.name, desc.example_value)) if desc.deprecated: oss.write(textwrap.fill( \"THIS OPTION IS DEPRECATED: {}\".format(desc.deprecated), initial_indent=\" \", subsequent_indent=\" \")) oss.write('\\n\\n') description = ' '.join(line.strip() for line in group.description.strip().splitlines()) oss.write(textwrap.fill(description, initial_indent=' ', subsequent_indent=' ')) oss.write('\\n') if isinstance(desc.default_value, (list, dict, set)): oss.write('\\n') oss.write(' (This option may be supplied multiple times.)\\n') oss.write('\\n\\n') return oss.getvalue() beancount.parser.options.options_validate_booking_method(value) \uf0c1 Validate a booking method name. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_booking_method(value): \"\"\"Validate a booking method name. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" try: return data.Booking[value] except KeyError as exc: raise ValueError(str(exc)) beancount.parser.options.options_validate_boolean(value) \uf0c1 Validate a boolean option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_boolean(value): \"\"\"Validate a boolean option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return value.lower() in ('1', 'true', 'yes') beancount.parser.options.options_validate_plugin(value) \uf0c1 Validate the plugin option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_plugin(value): \"\"\"Validate the plugin option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the 'plugin' option specially: accept an optional # argument from it. NOTE: We will eventually phase this out and # replace it by a dedicated 'plugin' directive. match = re.match('(.*):(.*)', value) if match: plugin_name, plugin_config = match.groups() else: plugin_name, plugin_config = value, None return (plugin_name, plugin_config) beancount.parser.options.options_validate_processing_mode(value) \uf0c1 Validate the options processing mode. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_processing_mode(value): \"\"\"Validate the options processing mode. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" if value not in ('raw', 'default'): raise ValueError(\"Invalid value '{}'\".format(value)) return value beancount.parser.options.options_validate_tolerance(value) \uf0c1 Validate the tolerance option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance(value): \"\"\"Validate the tolerance option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return D(value) beancount.parser.options.options_validate_tolerance_map(value) \uf0c1 Validate an option with a map of currency/tolerance pairs in a string. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance_map(value): \"\"\"Validate an option with a map of currency/tolerance pairs in a string. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the setting of a key-value, whereby the value is a Decimal # representation. match = re.match('(.*):(.*)', value) if not match: raise ValueError(\"Invalid value '{}'\".format(value)) currency, tolerance_str = match.groups() return (currency, D(tolerance_str)) beancount.parser.parser \uf0c1 Beancount syntax parser. IMPORTANT: The parser (and its grammar builder) produces \"incomplete\" Transaction objects. This means that some of the data can be found missing from the output of the parser and some of the data types vary slightly. Missing components are replaced not by None, but by a special constant 'NA' which helps diagnose problems if a user inadvertently attempts to work on an incomplete posting instead of a complete one. Those incomplete entries are then run through the \"booking\" routines which do two things simultaneously: They find matching lots for reducing inventory positions, and They interpolate missing numbers. In doing so they normalize the entries to \"complete\" entries by converting a position/lot's \"cost\" attribute from a CostSpec to a Cost. A Cost is similar to an Amount in that it shares \"number\" and \"currency\" attributes, but also has a label and a lot date. A CostSpec is similar to a Cost, but has all optional data; it consists in a specification for matching against a particular inventory lot. Other parts of a posting may also be missing, not just parts of the cost. Leaving out some parts of the input is used to invoke interpolation, to tell Beancount to automatically compute the missing numbers (if possible). Missing components will be set to the special value \"beancount.core.number.MISSING\" until inventory booking and number interpolation has been completed. The \"MISSING\" value should never appear in completed, loaded transaction postings. For instance, all of the units may be missing: INPUT: Assets:Account posting.units = MISSING Or just the number of the units: INPUT: Assets:Account USD posting.units = Amount(MISSING, \"USD\") You must always specify the currency. If a price annotation is simply absent, it appears as None: INPUT: Assets:Account 2 MXN posting.price = None However, you may indicate that there is a price but have Beancount compute it automatically: INPUT: Assets:Account 2 MXN @ posting.price = Amount(MISSING, MISSING) Indicating the conversion currency is also possible (and recommended): INPUT: Assets:Account 2 MXN @ USD posting.price = Amount(MISSING, \"USD\") If a cost specification is provided, a \"cost\" attribute it set but it does not refer to a Cost instance (as in complete entries) but rather to a CostSpec instance. Some of the fields of a CostSpec may be MISSING if they were not specified in the input. For example: INPUT: Assets:Account 1 HOOL {100 # 5 USD} posting.cost = CostSpec(Decimal(\"100\"), Decimal(\"5\"), \"USD\", None, None, False)) Note how we never consider the label of date override to be MISSING; this is because those inputs are optional: A missing label is simply left unset in the computed Cost, and a missing date override uses the date of the transaction that contains the posting. You can indicate that there is a total number to be filled in like this: INPUT: Assets:Account 1 HOOL {100 # USD} posting.cost = CostSpec(Decimal(\"100\"), MISSING, \"USD\", None, None, False)) This is in contrast to the total value simple not being used: INPUT: Assets:Account 1 HOOL {100 USD} posting.cost = CostSpec(Decimal(\"100\"), None, \"USD\", None, None, False)) Both per-unit and total numbers may be omitted as well, in which case, only the number-per-unit portion of the CostSpec will appear as MISSING: INPUT: Assets:Account 1 HOOL {USD} posting.cost = CostSpec(MISSING, None, \"USD\", None, None, False)) And furthermore, all the cost basis may be missing: INPUT: Assets:Account 1 HOOL {} posting.cost = CostSpec(MISSING, None, MISSING, None, None, False)) If you ask for the lots to be merged, you get this: INPUT: Assets:Account 1 HOOL {*} posting.cost = CostSpec(MISSING, None, MISSING, None, None, True)) The numbers have to be computed by Beancount, so we output this with MISSING values. Of course, you can provide only the non-basis information, like just the date or label: INPUT: Assets:Account 1 HOOL {2015-09-21} posting.cost = CostSpec(MISSING, None, MISSING, date(2015, 9, 21), None, True) See the test beancount.parser.grammar_test.TestIncompleteInputs for examples and corresponding expected values. beancount.parser.parser.is_entry_incomplete(entry) \uf0c1 Detect the presence of elided amounts in Transactions. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_entry_incomplete(entry): \"\"\"Detect the presence of elided amounts in Transactions. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" if isinstance(entry, data.Transaction): if any(is_posting_incomplete(posting) for posting in entry.postings): return True return False beancount.parser.parser.is_posting_incomplete(posting) \uf0c1 Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_posting_incomplete(posting): \"\"\"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" units = posting.units if (units is MISSING or units.number is MISSING or units.currency is MISSING): return True price = posting.price if (price is MISSING or price is not None and (price.number is MISSING or price.currency is MISSING)): return True cost = posting.cost if cost is not None and (cost.number_per is MISSING or cost.number_total is MISSING or cost.currency is MISSING): return True return False beancount.parser.parser.parse_doc(expect_errors=False, allow_incomplete=False) \uf0c1 Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete \u2013 A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. Source code in beancount/parser/parser.py def parse_doc(expect_errors=False, allow_incomplete=False): \"\"\"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete: A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: the function object to be decorated. Returns: A decorated test function. \"\"\" filename = inspect.getfile(fun) lines, lineno = inspect.getsourcelines(fun) # decorator line + function definition line (I realize this is largely # imperfect, but it's only for reporting in our tests) - empty first line # stripped away. lineno += 1 @functools.wraps(fun) def wrapper(self): assert fun.__doc__ is not None, ( \"You need to insert a docstring on {}\".format(fun.__name__)) entries, errors, options_map = parse_string(fun.__doc__, report_filename=filename, report_firstline=lineno, dedent=True) if not allow_incomplete and any(is_entry_incomplete(entry) for entry in entries): self.fail(\"parse_doc() may not use interpolation.\") if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator beancount.parser.parser.parse_file(filename, **kw) \uf0c1 Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: filename \u2013 the name of the file to be parsed. kw \u2013 a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) Source code in beancount/parser/parser.py def parse_file(filename, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: filename: the name of the file to be parsed. kw: a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) \"\"\" abs_filename = path.abspath(filename) if filename else None builder = grammar.Builder(abs_filename) _parser.parse_file(filename, builder, **kw) return builder.finalize() beancount.parser.parser.parse_many(string, level=0) \uf0c1 Parse a string with a snippet of Beancount input and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_many(string, level=0): \"\"\"Parse a string with a snippet of Beancount input and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" # Get the locals in the stack for the callers and produce the final text. frame = inspect.stack()[level+1] varkwds = frame[0].f_locals input_string = textwrap.dedent(string.format(**varkwds)) # Parse entries and check there are no errors. entries, errors, __ = parse_string(input_string) assert not errors return entries beancount.parser.parser.parse_one(string) \uf0c1 Parse a string with single Beancount directive and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_one(string): \"\"\"Parse a string with single Beancount directive and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" entries = parse_many(string, level=1) assert len(entries) == 1 return entries[0] beancount.parser.parser.parse_string(string, report_filename=None, **kw) \uf0c1 Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: string \u2013 A string, the contents to be parsed instead of a file's. report_filename \u2013 A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw \u2013 See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Returns: Same as the output of parse_file(). Source code in beancount/parser/parser.py def parse_string(string, report_filename=None, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: string: A string, the contents to be parsed instead of a file's. report_filename: A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw: See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Return: Same as the output of parse_file(). \"\"\" if kw.pop('dedent', None): string = textwrap.dedent(string) builder = grammar.Builder(report_filename or '') _parser.parse_string(string, builder, report_filename=report_filename, **kw) return builder.finalize() beancount.parser.printer \uf0c1 Conversion from internal data structures to text. beancount.parser.printer.EntryPrinter \uf0c1 A multi-method interface for printing all directive types. Attributes: Name Type Description dcontext An instance of DisplayContext with which to render all the numbers. render_weight A boolean, true if we should render the weight of the postings as a comment, for debugging. min_width_account An integer, the minimum width to leave for the account name. beancount.parser.printer.EntryPrinter.__call__(self, obj) special \uf0c1 Render a directive. Parameters: obj \u2013 The directive to be rendered. Returns: A string, the rendered directive. Source code in beancount/parser/printer.py def __call__(self, obj): \"\"\"Render a directive. Args: obj: The directive to be rendered. Returns: A string, the rendered directive. \"\"\" oss = io.StringIO() method = getattr(self, obj.__class__.__name__) method(obj, oss) return oss.getvalue() beancount.parser.printer.EntryPrinter.render_posting_strings(self, posting) \uf0c1 This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Parameters: posting \u2013 An instance of Posting, the posting to render. Returns: A tuple of flag_account \u2013 A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. Source code in beancount/parser/printer.py def render_posting_strings(self, posting): \"\"\"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Args: posting: An instance of Posting, the posting to render. Returns: A tuple of flag_account: A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. \"\"\" # Render a string of the flag and the account. flag = '{} '.format(posting.flag) if posting.flag else '' flag_account = flag + posting.account # Render a string with the amount and cost and optional price, if # present. Also render a string with the weight. weight_str = '' if isinstance(posting.units, amount.Amount): position_str = position.to_string(posting, self.dformat) # Note: we render weights at maximum precision, for debugging. if posting.cost is None or (isinstance(posting.cost, position.Cost) and isinstance(posting.cost.number, Decimal)): weight_str = str(convert.get_weight(posting)) else: position_str = '' if posting.price is not None: position_str += ' @ {}'.format(posting.price.to_string(self.dformat_max)) return flag_account, position_str, weight_str beancount.parser.printer.EntryPrinter.write_metadata(self, meta, oss, prefix=None) \uf0c1 Write metadata to the file object, excluding filename and line number. Parameters: meta \u2013 A dict that contains the metadata for this directive. oss \u2013 A file object to write to. Source code in beancount/parser/printer.py def write_metadata(self, meta, oss, prefix=None): \"\"\"Write metadata to the file object, excluding filename and line number. Args: meta: A dict that contains the metadata for this directive. oss: A file object to write to. \"\"\" if meta is None: return if prefix is None: prefix = self.prefix for key, value in sorted(meta.items()): if key not in self.META_IGNORE: value_str = None if isinstance(value, str): value_str = '\"{}\"'.format(misc_utils.escape_string(value)) elif isinstance(value, (Decimal, datetime.date, amount.Amount)): value_str = str(value) elif isinstance(value, bool): value_str = 'TRUE' if value else 'FALSE' elif isinstance(value, (dict, inventory.Inventory)): pass # Ignore dicts, don't print them out. elif value is None: value_str = '' # Render null metadata as empty, on purpose. else: raise ValueError(\"Unexpected value: '{!r}'\".format(value)) if value_str is not None: oss.write(\"{}{}: {}\\n\".format(prefix, key, value_str)) beancount.parser.printer.align_position_strings(strings) \uf0c1 A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Parameters: strings \u2013 A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. Source code in beancount/parser/printer.py def align_position_strings(strings): \"\"\"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Args: strings: A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. \"\"\" # Maximum length before the alignment character. max_before = 0 # Maximum length after the alignment character. max_after = 0 # Maximum length of unknown strings. max_unknown = 0 string_items = [] search = re.compile('[A-Z]').search for string in strings: match = search(string) if match: index = match.start() if index != 0: max_before = max(index, max_before) max_after = max(len(string) - index, max_after) string_items.append((index, string)) continue # else max_unknown = max(len(string), max_unknown) string_items.append((None, string)) # Compute formatting string. max_total = max(max_before + max_after, max_unknown) max_after_prime = max_total - max_before fmt = \"{{:>{0}}}{{:{1}}}\".format(max_before, max_after_prime).format fmt_unknown = \"{{:<{0}}}\".format(max_total).format # Align the strings and return them. aligned_strings = [] for index, string in string_items: if index is not None: string = fmt(string[:index], string[index:]) else: string = fmt_unknown(string) aligned_strings.append(string) return aligned_strings, max_total beancount.parser.printer.format_entry(entry, dcontext=None, render_weights=False, prefix=None) \uf0c1 Format an entry into a string in the same input syntax the parser accepts. Parameters: entry \u2013 An entry instance. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. Source code in beancount/parser/printer.py def format_entry(entry, dcontext=None, render_weights=False, prefix=None): \"\"\"Format an entry into a string in the same input syntax the parser accepts. Args: entry: An entry instance. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. \"\"\" return EntryPrinter(dcontext, render_weights, prefix=prefix)(entry) beancount.parser.printer.format_error(error) \uf0c1 Given an error objects, return a formatted string for it. Parameters: error \u2013 a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. Source code in beancount/parser/printer.py def format_error(error): \"\"\"Given an error objects, return a formatted string for it. Args: error: a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. \"\"\" oss = io.StringIO() oss.write('{} {}\\n'.format(render_source(error.source), error.message)) if error.entry is not None: entries = error.entry if isinstance(error.entry, list) else [error.entry] error_string = '\\n'.join(format_entry(entry) for entry in entries) oss.write('\\n') oss.write(textwrap.indent(error_string, ' ')) oss.write('\\n') return oss.getvalue() beancount.parser.printer.print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None) \uf0c1 A convenience function that prints a list of entries to a file. Parameters: entries \u2013 A list of directives. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None): \"\"\"A convenience function that prints a list of entries to a file. Args: entries: A list of directives. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" assert isinstance(entries, list), \"Entries is not a list: {}\".format(entries) output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) if prefix: output.write(prefix) previous_type = type(entries[0]) if entries else None eprinter = EntryPrinter(dcontext, render_weights) for entry in entries: # Insert a newline between transactions and between blocks of directives # of the same type. entry_type = type(entry) if (entry_type in (data.Transaction, data.Commodity) or entry_type is not previous_type): output.write('\\n') previous_type = entry_type string = eprinter(entry) output.write(string) beancount.parser.printer.print_entry(entry, dcontext=None, render_weights=False, file=None) \uf0c1 A convenience function that prints a single entry to a file. Parameters: entry \u2013 A directive entry. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entry(entry, dcontext=None, render_weights=False, file=None): \"\"\"A convenience function that prints a single entry to a file. Args: entry: A directive entry. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) output.write(format_entry(entry, dcontext, render_weights)) output.write('\\n') beancount.parser.printer.print_error(error, file=None) \uf0c1 A convenience function that prints a single error to a file. Parameters: error \u2013 An error object. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_error(error, file=None): \"\"\"A convenience function that prints a single error to a file. Args: error: An error object. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout output.write(format_error(error)) output.write('\\n') beancount.parser.printer.print_errors(errors, file=None, prefix=None) \uf0c1 A convenience function that prints a list of errors to a file. Parameters: errors \u2013 A list of errors. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_errors(errors, file=None, prefix=None): \"\"\"A convenience function that prints a list of errors to a file. Args: errors: A list of errors. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout if prefix: output.write(prefix) for error in errors: output.write(format_error(error)) output.write('\\n') beancount.parser.printer.render_source(meta) \uf0c1 Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Parameters: meta \u2013 A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. Source code in beancount/parser/printer.py def render_source(meta): \"\"\"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Args: meta: A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. \"\"\" return '{}:{:8}'.format(meta['filename'], '{}:'.format(meta['lineno']))","title":"beancount.parser"},{"location":"api_reference/beancount.parser.html#beancountparser","text":"Parser module for beancount input files.","title":"beancount.parser"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking","text":"Algorithms for 'booking' inventory, that is, the process of finding a matching lot when reducing the content of an inventory.","title":"booking"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError","text":"BookingError(source, message, entry)","title":"BookingError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__new__","text":"Create new instance of BookingError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.book","text":"Book inventory lots and complete all positions with incomplete numbers. Parameters: incomplete_entries \u2013 A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map \u2013 An options dict as produced by the parser. Returns: A pair of entries \u2013 A list of completed entries with all their postings completed. errors: New errors produced during interpolation. Source code in beancount/parser/booking.py def book(incomplete_entries, options_map): \"\"\"Book inventory lots and complete all positions with incomplete numbers. Args: incomplete_entries: A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map: An options dict as produced by the parser. Returns: A pair of entries: A list of completed entries with all their postings completed. errors: New errors produced during interpolation. \"\"\" # Get the list of booking methods for each account. booking_methods = collections.defaultdict(lambda: options_map[\"booking_method\"]) for entry in incomplete_entries: if isinstance(entry, data.Open) and entry.booking: booking_methods[entry.account] = entry.booking # Do the booking here! entries, booking_errors = booking_full.book(incomplete_entries, options_map, booking_methods) # Check for MISSING elements remaining. missing_errors = validate_missing_eliminated(entries, options_map) return entries, (booking_errors + missing_errors)","title":"book()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.validate_inventory_booking","text":"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. booking_methods \u2013 A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_inventory_booking(entries, unused_options_map, booking_methods): \"\"\"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Args: entries: A list of directives. unused_options_map: An options map. booking_methods: A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. \"\"\" errors = [] balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: # Update the balance of each posting on its respective account # without allowing booking to a negative position, and if an error # is encountered, catch it and return it. running_balance = balances[posting.account] position_, _ = running_balance.add_position(posting) # Skip this check if the booking method is set to ignore it. if booking_methods.get(posting.account, None) == data.Booking.NONE: continue # Check if the resulting inventory is mixed, which is not # allowed under the STRICT method. if running_balance.is_mixed(): errors.append( BookingError( entry.meta, (\"Reducing position results in inventory with positive \" \"and negative lots: {}\").format(position_), entry)) return errors","title":"validate_inventory_booking()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.validate_missing_eliminated","text":"Validate that all the missing bits of postings have been eliminated. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_missing_eliminated(entries, unused_options_map): \"\"\"Validate that all the missing bits of postings have been eliminated. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of errors. \"\"\" errors = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: units = posting.units cost = posting.cost if (MISSING in (units.number, units.currency) or cost is not None and MISSING in (cost.number, cost.currency, cost.date, cost.label)): errors.append( BookingError(entry.meta, \"Transaction has incomplete elements\", entry)) break return errors","title":"validate_missing_eliminated()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full","text":"Full (new) booking implementation. Problem description: Interpolation and booking feed on each other, that is, numbers filled in from interpolation might affect the booking process, and numbers derived from the booking process may help carry out interpolation that would otherwise be under-defined. Here's an example of interpolation helping the booking process: Assume the ante-inventory of Assets:Investments contains two lots of shares of HOOL, one at 100.00 USD and the other at 101.00 USD and apply this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains -200 USD Interpolation is unambiguously able to back out a cost of 100 USD / HOOL, which would then result in an unambiguous booking result. On the other hand, consider this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains Now the interpolation cannot succeed. If the Assets:Investments account is configured to use the FIFO method, the 10 oldest shares would be selected for the cost, and we could then interpolate the capital gains correctly. First observation: The second case is much more frequent than the first, and the first is easily resolved manually by requiring a particular cost be specified. Moreover, in many cases there isn't just a single lot of shares to be reduced from and figuring out the correct set of shares given a target cost is an underspecified problem. Second observation: Booking can only be achieved for inventory reductions, not for augmentations. Therefore, we should carry out booking on inventory reductions and fail early if reduction is undefined there, and leave inventory augmentations with missing numbers undefined, so that interpolation can fill them in at a later stage. Note that one case we'd like to but may not be able to handle is of a reduction with interpolated price, like this: 2015-09-30 * Assets:Investments -10 HOOL {100.00 # USD} Expenses:Commission 9.95 USD Assets:Cash 990.05 USD Therefore we choose to 1) Carry out booking first, on inventory reductions only, and leave inventory augmentations as they are, possibly undefined. The 'cost' attributed of booked postings are converted from CostSpec to Cost. Augmented postings with missing amounts are left as CostSpec instances in order to allow for interpolation of total vs. per-unit amount. 2) Compute interpolations on the resulting postings. Undefined costs for inventory augmentations may be filled in by interpolations at this stage (if possible). 3) Finally, convert the interpolated CostSpec instances to Cost instances. Improving on this algorithm would require running a loop over the booking and interpolation steps until all numbers are resolved or no more inference can occur. We may consider that for later, as an experimental feature. My hunch is that there are so few cases for which this would be useful that we won't bother improving on the algorithm above.","title":"booking_full"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError","text":"CategorizationError(source, message, entry)","title":"CategorizationError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__new__","text":"Create new instance of CategorizationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError","text":"InterpolationError(source, message, entry)","title":"InterpolationError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__new__","text":"Create new instance of InterpolationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.MissingType","text":"The type of missing number.","title":"MissingType"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError","text":"ReductionError(source, message, entry)","title":"ReductionError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__new__","text":"Create new instance of ReductionError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer","text":"Refer(index, units_currency, cost_currency, price_currency)","title":"Refer"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__new__","text":"Create new instance of Refer(index, units_currency, cost_currency, price_currency)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError","text":"SelfReduxError(source, message, entry)","title":"SelfReduxError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__new__","text":"Create new instance of SelfReduxError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.book","text":"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. Source code in beancount/parser/booking_full.py def book(entries, options_map, methods): \"\"\"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. \"\"\" entries, errors, _ = _book(entries, options_map, methods) return entries, errors","title":"book()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.book_reductions","text":"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Parameters: entry \u2013 An instance of Transaction. This is only used to refer to when logging errors. group_postings \u2013 A list of Posting instances for the group. balances \u2013 A dict of account name to inventory contents. methods \u2013 A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings \u2013 A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. Source code in beancount/parser/booking_full.py def book_reductions(entry, group_postings, balances, methods): \"\"\"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. * For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. * For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Args: entry: An instance of Transaction. This is only used to refer to when logging errors. group_postings: A list of Posting instances for the group. balances: A dict of account name to inventory contents. methods: A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings: A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. \"\"\" errors = [] # A local copy of the balances dictionary which is updated just for the # duration of this function's updates, in order to take into account the # cumulative effect of all the postings inferred here local_balances = {} empty = inventory.Inventory() booked_postings = [] for posting in group_postings: # Process a single posting. units = posting.units costspec = posting.cost account = posting.account # Note: We ensure there is no mutation on 'balances' to keep this # function without side-effects. Note that we may be able to optimize # performance later on by giving up this property. # # Also note that if there is no existing balance, then won't be any lot # reduction because none of the postings will be able to match against # any currencies of the balance. previous_balance = balances.get(account, empty) balance = local_balances.setdefault(account, copy.copy(previous_balance)) # Check if this is a lot held at cost. if costspec is None or units.number is MISSING: # This posting is not held at cost; we do nothing. booked_postings.append(posting) else: # This posting is held at cost; figure out if it's a reduction or an # augmentation. method = methods[account] if (method is not Booking.NONE and balance is not None and balance.is_reduced_by(units)): # This posting is a reduction. # Match the positions. cost_number = compute_cost_number(costspec, units) matches = [] for position in balance: # Skip inventory contents of a different currency. if (units.currency and position.units.currency != units.currency): continue # Skip balance positions not held at cost. if position.cost is None: continue if (cost_number is not None and position.cost.number != cost_number): continue if (isinstance(costspec.currency, str) and position.cost.currency != costspec.currency): continue if (costspec.date and position.cost.date != costspec.date): continue if (costspec.label and position.cost.label != costspec.label): continue matches.append(position) # Check for ambiguous matches. if len(matches) == 0: errors.append( ReductionError(entry.meta, 'No position matches \"{}\" against balance {}'.format( posting, balance), entry)) return [], errors # This is irreconcilable, remove these postings. reduction_postings, matched_postings, ambi_errors = ( booking_method.handle_ambiguous_matches(entry, posting, matches, method)) if ambi_errors: errors.extend(ambi_errors) return [], errors # Add the reductions to the resulting list of booked postings. booked_postings.extend(reduction_postings) # Update the local balance in order to avoid matching against # the same postings twice when processing multiple postings in # the same transaction. Note that we only do this for postings # held at cost because the other postings may need interpolation # in order to be resolved properly. for posting in reduction_postings: balance.add_position(posting) else: # This posting is an augmentation. # # Note that we do not convert the CostSpec instances to Cost # instances, because we want to let the subsequent interpolation # process able to interpolate either the cost per-unit or the # total cost, separately. # Put in the date of the parent Transaction if there is no # explicit date specified on the spec. if costspec.date is None: dated_costspec = costspec._replace(date=entry.date) posting = posting._replace(cost=dated_costspec) # FIXME: Insert unique ids for trade tracking; right now this # creates ambiguous matches errors (and it shouldn't). # # Insert a unique label if there isn't one. # if posting.cost is not None and posting.cost.label is None: # posting = posting._replace( # cost=posting.cost._replace(label=unique_label())) booked_postings.append(posting) return booked_postings, errors","title":"book_reductions()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.categorize_by_currency","text":"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. If the currency is explicitly specified, we put the posting in that currency's bucket. If not, we have a few methods left to disambiguate the currency: We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Parameters: postings \u2013 A list of incomplete postings to categorize. balances \u2013 A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains \u2013 index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. Source code in beancount/parser/booking_full.py def categorize_by_currency(entry, balances): \"\"\"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. - First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. - If the currency is explicitly specified, we put the posting in that currency's bucket. - If not, we have a few methods left to disambiguate the currency: 1. We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. 2. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Args: postings: A list of incomplete postings to categorize. balances: A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains: index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. \"\"\" errors = [] groups = collections.defaultdict(list) sortdict = {} auto_postings = [] unknown = [] for index, posting in enumerate(entry.postings): units = posting.units cost = posting.cost price = posting.price # Extract and override the currencies locally. units_currency = (units.currency if units is not MISSING and units is not None else None) cost_currency = (cost.currency if cost is not MISSING and cost is not None else None) price_currency = (price.currency if price is not MISSING and price is not None else None) # First we apply the constraint that cost-currency and price-currency # must match, if there is both a cost and a price. This reduces the # space of possibilities somewhat. if cost_currency is MISSING and isinstance(price_currency, str): cost_currency = price_currency if price_currency is MISSING and isinstance(cost_currency, str): price_currency = cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) if units is MISSING and price_currency is None: # Bucket auto-postings separately from unknown. auto_postings.append(refer) else: # Bucket with what we know so far. currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: # If we need to infer the currency, store in unknown. unknown.append(refer) # We look at the remaining postings... if they are all of a single currency, # the posting must be in that currency too. if unknown and len(unknown) == 1 and len(groups) == 1: (index, units_currency, cost_currency, price_currency) = unknown.pop() other_currency = next(iter(groups.keys())) if price_currency is None and cost_currency is None: # Infer to the units currency. units_currency = other_currency else: # Infer to the cost and price currencies. if price_currency is MISSING: price_currency = other_currency if cost_currency is MISSING: cost_currency = other_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) assert currency is not None sortdict.setdefault(currency, index) groups[currency].append(refer) # Finally, try to resolve all the unknown legs using the inventory contents # of each account. for refer in unknown: (index, units_currency, cost_currency, price_currency) = refer posting = entry.postings[index] balance = balances.get(posting.account, None) if balance is None: balance = inventory.Inventory() if units_currency is MISSING: balance_currencies = balance.currencies() if len(balance_currencies) == 1: units_currency = balance_currencies.pop() if cost_currency is MISSING or price_currency is MISSING: balance_cost_currencies = balance.cost_currencies() if len(balance_cost_currencies) == 1: balance_cost_currency = balance_cost_currencies.pop() if price_currency is MISSING: price_currency = balance_cost_currency if cost_currency is MISSING: cost_currency = balance_cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: errors.append( CategorizationError(posting.meta, \"Failed to categorize posting {}\".format(index + 1), entry)) # Fill in missing units currencies if some remain as missing. This may occur # if we used the cost or price to bucket the currency but the units currency # was missing. for currency, refers in groups.items(): for rindex, refer in enumerate(refers): if refer.units_currency is MISSING: posting = entry.postings[refer.index] balance = balances.get(posting.account, None) if balance is None: continue balance_currencies = balance.currencies() if len(balance_currencies) == 1: refers[rindex] = refer._replace(units_currency=balance_currencies.pop()) # Deal with auto-postings. if len(auto_postings) > 1: refer = auto_postings[-1] posting = entry.postings[refer.index] errors.append( CategorizationError(posting.meta, \"You may not have more than one auto-posting per currency\", entry)) auto_postings = auto_postings[0:1] for refer in auto_postings: for currency in groups.keys(): sortdict.setdefault(currency, refer.index) groups[currency].append(Refer(refer.index, currency, None, None)) # Issue error for all currencies which we could not resolve. for currency, refers in groups.items(): for refer in refers: posting = entry.postings[refer.index] for currency, name in [(refer.units_currency, 'units'), (refer.cost_currency, 'cost'), (refer.price_currency, 'price')]: if currency is MISSING: errors.append(CategorizationError( posting.meta, \"Could not resolve {} currency\".format(name), entry)) sorted_groups = sorted(groups.items(), key=lambda item: sortdict[item[0]]) return sorted_groups, errors","title":"categorize_by_currency()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.compute_cost_number","text":"Given a CostSpec, return the cost number, if possible to compute. Parameters: costspec \u2013 A parsed instance of CostSpec. units \u2013 An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. Source code in beancount/parser/booking_full.py def compute_cost_number(costspec, units): \"\"\"Given a CostSpec, return the cost number, if possible to compute. Args: costspec: A parsed instance of CostSpec. units: An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. \"\"\" number_per = costspec.number_per number_total = costspec.number_total if MISSING in (number_per, number_total): return None if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total units_number = units.number if number_per is not None: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) elif number_per is None: return None else: unit_cost = number_per return unit_cost","title":"compute_cost_number()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.convert_costspec_to_cost","text":"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. Source code in beancount/parser/booking_full.py def convert_costspec_to_cost(posting): \"\"\"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Args: posting: An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. \"\"\" cost = posting.cost if isinstance(cost, position.CostSpec): if cost is not None: units_number = posting.units.number number_per = cost.number_per number_total = cost.number_total if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total if number_per is not MISSING: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) else: unit_cost = number_per new_cost = Cost(unit_cost, cost.currency, cost.date, cost.label) posting = posting._replace(units=posting.units, cost=new_cost) return posting","title":"convert_costspec_to_cost()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.get_bucket_currency","text":"Given currency references for a posting, return the bucket currency. Parameters: refer \u2013 An instance of Refer. Returns: A currency string. Source code in beancount/parser/booking_full.py def get_bucket_currency(refer): \"\"\"Given currency references for a posting, return the bucket currency. Args: refer: An instance of Refer. Returns: A currency string. \"\"\" currency = None if isinstance(refer.cost_currency, str): currency = refer.cost_currency elif isinstance(refer.price_currency, str): currency = refer.price_currency elif (refer.cost_currency is None and refer.price_currency is None and isinstance(refer.units_currency, str)): currency = refer.units_currency return currency","title":"get_bucket_currency()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.has_self_reduction","text":"Return true if the postings potentially reduce each other at cost. Parameters: postings \u2013 A list of postings with uninterpolated CostSpec cost instances. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. Source code in beancount/parser/booking_full.py def has_self_reduction(postings, methods): \"\"\"Return true if the postings potentially reduce each other at cost. Args: postings: A list of postings with uninterpolated CostSpec cost instances. methods: A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. \"\"\" # A mapping of (currency, cost-currency) and sign. cost_changes = {} for posting in postings: cost = posting.cost if cost is None: continue if methods[posting.account] is Booking.NONE: continue key = (posting.account, posting.units.currency) sign = 1 if posting.units.number > ZERO else -1 if cost_changes.setdefault(key, sign) != sign: return True return False","title":"has_self_reduction()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.interpolate_group","text":"Interpolate missing numbers in the set of postings. Parameters: postings \u2013 A list of Posting instances. balances \u2013 A dict of account to its ante-inventory. currency \u2013 The weight currency of this group, used for reporting errors. tolerances \u2013 A dict of currency to tolerance values. Returns: A tuple of postings \u2013 A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) Source code in beancount/parser/booking_full.py def interpolate_group(postings, balances, currency, tolerances): \"\"\"Interpolate missing numbers in the set of postings. Args: postings: A list of Posting instances. balances: A dict of account to its ante-inventory. currency: The weight currency of this group, used for reporting errors. tolerances: A dict of currency to tolerance values. Returns: A tuple of postings: A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) \"\"\" errors = [] # Figure out which type of amount is missing, by creating a list of # incomplete postings and which type of units is missing. incomplete = [] for index, posting in enumerate(postings): units = posting.units cost = posting.cost price = posting.price # Identify incomplete parts of the Posting components. if units.number is MISSING: incomplete.append((MissingType.UNITS, index)) if isinstance(cost, CostSpec): if cost and cost.number_per is MISSING: incomplete.append((MissingType.COST_PER, index)) if cost and cost.number_total is MISSING: incomplete.append((MissingType.COST_TOTAL, index)) else: # Check that a resolved instance of Cost never needs interpolation. # # Note that in theory we could support the interpolation of regular # per-unit costs in these if we wanted to; but because they're all # reducing postings that have been booked earlier, those never need # to be interpolated. if cost is not None: assert isinstance(cost.number, Decimal), ( \"Internal error: cost has no number: {}\".format(cost)) if price and price.number is MISSING: incomplete.append((MissingType.PRICE, index)) # The replacement posting for the incomplete posting of this group. new_posting = None if len(incomplete) == 0: # If there are no missing numbers, just convert the CostSpec to Cost and # return that. out_postings = [convert_costspec_to_cost(posting) for posting in postings] elif len(incomplete) > 1: # If there is more than a single value to be interpolated, generate an # error and return no postings. _, posting_index = incomplete[0] errors.append(InterpolationError( postings[posting_index].meta, \"Too many missing numbers for currency group '{}'\".format(currency), None)) out_postings = [] else: # If there is a single missing number, calculate it and fill it in here. missing, index = incomplete[0] incomplete_posting = postings[index] # Convert augmenting postings' costs from CostSpec to corresponding Cost # instances, except for the incomplete posting. new_postings = [(posting if posting is incomplete_posting else convert_costspec_to_cost(posting)) for posting in postings] # Compute the balance of the other postings. residual = interpolate.compute_residual(posting for posting in new_postings if posting is not incomplete_posting) assert len(residual) < 2, \"Internal error in grouping postings by currencies.\" if not residual.is_empty(): respos = next(iter(residual)) assert respos.cost is None, ( \"Internal error; cost appears in weight calculation.\") assert respos.units.currency == currency, ( \"Internal error; residual different than currency group.\") weight = -respos.units.number weight_currency = respos.units.currency else: weight = ZERO weight_currency = currency if missing == MissingType.UNITS: units = incomplete_posting.units cost = incomplete_posting.cost if cost: # Handle the special case where we only have total cost. if cost.number_per == ZERO: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer per-unit cost only from total\", None)) return postings, errors, True assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") cost_total = cost.number_total or ZERO units_number = (weight - cost_total) / cost.number_per elif incomplete_posting.price: assert incomplete_posting.price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight / incomplete_posting.price.number else: assert units.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight # Quantize the interpolated units if necessary. units_number = interpolate.quantize_with_tolerance(tolerances, units.currency, units_number) if weight != ZERO: new_pos = Position(Amount(units_number, units.currency), cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_PER: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") if units.number != ZERO: number_per = (weight - (cost.number_total or ZERO)) / units.number new_cost = cost._replace(number_per=number_per) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_TOTAL: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") number_total = (weight - cost.number_per * units.number) new_cost = cost._replace(number_total=number_total) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) elif missing == MissingType.PRICE: units = incomplete_posting.units cost = incomplete_posting.cost if cost is not None: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer price for postings with units held at cost\", None)) return postings, errors, True else: price = incomplete_posting.price assert price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") new_price_number = abs(weight / units.number) new_posting = incomplete_posting._replace(price=Amount(new_price_number, price.currency)) else: assert False, \"Internal error; Invalid missing type.\" # Replace the number in the posting. if new_posting is not None: # Set meta-data on the new posting to indicate it was interpolated. if new_posting.meta is None: new_posting = new_posting._replace(meta={}) new_posting.meta[interpolate.AUTOMATIC_META] = True # Convert augmenting posting costs from CostSpec to a corresponding # Cost instance. new_postings[index] = convert_costspec_to_cost(new_posting) else: del new_postings[index] out_postings = new_postings assert all(not isinstance(posting.cost, CostSpec) for posting in out_postings) # Check that units are non-zero and that no cost remains negative; issue an # error if this is the case. for posting in out_postings: if posting.cost is None: continue # If there is a cost, we don't allow either a cost value of zero, # nor a zero number of units. Note that we allow a price of zero as # the only special case allowed (for conversion entries), but never # for costs. if posting.units.number == ZERO: errors.append(InterpolationError( posting.meta, 'Amount is zero: \"{}\"'.format(posting.units), None)) if posting.cost.number < ZERO: errors.append(InterpolationError( posting.meta, 'Cost is negative: \"{}\"'.format(posting.cost), None)) return out_postings, errors, (new_posting is not None)","title":"interpolate_group()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.replace_currencies","text":"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Parameters: postings \u2013 A list of Posting instances to replace. refer_groups \u2013 A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. Source code in beancount/parser/booking_full.py def replace_currencies(postings, refer_groups): \"\"\"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Args: postings: A list of Posting instances to replace. refer_groups: A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. \"\"\" new_groups = [] for currency, refers in refer_groups: new_postings = [] for refer in sorted(refers, key=lambda r: r.index): posting = postings[refer.index] units = posting.units if units is MISSING or units is None: posting = posting._replace(units=Amount(MISSING, refer.units_currency)) else: replace = False cost = posting.cost price = posting.price if units.currency is MISSING: units = Amount(units.number, refer.units_currency) replace = True if cost and cost.currency is MISSING: cost = cost._replace(currency=refer.cost_currency) replace = True if price and price.currency is MISSING: price = Amount(price.number, refer.price_currency) replace = True if replace: posting = posting._replace(units=units, cost=cost, price=price) new_postings.append(posting) new_groups.append((currency, new_postings)) return new_groups","title":"replace_currencies()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.unique_label","text":"Return a globally unique label for cost entries. Source code in beancount/parser/booking_full.py def unique_label() -> Text: \"Return a globally unique label for cost entries.\" return str(uuid.uuid4())","title":"unique_label()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method","text":"Implementations of all the particular booking methods. This code is used by the full booking algorithm.","title":"booking_method"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError","text":"AmbiguousMatchError(source, message, entry)","title":"AmbiguousMatchError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_method.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__new__","text":"Create new instance of AmbiguousMatchError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_method.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_AVERAGE","text":"AVERAGE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_AVERAGE(entry, posting, matches): \"\"\"AVERAGE booking method implementation.\"\"\" booked_reductions = [] booked_matches = [] errors = [AmbiguousMatchError(entry.meta, \"AVERAGE method is not supported\", entry)] return booked_reductions, booked_matches, errors, False # FIXME: Future implementation here. # pylint: disable=unreachable if False: # pylint: disable=using-constant-test # DISABLED - This is the code for AVERAGE, which is currently disabled. # If there is more than a single match we need to ultimately merge the # postings. Also, if the reducing posting provides a specific cost, we # need to update the cost basis as well. Both of these cases are carried # out by removing all the matches and readding them later on. if len(matches) == 1 and ( not isinstance(posting.cost.number_per, Decimal) and not isinstance(posting.cost.number_total, Decimal)): # There is no cost. Just reduce the one leg. This should be the # normal case if we always merge augmentations and the user lets # Beancount deal with the cost. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) insufficient = (match_units.number != posting.units.number) else: # Merge the matching postings to a single one. merged_units = inventory.Inventory() merged_cost = inventory.Inventory() for match in matches: merged_units.add_amount(match.units) merged_cost.add_amount(convert.get_weight(match)) if len(merged_units) != 1 or len(merged_cost) != 1: errors.append( AmbiguousMatchError( entry.meta, 'Cannot merge positions in multiple currencies: {}'.format( ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: if (isinstance(posting.cost.number_per, Decimal) or isinstance(posting.cost.number_total, Decimal)): errors.append( AmbiguousMatchError( entry.meta, \"Explicit cost reductions aren't supported yet: {}\".format( position.to_string(posting)), entry)) else: # Insert postings to remove all the matches. booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost, flag=flags.FLAG_MERGING) for match in matches) units = merged_units[0].units date = matches[0].cost.date ## FIXME: Select which one, ## oldest or latest. cost_units = merged_cost[0].units cost = Cost(cost_units.number/units.number, cost_units.currency, date, None) # Insert a posting to refill those with a replacement match. booked_reductions.append( posting._replace(units=units, cost=cost, flag=flags.FLAG_MERGING)) # Now, match the reducing request against this lot. booked_reductions.append( posting._replace(units=posting.units, cost=cost)) insufficient = abs(posting.units.number) > abs(units.number)","title":"booking_method_AVERAGE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_FIFO","text":"FIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_FIFO(entry, posting, matches): \"\"\"FIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, False)","title":"booking_method_FIFO()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_LIFO","text":"LIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_LIFO(entry, posting, matches): \"\"\"LIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, True)","title":"booking_method_LIFO()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_NONE","text":"NONE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_NONE(entry, posting, matches): \"\"\"NONE booking method implementation.\"\"\" # This never needs to match against any existing positions... we # disregard the matches, there's never any error. Note that this never # gets called in practice, we want to treat NONE postings as # augmentations. Default behaviour is to return them with their original # CostSpec, and the augmentation code will handle signaling an error if # there is insufficient detail to carry out the conversion to an # instance of Cost. # Note that it's an interesting question whether a reduction on an # account with NONE method which happens to match a single position # ought to be matched against it. We don't allow it for now. return [posting], [], False","title":"booking_method_NONE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_STRICT","text":"Strict booking method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. Source code in beancount/parser/booking_method.py def booking_method_STRICT(entry, posting, matches): \"\"\"Strict booking method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. \"\"\" booked_reductions = [] booked_matches = [] errors = [] insufficient = False # In strict mode, we require at most a single matching posting. if len(matches) > 1: # If the total requested to reduce matches the sum of all the # ambiguous postings, match against all of them. sum_matches = sum(p.units.number for p in matches) if sum_matches == -posting.units.number: booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost) for match in matches) else: errors.append( AmbiguousMatchError(entry.meta, 'Ambiguous matches for \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: # Replace the posting's units and cost values. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) booked_matches.append(match) insufficient = (match_units.number != posting.units.number) return booked_reductions, booked_matches, errors, insufficient","title":"booking_method_STRICT()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.handle_ambiguous_matches","text":"Handle ambiguous matches by dispatching to a particular method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. Source code in beancount/parser/booking_method.py def handle_ambiguous_matches(entry, posting, matches, method): \"\"\"Handle ambiguous matches by dispatching to a particular method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods: A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. \"\"\" assert isinstance(method, Booking), ( \"Invalid type: {}\".format(method)) assert matches, \"Internal error: Invalid call with no matches\" #method = globals()['booking_method_{}'.format(method.name)] method = _BOOKING_METHODS[method] (booked_reductions, booked_matches, errors, insufficient) = method(entry, posting, matches) if insufficient: errors.append( AmbiguousMatchError(entry.meta, 'Not enough lots to reduce \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) return booked_reductions, booked_matches, errors","title":"handle_ambiguous_matches()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest","text":"Support utilities for testing scripts.","title":"cmptest"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.TestError","text":"Errors within the test implementation itself. These should never occur.","title":"TestError"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertEqualEntries","text":"Compare two lists of entries exactly and print missing entries verbosely if they occur. Parameters: expected_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries \u2013 Same treatment as expected_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertEqualEntries(expected_entries, actual_entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, \"Missing is missing: {}, {}\".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write(\"Present in expected set and not in actual set:\\n\\n\") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') if actual_missing: oss.write(\"Present in actual set and not in expected set:\\n\\n\") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertEqualEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertExcludesEntries","text":"Check that subset_entries is not included in entries and print extra entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertExcludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is not included in entries and print extra entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) excludes, extra = compare.excludes_entries(subset_entries, entries) if not excludes: assert extra, \"Extra is empty: {}\".format(extra) oss = io.StringIO() if extra: oss.write(\"Extra from from first/excluded set:\\n\\n\") for entry in extra: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertExcludesEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertIncludesEntries","text":"Check that subset_entries is included in entries and print missing entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertIncludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is included in entries and print missing entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) includes, missing = compare.includes_entries(subset_entries, entries) if not includes: assert missing, \"Missing is empty: {}\".format(missing) oss = io.StringIO() if missing: oss.write(\"Missing from from expected set:\\n\\n\") for entry in missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertIncludesEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.read_string_or_entries","text":"Read a string of entries or just entries. Parameters: entries_or_str \u2013 Either a list of directives, or a string containing directives. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. Source code in beancount/parser/cmptest.py def read_string_or_entries(entries_or_str, allow_incomplete=False): \"\"\"Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. \"\"\" if isinstance(entries_or_str, str): entries, errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) if allow_incomplete: # Do a simplistic local conversion in order to call the comparison. entries = [_local_booking(entry) for entry in entries] else: # Don't accept incomplete entries either. if any(parser.is_entry_incomplete(entry) for entry in entries): raise TestError(\"Entries in assertions may not use interpolation.\") entries, booking_errors = booking.book(entries, options_map) errors = errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError(\"Unexpected errors in expected: {}\".format(oss.getvalue())) else: assert isinstance(entries_or_str, list), \"Expecting list: {}\".format(entries_or_str) entries = entries_or_str return entries","title":"read_string_or_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar","text":"Builder for Beancount grammar.","title":"grammar"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder","text":"A builder used by the lexer and grammar parser as callbacks to create the data objects corresponding to rules parsed from the input file.","title":"Builder"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.amount","text":"Process an amount grammar rule. Parameters: number \u2013 a Decimal instance, the number of the amount. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. Source code in beancount/parser/grammar.py def amount(self, number, currency): \"\"\"Process an amount grammar rule. Args: number: a Decimal instance, the number of the amount. currency: a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number, currency) return Amount(number, currency)","title":"amount()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.balance","text":"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to balance. amount \u2013 The expected amount, to be checked. tolerance \u2013 The tolerance number. kvlist \u2013 a list of KeyValue instances. Returns: A new Balance object. Source code in beancount/parser/grammar.py def balance(self, filename, lineno, date, account, amount, tolerance, kvlist): \"\"\"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to balance. amount: The expected amount, to be checked. tolerance: The tolerance number. kvlist: a list of KeyValue instances. Returns: A new Balance object. \"\"\" diff_amount = None meta = new_metadata(filename, lineno, kvlist) return Balance(meta, date, account, amount, tolerance, diff_amount)","title":"balance()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.build_grammar_error","text":"Build a grammar error and appends it to the list of pending errors. Parameters: filename \u2013 The current filename lineno \u2013 The current line number excvalue \u2013 The exception value, or a str, the message of the error. exc_type \u2013 An exception type, if an exception occurred. exc_traceback \u2013 A traceback object. Source code in beancount/parser/grammar.py def build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None): \"\"\"Build a grammar error and appends it to the list of pending errors. Args: filename: The current filename lineno: The current line number excvalue: The exception value, or a str, the message of the error. exc_type: An exception type, if an exception occurred. exc_traceback: A traceback object. \"\"\" if exc_type is not None: assert not isinstance(exc_value, str) strings = traceback.format_exception_only(exc_type, exc_value) tblist = traceback.extract_tb(exc_traceback) filename, lineno, _, __ = tblist[0] message = '{} ({}:{})'.format(strings[0], filename, lineno) else: message = str(exc_value) meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, message, None))","title":"build_grammar_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.close","text":"Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def close(self, filename, lineno, date, account, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Close(meta, date, account)","title":"close()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.commodity","text":"Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. currency \u2013 A string, the commodity being declared. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def commodity(self, filename, lineno, date, currency, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. currency: A string, the commodity being declared. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Commodity(meta, date, currency)","title":"commodity()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.compound_amount","text":"Process an amount grammar rule. Parameters: number_per \u2013 a Decimal instance, the number of the cost per share. number_total \u2013 a Decimal instance, the number of the cost over all shares. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. Source code in beancount/parser/grammar.py def compound_amount(self, number_per, number_total, currency): \"\"\"Process an amount grammar rule. Args: number_per: a Decimal instance, the number of the cost per share. number_total: a Decimal instance, the number of the cost over all shares. currency: a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number_per, currency) self.dcupdate(number_total, currency) # Note that we are not able to reduce the value to a number per-share # here because we only get the number of units in the full lot spec. return CompoundAmount(number_per, number_total, currency)","title":"compound_amount()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.cost_merge","text":"Create a 'merge cost' token. Source code in beancount/parser/grammar.py def cost_merge(self, _): \"\"\"Create a 'merge cost' token.\"\"\" return MERGE_COST","title":"cost_merge()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.cost_spec","text":"Process a cost_spec grammar rule. Parameters: cost_comp_list \u2013 A list of CompoundAmount, a datetime.date, or label ID strings. is_total \u2013 Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". Source code in beancount/parser/grammar.py def cost_spec(self, cost_comp_list, is_total): \"\"\"Process a cost_spec grammar rule. Args: cost_comp_list: A list of CompoundAmount, a datetime.date, or label ID strings. is_total: Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". \"\"\" if not cost_comp_list: return CostSpec(MISSING, None, MISSING, None, None, False) assert isinstance(cost_comp_list, list), ( \"Internal error in parser: {}\".format(cost_comp_list)) compound_cost = None date_ = None label = None merge = None for comp in cost_comp_list: if isinstance(comp, CompoundAmount): if compound_cost is None: compound_cost = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate cost: '{}'.\".format(comp), None)) elif isinstance(comp, date): if date_ is None: date_ = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate date: '{}'.\".format(comp), None)) elif comp is MERGE_COST: if merge is None: merge = True self.errors.append( ParserError(self.get_lexer_location(), \"Cost merging is not supported yet\", None)) else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate merge-cost spec\", None)) else: assert isinstance(comp, str), ( \"Currency component is not string: '{}'\".format(comp)) if label is None: label = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate label: '{}'.\".format(comp), None)) # If there was a cost_comp_list, thus a \"{...}\" cost basis spec, you must # indicate that by creating a CompoundAmount(), always. if compound_cost is None: number_per, number_total, currency = MISSING, None, MISSING else: number_per, number_total, currency = compound_cost if is_total: if number_total is not None: self.errors.append( ParserError( self.get_lexer_location(), (\"Per-unit cost may not be specified using total cost \" \"syntax: '{}'; ignoring per-unit cost\").format(compound_cost), None)) # Ignore per-unit number. number_per = ZERO else: # There's a single number specified; interpret it as a total cost. number_total = number_per number_per = ZERO if merge is None: merge = False return CostSpec(number_per, number_total, currency, date_, label, merge)","title":"cost_spec()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.custom","text":"Process a custom directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. dir_type \u2013 A string, a type for the custom directive being parsed. custom_values \u2013 A list of the various tokens seen on the same line. kvlist \u2013 a list of KeyValue instances. Returns: A new Custom object. Source code in beancount/parser/grammar.py def custom(self, filename, lineno, date, dir_type, custom_values, kvlist): \"\"\"Process a custom directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. dir_type: A string, a type for the custom directive being parsed. custom_values: A list of the various tokens seen on the same line. kvlist: a list of KeyValue instances. Returns: A new Custom object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Custom(meta, date, dir_type, custom_values)","title":"custom()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.custom_value","text":"Create a custom value object, along with its type. Parameters: value \u2013 One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. Source code in beancount/parser/grammar.py def custom_value(self, value, dtype=None): \"\"\"Create a custom value object, along with its type. Args: value: One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. \"\"\" if dtype is None: dtype = type(value) return ValueType(value, dtype)","title":"custom_value()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.dcupdate","text":"Update the display context. Source code in beancount/parser/grammar.py def dcupdate(self, number, currency): \"\"\"Update the display context.\"\"\" if isinstance(number, Decimal) and currency and currency is not MISSING: self._dcupdate(number, currency)","title":"dcupdate()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.document","text":"Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. account \u2013 an Account instance. document_filename \u2013 a str, the name of the document file. tags_links \u2013 The current TagsLinks accumulator. kvlist \u2013 a list of KeyValue instances. Returns: A new Document object. Source code in beancount/parser/grammar.py def document(self, filename, lineno, date, account, document_filename, tags_links, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. account: an Account instance. document_filename: a str, the name of the document file. tags_links: The current TagsLinks accumulator. kvlist: a list of KeyValue instances. Returns: A new Document object. \"\"\" meta = new_metadata(filename, lineno, kvlist) if not path.isabs(document_filename): document_filename = path.abspath(path.join(path.dirname(filename), document_filename)) tags, links = self.finalize_tags_links(tags_links.tags, tags_links.links) return Document(meta, date, account, document_filename, tags, links)","title":"document()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.event","text":"Process an event directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. event_type \u2013 a str, the name of the event type. description \u2013 a str, the event value, the contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Event object. Source code in beancount/parser/grammar.py def event(self, filename, lineno, date, event_type, description, kvlist): \"\"\"Process an event directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. event_type: a str, the name of the event type. description: a str, the event value, the contents. kvlist: a list of KeyValue instances. Returns: A new Event object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Event(meta, date, event_type, description)","title":"event()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.finalize","text":"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries \u2013 A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. Source code in beancount/parser/grammar.py def finalize(self): \"\"\"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries: A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. \"\"\" # If the user left some tags unbalanced, issue an error. for tag in self.tags: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Unbalanced pushed tag: '{}'\".format(tag), None)) # If the user left some metadata unpopped, issue an error. for key, value_list in self.meta.items(): meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, ( \"Unbalanced metadata key '{}'; leftover metadata '{}'\").format( key, ', '.join(value_list)), None)) # Weave the commas option in the DisplayContext itself, so it propagates # everywhere it is used automatically. self.dcontext.set_commas(self.options['render_commas']) return (self.get_entries(), self.errors, self.get_options())","title":"finalize()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.finalize_tags_links","text":"Finally amend tags and links and return final objects to be inserted. Parameters: tags \u2013 A set of tag strings (warning: this gets mutated in-place). links \u2013 A set of link strings. Returns: A sanitized pair of (tags, links). Source code in beancount/parser/grammar.py def finalize_tags_links(self, tags, links): \"\"\"Finally amend tags and links and return final objects to be inserted. Args: tags: A set of tag strings (warning: this gets mutated in-place). links: A set of link strings. Returns: A sanitized pair of (tags, links). \"\"\" if self.tags: tags.update(self.tags) return (frozenset(tags) if tags else EMPTY_SET, frozenset(links) if links else EMPTY_SET)","title":"finalize_tags_links()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_entries","text":"Return the accumulated entries. Returns: A list of sorted directives. Source code in beancount/parser/grammar.py def get_entries(self): \"\"\"Return the accumulated entries. Returns: A list of sorted directives. \"\"\" return sorted(self.entries, key=data.entry_sortkey)","title":"get_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_invalid_account","text":"See base class. Source code in beancount/parser/grammar.py def get_invalid_account(self): \"\"\"See base class.\"\"\" return account.join(self.options['name_equity'], 'InvalidAccountName')","title":"get_invalid_account()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_long_string_maxlines","text":"See base class. Source code in beancount/parser/grammar.py def get_long_string_maxlines(self): \"\"\"See base class.\"\"\" return self.options['long_string_maxlines']","title":"get_long_string_maxlines()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_options","text":"Return the final options map. Returns: A dict of option names to options. Source code in beancount/parser/grammar.py def get_options(self): \"\"\"Return the final options map. Returns: A dict of option names to options. \"\"\" # Build and store the inferred DisplayContext instance. self.options['dcontext'] = self.dcontext # Add the full list of seen commodities. # # IMPORTANT: This is currently where the list of all commodities seen # from the parser lives. The # beancount.core.getters.get_commodities_map() routine uses this to # automatically generate a full list of directives. An alternative would # be to implement a plugin that enforces the generate of these # post-parsing so that they are always guaranteed to live within the # flow of entries. This would allow us to keep all the data in that list # of entries and to avoid depending on the options to store that output. self.options['commodities'] = self.commodities return self.options","title":"get_options()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.handle_list","text":"Handle a recursive list grammar rule, generically. Parameters: object_list \u2013 the current list of objects. new_object \u2013 the new object to be added. Returns: The new, updated list of objects. Source code in beancount/parser/grammar.py def handle_list(self, object_list, new_object): \"\"\"Handle a recursive list grammar rule, generically. Args: object_list: the current list of objects. new_object: the new object to be added. Returns: The new, updated list of objects. \"\"\" if object_list is None: object_list = [] if new_object is not None: object_list.append(new_object) return object_list","title":"handle_list()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.include","text":"Process an include directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. include_name \u2013 A string, the name of the file to include. Source code in beancount/parser/grammar.py def include(self, filename, lineno, include_filename): \"\"\"Process an include directive. Args: filename: current filename. lineno: current line number. include_name: A string, the name of the file to include. \"\"\" self.options['include'].append(include_filename)","title":"include()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.key_value","text":"Process a document directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account the document relates to. document_filename \u2013 A str, the name of the document file. Returns: A new KeyValue object. Source code in beancount/parser/grammar.py def key_value(self, key, value): \"\"\"Process a document directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account the document relates to. document_filename: A str, the name of the document file. Returns: A new KeyValue object. \"\"\" return KeyValue(key, value)","title":"key_value()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.note","text":"Process a note directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to attach the note to. comment \u2013 A str, the note's comments contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Note object. Source code in beancount/parser/grammar.py def note(self, filename, lineno, date, account, comment, kvlist): \"\"\"Process a note directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to attach the note to. comment: A str, the note's comments contents. kvlist: a list of KeyValue instances. Returns: A new Note object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Note(meta, date, account, comment)","title":"note()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.open","text":"Process an open directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. currencies \u2013 A list of constraint currencies. booking_str \u2013 A string, the booking method, or None if none was specified. kvlist \u2013 a list of KeyValue instances. Returns: A new Open object. Source code in beancount/parser/grammar.py def open(self, filename, lineno, date, account, currencies, booking_str, kvlist): \"\"\"Process an open directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. currencies: A list of constraint currencies. booking_str: A string, the booking method, or None if none was specified. kvlist: a list of KeyValue instances. Returns: A new Open object. \"\"\" meta = new_metadata(filename, lineno, kvlist) error = False if booking_str: try: # Note: Somehow the 'in' membership operator is not defined on Enum. booking = Booking[booking_str] except KeyError: # If the per-account method is invalid, set it to the global # default method and continue. booking = self.options['booking_method'] error = True else: booking = None entry = Open(meta, date, account, currencies, booking) if error: self.errors.append(ParserError(meta, \"Invalid booking method: {}\".format(booking_str), entry)) return entry","title":"open()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.option","text":"Process an option directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. key \u2013 option's key (str) value \u2013 option's value Source code in beancount/parser/grammar.py def option(self, filename, lineno, key, value): \"\"\"Process an option directive. Args: filename: current filename. lineno: current line number. key: option's key (str) value: option's value \"\"\" if key not in self.options: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Invalid option: '{}'\".format(key), None)) elif key in options.READ_ONLY_OPTIONS: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Option '{}' may not be set\".format(key), None)) else: option_descriptor = options.OPTIONS[key] # Issue a warning if the option is deprecated. if option_descriptor.deprecated: assert isinstance(option_descriptor.deprecated, str), \"Internal error.\" meta = new_metadata(filename, lineno) self.errors.append( DeprecatedError(meta, option_descriptor.deprecated, None)) # Rename the option if it has an alias. if option_descriptor.alias: key = option_descriptor.alias option_descriptor = options.OPTIONS[key] # Convert the value, if necessary. if option_descriptor.converter: try: value = option_descriptor.converter(value) except ValueError as exc: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Error for option '{}': {}\".format(key, exc), None)) return option = self.options[key] if isinstance(option, list): # Append to a list of values. option.append(value) elif isinstance(option, dict): # Set to a dict of values. if not (isinstance(value, tuple) and len(value) == 2): self.errors.append( ParserError( meta, \"Error for option '{}': {}\".format(key, value), None)) return dict_key, dict_value = value option[dict_key] = dict_value elif isinstance(option, bool): # Convert to a boolean. if not isinstance(value, bool): value = (value.lower() in {'true', 'on'}) or (value == '1') self.options[key] = value else: # Set the value. self.options[key] = value # Refresh the list of valid account regexps as we go along. if key.startswith('name_'): # Update the set of valid account types. self.account_regexp = valid_account_regexp(self.options) elif key == 'insert_pythonpath': # Insert the PYTHONPATH to this file when and only if you # encounter this option. sys.path.insert(0, path.dirname(filename))","title":"option()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pad","text":"Process a pad directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to be padded. source_account \u2013 A string, the account to pad from. kvlist \u2013 a list of KeyValue instances. Returns: A new Pad object. Source code in beancount/parser/grammar.py def pad(self, filename, lineno, date, account, source_account, kvlist): \"\"\"Process a pad directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to be padded. source_account: A string, the account to pad from. kvlist: a list of KeyValue instances. Returns: A new Pad object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Pad(meta, date, account, source_account)","title":"pad()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pipe_deprecated_error","text":"Issue a 'Pipe deprecated' error. Parameters: filename \u2013 The current filename lineno \u2013 The current line number Source code in beancount/parser/grammar.py def pipe_deprecated_error(self, filename, lineno): \"\"\"Issue a 'Pipe deprecated' error. Args: filename: The current filename lineno: The current line number \"\"\" if self.options['allow_pipe_separator']: return meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, \"Pipe symbol is deprecated.\", None))","title":"pipe_deprecated_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.plugin","text":"Process a plugin directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. plugin_name \u2013 A string, the name of the plugin module to import. plugin_config \u2013 A string or None, an optional configuration string to pass in to the plugin module. Source code in beancount/parser/grammar.py def plugin(self, filename, lineno, plugin_name, plugin_config): \"\"\"Process a plugin directive. Args: filename: current filename. lineno: current line number. plugin_name: A string, the name of the plugin module to import. plugin_config: A string or None, an optional configuration string to pass in to the plugin module. \"\"\" self.options['plugin'].append((plugin_name, plugin_config))","title":"plugin()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.popmeta","text":"Removed a key off the current set of stacks. Parameters: key \u2013 A string, a key to be removed from the meta dict. Source code in beancount/parser/grammar.py def popmeta(self, key): \"\"\"Removed a key off the current set of stacks. Args: key: A string, a key to be removed from the meta dict. \"\"\" try: if key not in self.meta: raise IndexError value_list = self.meta[key] value_list.pop(-1) if not value_list: self.meta.pop(key) except IndexError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent metadata key: '{}'\".format(key), None))","title":"popmeta()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.poptag","text":"Pop a tag off the current set of stacks. Parameters: tag \u2013 A string, a tag to be removed from the current set of tags. Source code in beancount/parser/grammar.py def poptag(self, tag): \"\"\"Pop a tag off the current set of stacks. Args: tag: A string, a tag to be removed from the current set of tags. \"\"\" try: self.tags.remove(tag) except ValueError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent tag: '{}'\".format(tag), None))","title":"poptag()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.posting","text":"Process a posting grammar rule. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. account \u2013 A string, the account of the posting. units \u2013 An instance of Amount for the units. cost \u2013 An instance of CostSpec for the cost. price \u2013 Either None, or an instance of Amount that is the cost of the position. istotal \u2013 A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag \u2013 A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. Source code in beancount/parser/grammar.py def posting(self, filename, lineno, account, units, cost, price, istotal, flag): \"\"\"Process a posting grammar rule. Args: filename: the current filename. lineno: the current line number. account: A string, the account of the posting. units: An instance of Amount for the units. cost: An instance of CostSpec for the cost. price: Either None, or an instance of Amount that is the cost of the position. istotal: A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag: A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. \"\"\" meta = new_metadata(filename, lineno) # Prices may not be negative. if price and isinstance(price.number, Decimal) and price.number < ZERO: self.errors.append( ParserError(meta, ( \"Negative prices are not allowed: {} \" \"(see http://furius.ca/beancount/doc/bug-negative-prices \" \"for workaround)\" ).format(price), None)) # Fix it and continue. price = Amount(abs(price.number), price.currency) # If the price is specified for the entire amount, compute the effective # price here and forget about that detail of the input syntax. if istotal: if units.number == ZERO: number = ZERO else: number = price.number if number is not MISSING: number = number/abs(units.number) price = Amount(number, price.currency) # Note: Allow zero prices because we need them for round-trips for # conversion entries. # # if price is not None and price.number == ZERO: # self.errors.append( # ParserError(meta, \"Price is zero: {}\".format(price), None)) # If both cost and price are specified, the currencies must match, or # that is an error. if (cost is not None and price is not None and isinstance(cost.currency, str) and isinstance(price.currency, str) and cost.currency != price.currency): self.errors.append( ParserError(meta, \"Cost and price currencies must match: {} != {}\".format( cost.currency, price.currency), None)) return Posting(account, units, cost, price, chr(flag) if flag else None, meta)","title":"posting()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.price","text":"Process a price directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. currency \u2013 the currency to be priced. amount \u2013 an instance of Amount, that is the price of the currency. kvlist \u2013 a list of KeyValue instances. Returns: A new Price object. Source code in beancount/parser/grammar.py def price(self, filename, lineno, date, currency, amount, kvlist): \"\"\"Process a price directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. currency: the currency to be priced. amount: an instance of Amount, that is the price of the currency. kvlist: a list of KeyValue instances. Returns: A new Price object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Price(meta, date, currency, amount)","title":"price()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pushmeta","text":"Set a metadata field on the current key-value pairs to be added to transactions. Parameters: key_value \u2013 A KeyValue instance, to be added to the dict of metadata. Source code in beancount/parser/grammar.py def pushmeta(self, key, value): \"\"\"Set a metadata field on the current key-value pairs to be added to transactions. Args: key_value: A KeyValue instance, to be added to the dict of metadata. \"\"\" self.meta[key].append(value)","title":"pushmeta()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pushtag","text":"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Parameters: tag \u2013 A string, a tag to be added. Source code in beancount/parser/grammar.py def pushtag(self, tag): \"\"\"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Args: tag: A string, a tag to be added. \"\"\" self.tags.append(tag)","title":"pushtag()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.query","text":"Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. query_name \u2013 a str, the name of the query. query_string \u2013 a str, the SQL query itself. kvlist \u2013 a list of KeyValue instances. Returns: A new Query object. Source code in beancount/parser/grammar.py def query(self, filename, lineno, date, query_name, query_string, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. query_name: a str, the name of the query. query_string: a str, the SQL query itself. kvlist: a list of KeyValue instances. Returns: A new Query object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Query(meta, date, query_name, query_string)","title":"query()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.store_result","text":"Start rule stores the final result here. Parameters: entries \u2013 A list of entries to store. Source code in beancount/parser/grammar.py def store_result(self, entries): \"\"\"Start rule stores the final result here. Args: entries: A list of entries to store. \"\"\" if entries: self.entries = entries","title":"store_result()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_LINK","text":"Add a link to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. link \u2013 A string, the new link to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_LINK(self, tags_links, link): \"\"\"Add a link to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. link: A string, the new link to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.links.add(link) return tags_links","title":"tag_link_LINK()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_STRING","text":"Add a string to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. string \u2013 A string, the new string to insert in the list. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_STRING(self, tags_links, string): \"\"\"Add a string to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. string: A string, the new string to insert in the list. Returns: An updated TagsLinks instance. \"\"\" tags_links.strings.append(string) return tags_links","title":"tag_link_STRING()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_TAG","text":"Add a tag to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. tag \u2013 A string, the new tag to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_TAG(self, tags_links, tag): \"\"\"Add a tag to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. tag: A string, the new tag to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.tags.add(tag) return tags_links","title":"tag_link_TAG()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_new","text":"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. Source code in beancount/parser/grammar.py def tag_link_new(self, _): \"\"\"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. \"\"\" return TagsLinks(set(), set())","title":"tag_link_new()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.transaction","text":"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. flag \u2013 a str, one-character, the flag associated with this transaction. txn_strings \u2013 A list of strings, possibly empty, possibly longer. tags_links \u2013 A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list \u2013 a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. Source code in beancount/parser/grammar.py def transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list): \"\"\"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Args: filename: the current filename. lineno: the current line number. date: a datetime object. flag: a str, one-character, the flag associated with this transaction. txn_strings: A list of strings, possibly empty, possibly longer. tags_links: A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list: a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. \"\"\" meta = new_metadata(filename, lineno) # Separate postings and key-values. explicit_meta = {} postings = [] tags, links = tags_links.tags, tags_links.links if posting_or_kv_list: last_posting = None for posting_or_kv in posting_or_kv_list: if isinstance(posting_or_kv, Posting): postings.append(posting_or_kv) last_posting = posting_or_kv elif isinstance(posting_or_kv, TagsLinks): if postings: self.errors.append(ParserError( meta, \"Tags or links not allowed after first \" + \"Posting: {}\".format(posting_or_kv), None)) else: tags.update(posting_or_kv.tags) links.update(posting_or_kv.links) else: if last_posting is None: value = explicit_meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate metadata field on entry: {}\".format( posting_or_kv), None)) else: if last_posting.meta is None: last_posting = last_posting._replace(meta={}) postings.pop(-1) postings.append(last_posting) value = last_posting.meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate posting metadata field: {}\".format( posting_or_kv), None)) # Freeze the tags & links or set to default empty values. tags, links = self.finalize_tags_links(tags, links) # Initialize the metadata fields from the set of active values. if self.meta: for key, value_list in self.meta.items(): meta[key] = value_list[-1] # Add on explicitly defined values. if explicit_meta: meta.update(explicit_meta) # Unpack the transaction fields. payee_narration = self.unpack_txn_strings(txn_strings, meta) if payee_narration is None: return None payee, narration = payee_narration # We now allow a single posting when its balance is zero, so we # commented out the check below. If a transaction has a single posting # with a non-zero balance, it'll get caught below in the booking code. # # # Detect when a transaction does not have at least two legs. # if postings is None or len(postings) < 2: # self.errors.append( # ParserError(meta, # \"Transaction with only one posting: {}\".format(postings), # None)) # return None # If there are no postings, make sure we insert a list object. if postings is None: postings = [] # Create the transaction. return Transaction(meta, date, chr(flag), payee, narration, tags, links, postings)","title":"transaction()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.unpack_txn_strings","text":"Unpack a tags_links accumulator to its payee and narration fields. Parameters: txn_strings \u2013 A list of strings. meta \u2013 A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. Source code in beancount/parser/grammar.py def unpack_txn_strings(self, txn_strings, meta): \"\"\"Unpack a tags_links accumulator to its payee and narration fields. Args: txn_strings: A list of strings. meta: A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. \"\"\" num_strings = 0 if txn_strings is None else len(txn_strings) if num_strings == 1: payee, narration = None, txn_strings[0] elif num_strings == 2: payee, narration = txn_strings elif num_strings == 0: payee, narration = None, \"\" else: self.errors.append( ParserError(meta, \"Too many strings on transaction description: {}\".format( txn_strings), None)) return None return payee, narration","title":"unpack_txn_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount","text":"CompoundAmount(number_per, number_total, currency)","title":"CompoundAmount"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__new__","text":"Create new instance of CompoundAmount(number_per, number_total, currency)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError","text":"DeprecatedError(source, message, entry)","title":"DeprecatedError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__new__","text":"Create new instance of DeprecatedError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue","text":"KeyValue(key, value)","title":"KeyValue"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__new__","text":"Create new instance of KeyValue(key, value)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError","text":"ParserError(source, message, entry)","title":"ParserError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__new__","text":"Create new instance of ParserError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError","text":"ParserSyntaxError(source, message, entry)","title":"ParserSyntaxError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__new__","text":"Create new instance of ParserSyntaxError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks","text":"TagsLinks(tags, links)","title":"TagsLinks"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__new__","text":"Create new instance of TagsLinks(tags, links)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType","text":"ValueType(value, dtype)","title":"ValueType"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__new__","text":"Create new instance of ValueType(value, dtype)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.valid_account_regexp","text":"Build a regexp to validate account names from the options. Parameters: options \u2013 A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. Source code in beancount/parser/grammar.py def valid_account_regexp(options): \"\"\"Build a regexp to validate account names from the options. Args: options: A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. \"\"\" names = map(options.__getitem__, ('name_assets', 'name_liabilities', 'name_equity', 'name_income', 'name_expenses')) # Replace the first term of the account regular expression with the specific # names allowed under the options configuration. This code is kept in sync # with {5672c7270e1e}. return re.compile(\"(?:{})(?:{}{})+\".format('|'.join(names), account.sep, account.ACC_COMP_NAME_RE))","title":"valid_account_regexp()"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc","text":"Compute a hash of the source files in order to warn when the source goes out of date.","title":"hashsrc"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc.check_parser_source_files","text":"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). Source code in beancount/parser/hashsrc.py def check_parser_source_files(): \"\"\"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). \"\"\" parser_source_hash = hash_parser_source_files() if parser_source_hash is None: return # pylint: disable=import-outside-toplevel from . import _parser if _parser.SOURCE_HASH and _parser.SOURCE_HASH != parser_source_hash: warnings.warn( (\"The Beancount parser C extension module is out-of-date ('{}' != '{}'). \" \"You need to rebuild.\").format(_parser.SOURCE_HASH, parser_source_hash))","title":"check_parser_source_files()"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc.hash_parser_source_files","text":"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. Source code in beancount/parser/hashsrc.py def hash_parser_source_files(): \"\"\"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. \"\"\" md5 = hashlib.md5() for filename in PARSER_SOURCE_FILES: fullname = path.join(path.dirname(__file__), filename) if not path.exists(fullname): return None with open(fullname, 'rb') as file: md5.update(file.read()) # Note: Prepend a character in front of the hash because under Windows MSDEV # removes escapes, and if the hash starts with a number it fails to # recognize this is a string. A small compromise for portability. return md5.hexdigest()","title":"hash_parser_source_files()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer","text":"Beancount syntax lexer.","title":"lexer"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder","text":"A builder used only for building lexer objects. Attributes: Name Type Description long_string_maxlines_default Number of lines for a string to trigger a warning. This is meant to help users detecting dangling quotes in their source.","title":"LexBuilder"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.ACCOUNT","text":"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Parameters: account_name \u2013 a str, the valid name of an account. Returns: A string, the name of the account. Source code in beancount/parser/lexer.py def ACCOUNT(self, account_name): \"\"\"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Args: account_name: a str, the valid name of an account. Returns: A string, the name of the account. \"\"\" # Check account name validity. if not self.account_regexp.match(account_name): raise ValueError(\"Invalid account name: {}\".format(account_name)) # Reuse (intern) account strings as much as possible. This potentially # reduces memory usage a fair bit, because these strings are repeated # liberally. return self.accounts.setdefault(account_name, account_name)","title":"ACCOUNT()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.CURRENCY","text":"Process a CURRENCY token. Parameters: currency_name \u2013 the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. Source code in beancount/parser/lexer.py def CURRENCY(self, currency_name): \"\"\"Process a CURRENCY token. Args: currency_name: the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. \"\"\" self.commodities.add(currency_name) return currency_name","title":"CURRENCY()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.DATE","text":"Process a DATE token. Parameters: year \u2013 integer year. month \u2013 integer month. day \u2013 integer day Returns: A new datetime object. Source code in beancount/parser/lexer.py def DATE(self, year, month, day): \"\"\"Process a DATE token. Args: year: integer year. month: integer month. day: integer day Returns: A new datetime object. \"\"\" return datetime.date(year, month, day)","title":"DATE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.KEY","text":"Process an identifier token. Parameters: ident \u2013 a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def KEY(self, ident): \"\"\"Process an identifier token. Args: ident: a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return ident","title":"KEY()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.LINK","text":"Process a LINK token. Parameters: link \u2013 a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def LINK(self, link): \"\"\"Process a LINK token. Args: link: a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return link","title":"LINK()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.NUMBER","text":"Process a NUMBER token. Convert into Decimal. Parameters: number \u2013 a str, the number to be converted. Returns: A Decimal instance built of the number string. Source code in beancount/parser/lexer.py def NUMBER(self, number): \"\"\"Process a NUMBER token. Convert into Decimal. Args: number: a str, the number to be converted. Returns: A Decimal instance built of the number string. \"\"\" # Note: We don't use D() for efficiency here. # The lexer will only yield valid number strings. if ',' in number: # Extract the integer part and check the commas match the # locale-aware formatted version. This match = re.match(r\"([\\d,]*)(\\.\\d*)?$\", number) if not match: # This path is never taken because the lexer will parse a comma # in the fractional part as two NUMBERs with a COMMA token in # between. self.errors.append( LexerError(self.get_lexer_location(), \"Invalid number format: '{}'\".format(number), None)) else: int_string, float_string = match.groups() reformatted_number = r\"{:,.0f}\".format(int(int_string.replace(\",\", \"\"))) if int_string != reformatted_number: self.errors.append( LexerError(self.get_lexer_location(), \"Invalid commas: '{}'\".format(number), None)) number = number.replace(',', '') return Decimal(number)","title":"NUMBER()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.STRING","text":"Process a STRING token. Parameters: string \u2013 the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. Source code in beancount/parser/lexer.py def STRING(self, string): \"\"\"Process a STRING token. Args: string: the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. \"\"\" # If a multiline string, warm over a certain number of lines. if '\\n' in string: num_lines = string.count('\\n') + 1 if num_lines > self.long_string_maxlines_default: # This is just a warning; accept the string anyhow. self.errors.append( LexerError( self.get_lexer_location(), \"String too long ({} lines); possible error\".format(num_lines), None)) return string","title":"STRING()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.TAG","text":"Process a TAG token. Parameters: tag \u2013 a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. Source code in beancount/parser/lexer.py def TAG(self, tag): \"\"\"Process a TAG token. Args: tag: a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. \"\"\" return tag","title":"TAG()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.build_lexer_error","text":"Build a lexer error and appends it to the list of pending errors. Parameters: message \u2013 The message of the error. exc_type \u2013 An exception type, if an exception occurred. Source code in beancount/parser/lexer.py def build_lexer_error(self, message, exc_type=None): # {0e31aeca3363} \"\"\"Build a lexer error and appends it to the list of pending errors. Args: message: The message of the error. exc_type: An exception type, if an exception occurred. \"\"\" if not isinstance(message, str): message = str(message) if exc_type is not None: message = '{}: {}'.format(exc_type.__name__, message) self.errors.append( LexerError(self.get_lexer_location(), message, None))","title":"build_lexer_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.get_invalid_account","text":"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. Source code in beancount/parser/lexer.py def get_invalid_account(self): \"\"\"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. \"\"\" return 'Equity:InvalidAccountName'","title":"get_invalid_account()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError","text":"LexerError(source, message, entry)","title":"LexerError"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/lexer.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__new__","text":"Create new instance of LexerError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/lexer.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.lex_iter","text":"An iterator that yields all the tokens in the given file. Parameters: file \u2013 A string, the filename to run the lexer on, or a file object. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). Source code in beancount/parser/lexer.py def lex_iter(file, builder=None, encoding=None): \"\"\"An iterator that yields all the tokens in the given file. Args: file: A string, the filename to run the lexer on, or a file object. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). \"\"\" if isinstance(file, str): filename = file else: filename = file.name if builder is None: builder = LexBuilder() _parser.lexer_initialize(filename, builder, encoding) try: while 1: token_tuple = _parser.lexer_next() if token_tuple is None: break yield token_tuple finally: _parser.lexer_finalize()","title":"lex_iter()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.lex_iter_string","text":"Parse an input string and print the tokens to an output file. Parameters: input_string \u2013 a str or bytes, the contents of the ledger to be parsed. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. Source code in beancount/parser/lexer.py def lex_iter_string(string, builder=None, encoding=None): \"\"\"Parse an input string and print the tokens to an output file. Args: input_string: a str or bytes, the contents of the ledger to be parsed. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. \"\"\" tmp_file = tempfile.NamedTemporaryFile('w' if isinstance(string, str) else 'wb') tmp_file.write(string) tmp_file.flush() # Note: We pass in the file object in order to keep it alive during parsing. return lex_iter(tmp_file, builder, encoding)","title":"lex_iter_string()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options","text":"Declaration of options and their default values.","title":"options"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc","text":"OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"OptDesc"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__new__","text":"Create new instance of OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup","text":"OptGroup(description, options)","title":"OptGroup"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__new__","text":"Create new instance of OptGroup(description, options)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.Opt","text":"Alternative constructor for OptDesc, with default values. Parameters: name \u2013 See OptDesc. default_value \u2013 See OptDesc. example_value \u2013 See OptDesc. converter \u2013 See OptDesc. deprecated \u2013 See OptDesc. alias \u2013 See OptDesc. Returns: An instance of OptDesc. Source code in beancount/parser/options.py def Opt(name, default_value, example_value=UNSET, converter=None, deprecated=False, alias=None): \"\"\"Alternative constructor for OptDesc, with default values. Args: name: See OptDesc. default_value: See OptDesc. example_value: See OptDesc. converter: See OptDesc. deprecated: See OptDesc. alias: See OptDesc. Returns: An instance of OptDesc. \"\"\" if example_value is UNSET: example_value = default_value return OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"Opt()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_account_types","text":"Extract the account type names from the parser's options. Parameters: options \u2013 a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. Source code in beancount/parser/options.py def get_account_types(options): \"\"\"Extract the account type names from the parser's options. Args: options: a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. \"\"\" return account_types.AccountTypes( *[options[key] for key in (\"name_assets\", \"name_liabilities\", \"name_equity\", \"name_income\", \"name_expenses\")])","title":"get_account_types()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_current_accounts","text":"Return account names for the current earnings and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. Source code in beancount/parser/options.py def get_current_accounts(options): \"\"\"Return account names for the current earnings and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. \"\"\" equity = options['name_equity'] account_current_earnings = account.join(equity, options['account_current_earnings']) account_current_conversions = account.join(equity, options['account_current_conversions']) return (account_current_earnings, account_current_conversions)","title":"get_current_accounts()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_previous_accounts","text":"Return account names for the previous earnings, balances and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. Source code in beancount/parser/options.py def get_previous_accounts(options): \"\"\"Return account names for the previous earnings, balances and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. \"\"\" equity = options['name_equity'] account_previous_earnings = account.join(equity, options['account_previous_earnings']) account_previous_balances = account.join(equity, options['account_previous_balances']) account_previous_conversions = account.join(equity, options['account_previous_conversions']) return (account_previous_earnings, account_previous_balances, account_previous_conversions)","title":"get_previous_accounts()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.list_options","text":"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. Source code in beancount/parser/options.py def list_options(): \"\"\"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. \"\"\" oss = io.StringIO() for group in PUBLIC_OPTION_GROUPS: for desc in group.options: oss.write('option \"{}\" \"{}\"\\n'.format(desc.name, desc.example_value)) if desc.deprecated: oss.write(textwrap.fill( \"THIS OPTION IS DEPRECATED: {}\".format(desc.deprecated), initial_indent=\" \", subsequent_indent=\" \")) oss.write('\\n\\n') description = ' '.join(line.strip() for line in group.description.strip().splitlines()) oss.write(textwrap.fill(description, initial_indent=' ', subsequent_indent=' ')) oss.write('\\n') if isinstance(desc.default_value, (list, dict, set)): oss.write('\\n') oss.write(' (This option may be supplied multiple times.)\\n') oss.write('\\n\\n') return oss.getvalue()","title":"list_options()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_booking_method","text":"Validate a booking method name. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_booking_method(value): \"\"\"Validate a booking method name. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" try: return data.Booking[value] except KeyError as exc: raise ValueError(str(exc))","title":"options_validate_booking_method()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_boolean","text":"Validate a boolean option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_boolean(value): \"\"\"Validate a boolean option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return value.lower() in ('1', 'true', 'yes')","title":"options_validate_boolean()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_plugin","text":"Validate the plugin option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_plugin(value): \"\"\"Validate the plugin option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the 'plugin' option specially: accept an optional # argument from it. NOTE: We will eventually phase this out and # replace it by a dedicated 'plugin' directive. match = re.match('(.*):(.*)', value) if match: plugin_name, plugin_config = match.groups() else: plugin_name, plugin_config = value, None return (plugin_name, plugin_config)","title":"options_validate_plugin()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_processing_mode","text":"Validate the options processing mode. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_processing_mode(value): \"\"\"Validate the options processing mode. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" if value not in ('raw', 'default'): raise ValueError(\"Invalid value '{}'\".format(value)) return value","title":"options_validate_processing_mode()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_tolerance","text":"Validate the tolerance option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance(value): \"\"\"Validate the tolerance option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return D(value)","title":"options_validate_tolerance()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_tolerance_map","text":"Validate an option with a map of currency/tolerance pairs in a string. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance_map(value): \"\"\"Validate an option with a map of currency/tolerance pairs in a string. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the setting of a key-value, whereby the value is a Decimal # representation. match = re.match('(.*):(.*)', value) if not match: raise ValueError(\"Invalid value '{}'\".format(value)) currency, tolerance_str = match.groups() return (currency, D(tolerance_str))","title":"options_validate_tolerance_map()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser","text":"Beancount syntax parser. IMPORTANT: The parser (and its grammar builder) produces \"incomplete\" Transaction objects. This means that some of the data can be found missing from the output of the parser and some of the data types vary slightly. Missing components are replaced not by None, but by a special constant 'NA' which helps diagnose problems if a user inadvertently attempts to work on an incomplete posting instead of a complete one. Those incomplete entries are then run through the \"booking\" routines which do two things simultaneously: They find matching lots for reducing inventory positions, and They interpolate missing numbers. In doing so they normalize the entries to \"complete\" entries by converting a position/lot's \"cost\" attribute from a CostSpec to a Cost. A Cost is similar to an Amount in that it shares \"number\" and \"currency\" attributes, but also has a label and a lot date. A CostSpec is similar to a Cost, but has all optional data; it consists in a specification for matching against a particular inventory lot. Other parts of a posting may also be missing, not just parts of the cost. Leaving out some parts of the input is used to invoke interpolation, to tell Beancount to automatically compute the missing numbers (if possible). Missing components will be set to the special value \"beancount.core.number.MISSING\" until inventory booking and number interpolation has been completed. The \"MISSING\" value should never appear in completed, loaded transaction postings. For instance, all of the units may be missing: INPUT: Assets:Account posting.units = MISSING Or just the number of the units: INPUT: Assets:Account USD posting.units = Amount(MISSING, \"USD\") You must always specify the currency. If a price annotation is simply absent, it appears as None: INPUT: Assets:Account 2 MXN posting.price = None However, you may indicate that there is a price but have Beancount compute it automatically: INPUT: Assets:Account 2 MXN @ posting.price = Amount(MISSING, MISSING) Indicating the conversion currency is also possible (and recommended): INPUT: Assets:Account 2 MXN @ USD posting.price = Amount(MISSING, \"USD\") If a cost specification is provided, a \"cost\" attribute it set but it does not refer to a Cost instance (as in complete entries) but rather to a CostSpec instance. Some of the fields of a CostSpec may be MISSING if they were not specified in the input. For example: INPUT: Assets:Account 1 HOOL {100 # 5 USD} posting.cost = CostSpec(Decimal(\"100\"), Decimal(\"5\"), \"USD\", None, None, False)) Note how we never consider the label of date override to be MISSING; this is because those inputs are optional: A missing label is simply left unset in the computed Cost, and a missing date override uses the date of the transaction that contains the posting. You can indicate that there is a total number to be filled in like this: INPUT: Assets:Account 1 HOOL {100 # USD} posting.cost = CostSpec(Decimal(\"100\"), MISSING, \"USD\", None, None, False)) This is in contrast to the total value simple not being used: INPUT: Assets:Account 1 HOOL {100 USD} posting.cost = CostSpec(Decimal(\"100\"), None, \"USD\", None, None, False)) Both per-unit and total numbers may be omitted as well, in which case, only the number-per-unit portion of the CostSpec will appear as MISSING: INPUT: Assets:Account 1 HOOL {USD} posting.cost = CostSpec(MISSING, None, \"USD\", None, None, False)) And furthermore, all the cost basis may be missing: INPUT: Assets:Account 1 HOOL {} posting.cost = CostSpec(MISSING, None, MISSING, None, None, False)) If you ask for the lots to be merged, you get this: INPUT: Assets:Account 1 HOOL {*} posting.cost = CostSpec(MISSING, None, MISSING, None, None, True)) The numbers have to be computed by Beancount, so we output this with MISSING values. Of course, you can provide only the non-basis information, like just the date or label: INPUT: Assets:Account 1 HOOL {2015-09-21} posting.cost = CostSpec(MISSING, None, MISSING, date(2015, 9, 21), None, True) See the test beancount.parser.grammar_test.TestIncompleteInputs for examples and corresponding expected values.","title":"parser"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.is_entry_incomplete","text":"Detect the presence of elided amounts in Transactions. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_entry_incomplete(entry): \"\"\"Detect the presence of elided amounts in Transactions. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" if isinstance(entry, data.Transaction): if any(is_posting_incomplete(posting) for posting in entry.postings): return True return False","title":"is_entry_incomplete()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.is_posting_incomplete","text":"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_posting_incomplete(posting): \"\"\"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" units = posting.units if (units is MISSING or units.number is MISSING or units.currency is MISSING): return True price = posting.price if (price is MISSING or price is not None and (price.number is MISSING or price.currency is MISSING)): return True cost = posting.cost if cost is not None and (cost.number_per is MISSING or cost.number_total is MISSING or cost.currency is MISSING): return True return False","title":"is_posting_incomplete()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_doc","text":"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete \u2013 A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. Source code in beancount/parser/parser.py def parse_doc(expect_errors=False, allow_incomplete=False): \"\"\"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete: A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: the function object to be decorated. Returns: A decorated test function. \"\"\" filename = inspect.getfile(fun) lines, lineno = inspect.getsourcelines(fun) # decorator line + function definition line (I realize this is largely # imperfect, but it's only for reporting in our tests) - empty first line # stripped away. lineno += 1 @functools.wraps(fun) def wrapper(self): assert fun.__doc__ is not None, ( \"You need to insert a docstring on {}\".format(fun.__name__)) entries, errors, options_map = parse_string(fun.__doc__, report_filename=filename, report_firstline=lineno, dedent=True) if not allow_incomplete and any(is_entry_incomplete(entry) for entry in entries): self.fail(\"parse_doc() may not use interpolation.\") if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator","title":"parse_doc()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_file","text":"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: filename \u2013 the name of the file to be parsed. kw \u2013 a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) Source code in beancount/parser/parser.py def parse_file(filename, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: filename: the name of the file to be parsed. kw: a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) \"\"\" abs_filename = path.abspath(filename) if filename else None builder = grammar.Builder(abs_filename) _parser.parse_file(filename, builder, **kw) return builder.finalize()","title":"parse_file()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_many","text":"Parse a string with a snippet of Beancount input and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_many(string, level=0): \"\"\"Parse a string with a snippet of Beancount input and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" # Get the locals in the stack for the callers and produce the final text. frame = inspect.stack()[level+1] varkwds = frame[0].f_locals input_string = textwrap.dedent(string.format(**varkwds)) # Parse entries and check there are no errors. entries, errors, __ = parse_string(input_string) assert not errors return entries","title":"parse_many()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_one","text":"Parse a string with single Beancount directive and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_one(string): \"\"\"Parse a string with single Beancount directive and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" entries = parse_many(string, level=1) assert len(entries) == 1 return entries[0]","title":"parse_one()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_string","text":"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: string \u2013 A string, the contents to be parsed instead of a file's. report_filename \u2013 A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw \u2013 See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Returns: Same as the output of parse_file(). Source code in beancount/parser/parser.py def parse_string(string, report_filename=None, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: string: A string, the contents to be parsed instead of a file's. report_filename: A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw: See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Return: Same as the output of parse_file(). \"\"\" if kw.pop('dedent', None): string = textwrap.dedent(string) builder = grammar.Builder(report_filename or '') _parser.parse_string(string, builder, report_filename=report_filename, **kw) return builder.finalize()","title":"parse_string()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer","text":"Conversion from internal data structures to text.","title":"printer"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter","text":"A multi-method interface for printing all directive types. Attributes: Name Type Description dcontext An instance of DisplayContext with which to render all the numbers. render_weight A boolean, true if we should render the weight of the postings as a comment, for debugging. min_width_account An integer, the minimum width to leave for the account name.","title":"EntryPrinter"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.__call__","text":"Render a directive. Parameters: obj \u2013 The directive to be rendered. Returns: A string, the rendered directive. Source code in beancount/parser/printer.py def __call__(self, obj): \"\"\"Render a directive. Args: obj: The directive to be rendered. Returns: A string, the rendered directive. \"\"\" oss = io.StringIO() method = getattr(self, obj.__class__.__name__) method(obj, oss) return oss.getvalue()","title":"__call__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.render_posting_strings","text":"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Parameters: posting \u2013 An instance of Posting, the posting to render. Returns: A tuple of flag_account \u2013 A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. Source code in beancount/parser/printer.py def render_posting_strings(self, posting): \"\"\"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Args: posting: An instance of Posting, the posting to render. Returns: A tuple of flag_account: A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. \"\"\" # Render a string of the flag and the account. flag = '{} '.format(posting.flag) if posting.flag else '' flag_account = flag + posting.account # Render a string with the amount and cost and optional price, if # present. Also render a string with the weight. weight_str = '' if isinstance(posting.units, amount.Amount): position_str = position.to_string(posting, self.dformat) # Note: we render weights at maximum precision, for debugging. if posting.cost is None or (isinstance(posting.cost, position.Cost) and isinstance(posting.cost.number, Decimal)): weight_str = str(convert.get_weight(posting)) else: position_str = '' if posting.price is not None: position_str += ' @ {}'.format(posting.price.to_string(self.dformat_max)) return flag_account, position_str, weight_str","title":"render_posting_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.write_metadata","text":"Write metadata to the file object, excluding filename and line number. Parameters: meta \u2013 A dict that contains the metadata for this directive. oss \u2013 A file object to write to. Source code in beancount/parser/printer.py def write_metadata(self, meta, oss, prefix=None): \"\"\"Write metadata to the file object, excluding filename and line number. Args: meta: A dict that contains the metadata for this directive. oss: A file object to write to. \"\"\" if meta is None: return if prefix is None: prefix = self.prefix for key, value in sorted(meta.items()): if key not in self.META_IGNORE: value_str = None if isinstance(value, str): value_str = '\"{}\"'.format(misc_utils.escape_string(value)) elif isinstance(value, (Decimal, datetime.date, amount.Amount)): value_str = str(value) elif isinstance(value, bool): value_str = 'TRUE' if value else 'FALSE' elif isinstance(value, (dict, inventory.Inventory)): pass # Ignore dicts, don't print them out. elif value is None: value_str = '' # Render null metadata as empty, on purpose. else: raise ValueError(\"Unexpected value: '{!r}'\".format(value)) if value_str is not None: oss.write(\"{}{}: {}\\n\".format(prefix, key, value_str))","title":"write_metadata()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.align_position_strings","text":"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Parameters: strings \u2013 A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. Source code in beancount/parser/printer.py def align_position_strings(strings): \"\"\"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Args: strings: A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. \"\"\" # Maximum length before the alignment character. max_before = 0 # Maximum length after the alignment character. max_after = 0 # Maximum length of unknown strings. max_unknown = 0 string_items = [] search = re.compile('[A-Z]').search for string in strings: match = search(string) if match: index = match.start() if index != 0: max_before = max(index, max_before) max_after = max(len(string) - index, max_after) string_items.append((index, string)) continue # else max_unknown = max(len(string), max_unknown) string_items.append((None, string)) # Compute formatting string. max_total = max(max_before + max_after, max_unknown) max_after_prime = max_total - max_before fmt = \"{{:>{0}}}{{:{1}}}\".format(max_before, max_after_prime).format fmt_unknown = \"{{:<{0}}}\".format(max_total).format # Align the strings and return them. aligned_strings = [] for index, string in string_items: if index is not None: string = fmt(string[:index], string[index:]) else: string = fmt_unknown(string) aligned_strings.append(string) return aligned_strings, max_total","title":"align_position_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.format_entry","text":"Format an entry into a string in the same input syntax the parser accepts. Parameters: entry \u2013 An entry instance. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. Source code in beancount/parser/printer.py def format_entry(entry, dcontext=None, render_weights=False, prefix=None): \"\"\"Format an entry into a string in the same input syntax the parser accepts. Args: entry: An entry instance. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. \"\"\" return EntryPrinter(dcontext, render_weights, prefix=prefix)(entry)","title":"format_entry()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.format_error","text":"Given an error objects, return a formatted string for it. Parameters: error \u2013 a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. Source code in beancount/parser/printer.py def format_error(error): \"\"\"Given an error objects, return a formatted string for it. Args: error: a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. \"\"\" oss = io.StringIO() oss.write('{} {}\\n'.format(render_source(error.source), error.message)) if error.entry is not None: entries = error.entry if isinstance(error.entry, list) else [error.entry] error_string = '\\n'.join(format_entry(entry) for entry in entries) oss.write('\\n') oss.write(textwrap.indent(error_string, ' ')) oss.write('\\n') return oss.getvalue()","title":"format_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_entries","text":"A convenience function that prints a list of entries to a file. Parameters: entries \u2013 A list of directives. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None): \"\"\"A convenience function that prints a list of entries to a file. Args: entries: A list of directives. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" assert isinstance(entries, list), \"Entries is not a list: {}\".format(entries) output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) if prefix: output.write(prefix) previous_type = type(entries[0]) if entries else None eprinter = EntryPrinter(dcontext, render_weights) for entry in entries: # Insert a newline between transactions and between blocks of directives # of the same type. entry_type = type(entry) if (entry_type in (data.Transaction, data.Commodity) or entry_type is not previous_type): output.write('\\n') previous_type = entry_type string = eprinter(entry) output.write(string)","title":"print_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_entry","text":"A convenience function that prints a single entry to a file. Parameters: entry \u2013 A directive entry. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entry(entry, dcontext=None, render_weights=False, file=None): \"\"\"A convenience function that prints a single entry to a file. Args: entry: A directive entry. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) output.write(format_entry(entry, dcontext, render_weights)) output.write('\\n')","title":"print_entry()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_error","text":"A convenience function that prints a single error to a file. Parameters: error \u2013 An error object. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_error(error, file=None): \"\"\"A convenience function that prints a single error to a file. Args: error: An error object. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout output.write(format_error(error)) output.write('\\n')","title":"print_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_errors","text":"A convenience function that prints a list of errors to a file. Parameters: errors \u2013 A list of errors. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_errors(errors, file=None, prefix=None): \"\"\"A convenience function that prints a list of errors to a file. Args: errors: A list of errors. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout if prefix: output.write(prefix) for error in errors: output.write(format_error(error)) output.write('\\n')","title":"print_errors()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.render_source","text":"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Parameters: meta \u2013 A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. Source code in beancount/parser/printer.py def render_source(meta): \"\"\"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Args: meta: A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. \"\"\" return '{}:{:8}'.format(meta['filename'], '{}:'.format(meta['lineno']))","title":"render_source()"},{"location":"api_reference/beancount.plugins.html","text":"beancount.plugins \uf0c1 Example plugins for filtering transactions. These are various examples of how to filter entries in various creative ways. IMPORTANT: These are not meant to be complete features, rather just experiments in problem-solving using Beancount, work-in-progress that can be selectively installed via a --plugin option, or one-offs to answer questions on the mailing-list. beancount.plugins.auto \uf0c1 A plugin of plugins which triggers are all the automatic and lax plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. You can just import the \"auto\" plugin. Put that in a macro. Also see: the 'pedantic' plugin. beancount.plugins.auto_accounts \uf0c1 This module automatically inserts Open directives for accounts not opened (at the date of the first entry) and automatically removes open directives for unused accounts. This can be used as a convenience for doing demos, or when setting up your initial transactions, as an intermediate step. beancount.plugins.auto_accounts.auto_insert_open(entries, unused_options_map) \uf0c1 Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/auto_accounts.py def auto_insert_open(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" opened_accounts = {entry.account for entry in entries if isinstance(entry, data.Open)} new_entries = [] accounts_first, _ = getters.get_accounts_use_map(entries) for index, (account, date_first_used) in enumerate(sorted(accounts_first.items())): if account not in opened_accounts: meta = data.new_metadata('', index) new_entries.append(data.Open(meta, date_first_used, account, None, None)) if new_entries: new_entries.extend(entries) new_entries.sort(key=data.entry_sortkey) else: new_entries = entries return new_entries, [] beancount.plugins.book_conversions \uf0c1 A plugin that automatically converts postings at price to postings held at cost, applying an automatic booking algorithm in assigning the cost bases and matching lots. This plugin restricts itself to applying these transformations within a particular account, which you provide. For each of those accounts, it also requires a corresponding Income account to book the profit/loss of reducing lots (i.e., sales): plugin \"beancount.plugins.book_conversions\" \"Assets:Bitcoin,Income:Bitcoin\" Then, simply input the transactions with price conversion. We use \"Bitcoins\" in this example, converting Bitcoin purchases that were carried out as currency into maintaining these with cost basis, for tax reporting purposes: 2015-09-04 * \"Buy some bitcoins\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.333507 BTC @ 230.76 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.345747 BTC @ 230.11 USD 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -6.000000 BTC @ 230.50 USD Expenses:Something The result is that cost bases are inserted on augmenting lots: 2015-09-04 * \"Buy some bitcoins\" Assets:Bitcoin 4.333507 BTC {230.76 USD} @ 230.76 USD Assets:Bank -1000.00 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bitcoin 4.345747 BTC {230.11 USD} @ 230.11 USD Assets:Bank -1000.00 USD While on reducing lots, matching FIFO lots are automatically found and the corresponding cost basis added: 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -4.333507 BTC {230.76 USD} @ 230.50 USD Assets:Bitcoin -1.666493 BTC {230.11 USD} @ 230.50 USD Income:Bitcoin 0.47677955 USD Expenses:Something 1383.00000000 USD Note that multiple lots were required to fulfill the sale quantity here. As in this example, this may result in multiple lots being created for a single one. Finally, Beancount will eventually support booking methods built-in, but this is a quick method that shows how to hack your own booking method via transformations of the postings that run in a plugin. Implementation notes: This code uses the FIFO method only for now. However, it would be very easy to customize it to provide other booking methods, e.g. LIFO, or otherwise. This will be added eventually, and I'm hoping to reuse the same inventory abstractions that will be used to implement the fallback booking methods from the booking proposal review (http://furius.ca/beancount/doc/proposal-booking). Instead of keeping a list of (Position, Transaction) pairs for the pending FIFO lots, we really ought to use a beancount.core.inventory.Inventory instance. However, the class does not contain sufficient data to carry out FIFO booking at the moment. A newer implementation, living in the \"booking\" branch, does, and will be used in the future. This code assumes that a positive number of units is an augmenting lot and a reducing one has a negative number of units, though we never call them that way on purpose (to eventually allow this code to handle short positions). This is not strictly true; however, we would need an Inventory in order to figrue this out. This will be done in the future and is not difficult to do. IMPORTANT: This plugin was developed before the booking methods (FIFO, LIFO, and others) were fully implemented in Beancount. It was built to answer a question on the mailing-list about FIFO booking. You probably don't need to use them anymore. Always prefer to use the native syntax instead of this. beancount.plugins.book_conversions.BookConversionError ( tuple ) \uf0c1 BookConversionError(source, message, entry) beancount.plugins.book_conversions.BookConversionError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.book_conversions.BookConversionError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BookConversionError(source, message, entry) beancount.plugins.book_conversions.BookConversionError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.book_conversions.ConfigError ( tuple ) \uf0c1 ConfigError(source, message, entry) beancount.plugins.book_conversions.ConfigError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.book_conversions.ConfigError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ConfigError(source, message, entry) beancount.plugins.book_conversions.ConfigError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.book_conversions.augment_inventory(pending_lots, posting, entry, eindex) \uf0c1 Add the lots from the given posting to the running inventory. Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. entry \u2013 The parent transaction. eindex \u2013 The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. Source code in beancount/plugins/book_conversions.py def augment_inventory(pending_lots, posting, entry, eindex): \"\"\"Add the lots from the given posting to the running inventory. Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. entry: The parent transaction. eindex: The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. \"\"\" number = posting.units.number new_posting = posting._replace( units=copy.copy(posting.units), cost=position.Cost(posting.price.number, posting.price.currency, entry.date, None)) pending_lots.append(([number], new_posting, eindex)) return new_posting beancount.plugins.book_conversions.book_price_conversions(entries, assets_account, income_account) \uf0c1 Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Parameters: entries \u2013 A list of entry instances. assets_account \u2013 An account string, the name of the account to process. income_account \u2013 An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. Source code in beancount/plugins/book_conversions.py def book_price_conversions(entries, assets_account, income_account): \"\"\"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Args: entries: A list of entry instances. assets_account: An account string, the name of the account to process. income_account: An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. \"\"\" # Pairs of (Position, Transaction) instances used to match augmenting # entries with reducing ones. pending_lots = [] # A list of pairs of matching (augmenting, reducing) postings. all_matches = [] new_entries = [] errors = [] for eindex, entry in enumerate(entries): # Figure out if this transaction has postings in Bitcoins without a cost. # The purpose of this plugin is to fixup those. if isinstance(entry, data.Transaction) and any(is_matching(posting, assets_account) for posting in entry.postings): # Segregate the reducing lots, augmenting lots and other lots. augmenting, reducing, other = [], [], [] for pindex, posting in enumerate(entry.postings): if is_matching(posting, assets_account): out = augmenting if posting.units.number >= ZERO else reducing else: out = other out.append(posting) # We will create a replacement list of postings with costs filled # in, possibly more than the original list, to account for the # different lots. new_postings = [] # Convert all the augmenting postings to cost basis. for posting in augmenting: new_postings.append(augment_inventory(pending_lots, posting, entry, eindex)) # Then process reducing postings. if reducing: # Process all the reducing postings, booking them to matching lots. pnl = inventory.Inventory() for posting in reducing: rpostings, matches, posting_pnl, new_errors = ( reduce_inventory(pending_lots, posting, eindex)) new_postings.extend(rpostings) all_matches.extend(matches) errors.extend(new_errors) pnl.add_amount(amount.Amount(posting_pnl, posting.price.currency)) # If some reducing lots were seen in this transaction, insert an # Income leg to absorb the P/L. We need to do this for each currency # which incurred P/L. if not pnl.is_empty(): for pos in pnl: meta = data.new_metadata('', 0) new_postings.append( data.Posting(income_account, -pos.units, None, None, None, meta)) # Third, add back all the other unrelated legs in. for posting in other: new_postings.append(posting) # Create a replacement entry. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Add matching metadata to all matching postings. mod_matches = link_entries_with_metadata(new_entries, all_matches) # Resolve the indexes to their possibly modified Transaction instances. matches = [(data.TxnPosting(new_entries[aug_index], aug_posting), data.TxnPosting(new_entries[red_index], red_posting)) for (aug_index, aug_posting), (red_index, red_posting) in mod_matches] return new_entries, errors, matches beancount.plugins.book_conversions.book_price_conversions_plugin(entries, options_map, config) \uf0c1 Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. config \u2013 A string, in \",\" format. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. Source code in beancount/plugins/book_conversions.py def book_price_conversions_plugin(entries, options_map, config): \"\"\"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. config: A string, in \",\" format. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. \"\"\" # The expected configuration is two account names, separated by whitespace. errors = [] try: assets_account, income_account = re.split(r'[,; \\t]', config) if not account.is_valid(assets_account) or not account.is_valid(income_account): raise ValueError(\"Invalid account string\") except ValueError as exc: errors.append( ConfigError( None, ('Invalid configuration: \"{}\": {}, skipping booking').format(config, exc), None)) return entries, errors new_entries, errors, _ = book_price_conversions(entries, assets_account, income_account) return new_entries, errors beancount.plugins.book_conversions.extract_trades(entries) \uf0c1 Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Parameters: entries \u2013 The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). Source code in beancount/plugins/book_conversions.py def extract_trades(entries): \"\"\"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Args: entries: The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). \"\"\" trade_map = collections.defaultdict(list) for index, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue for posting in entry.postings: links_str = posting.meta.get(META, None) if links_str: links = links_str.split(',') for link in links: trade_map[link].append((index, entry, posting)) # Sort matches according to the index of the first entry, drop the index # used for doing this, and convert the objects to tuples.. trades = [(data.TxnPosting(augmenting[1], augmenting[2]), data.TxnPosting(reducing[1], reducing[2])) for augmenting, reducing in sorted(trade_map.values())] # Sanity check. for matches in trades: assert len(matches) == 2 return trades beancount.plugins.book_conversions.is_matching(posting, account) \uf0c1 \"Identify if the given posting is one to be booked. Parameters: posting \u2013 An instance of a Posting. account \u2013 The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. Source code in beancount/plugins/book_conversions.py def is_matching(posting, account): \"\"\"\"Identify if the given posting is one to be booked. Args: posting: An instance of a Posting. account: The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. \"\"\" return (posting.account == account and posting.cost is None and posting.price is not None) beancount.plugins.book_conversions.link_entries_with_metadata(entries, all_matches) \uf0c1 Modify the entries in-place to add matching links to postings. Parameters: entries \u2013 The list of entries to modify. all_matches \u2013 A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. Source code in beancount/plugins/book_conversions.py def link_entries_with_metadata(entries, all_matches): \"\"\"Modify the entries in-place to add matching links to postings. Args: entries: The list of entries to modify. all_matches: A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. \"\"\" # Allocate trade names and compute a map of posting to trade names. link_map = collections.defaultdict(list) for (aug_index, aug_posting), (red_index, red_posting) in all_matches: link = 'trade-{}'.format(str(uuid.uuid4()).split('-')[-1]) link_map[id(aug_posting)].append(link) link_map[id(red_posting)].append(link) # Modify the postings. postings_repl_map = {} for entry in entries: if isinstance(entry, data.Transaction): for index, posting in enumerate(entry.postings): links = link_map.pop(id(posting), None) if links: new_posting = posting._replace(meta=posting.meta.copy()) new_posting.meta[META] = ','.join(links) entry.postings[index] = new_posting postings_repl_map[id(posting)] = new_posting # Just a sanity check. assert not link_map, \"Internal error: not all matches found.\" # Return a list of the modified postings (mapping the old matches to the # newly created ones). return [((aug_index, postings_repl_map[id(aug_posting)]), (red_index, postings_repl_map[id(red_posting)])) for (aug_index, aug_posting), (red_index, red_posting) in all_matches] beancount.plugins.book_conversions.main() \uf0c1 Extract trades from metadata-annotated postings and report on them. Source code in beancount/plugins/book_conversions.py def main(): \"\"\"Extract trades from metadata-annotated postings and report on them. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output', action='store', help=\"Filename to output results to (default goes to stdout)\") oparser.add_argument('-f', '--format', default='text', choices=['text', 'csv'], help=\"Output format to render to (text, csv)\") args = parser.parse_args() # Load the input file. entries, errors, options_map = loader.load_file(args.filename) # Get the list of trades. trades = extract_trades(entries) # Produce a table of all the trades. columns = ('units currency cost_currency ' 'buy_date buy_price sell_date sell_price pnl').split() header = ['Units', 'Currency', 'Cost Currency', 'Buy Date', 'Buy Price', 'Sell Date', 'Sell Price', 'P/L'] body = [] for aug, red in trades: units = -red.posting.units.number buy_price = aug.posting.price.number sell_price = red.posting.price.number pnl = (units * (sell_price - buy_price)).quantize(buy_price) body.append([ -red.posting.units.number, red.posting.units.currency, red.posting.price.currency, aug.txn.date.isoformat(), buy_price, red.txn.date.isoformat(), sell_price, pnl ]) trades_table = table.Table(columns, header, body) # Render the table as text or CSV. outfile = open(args.output, 'w') if args.output else sys.stdout table.render_table(trades_table, outfile, args.format) beancount.plugins.book_conversions.reduce_inventory(pending_lots, posting, eindex) \uf0c1 Match a reducing posting against a list of lots (using FIFO order). Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. eindex \u2013 The index of the parent transaction housing this posting. Returns: A tuple of postings \u2013 A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. Source code in beancount/plugins/book_conversions.py def reduce_inventory(pending_lots, posting, eindex): \"\"\"Match a reducing posting against a list of lots (using FIFO order). Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. eindex: The index of the parent transaction housing this posting. Returns: A tuple of postings: A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. \"\"\" new_postings = [] matches = [] pnl = ZERO errors = [] match_number = -posting.units.number match_currency = posting.units.currency cost_currency = posting.price.currency while match_number != ZERO: # Find the first lot with matching currency. for fnumber, fposting, findex in pending_lots: funits = fposting.units fcost = fposting.cost if (funits.currency == match_currency and fcost and fcost.currency == cost_currency): assert fnumber[0] > ZERO, \"Internal error, zero lot\" break else: errors.append( BookConversionError(posting.meta, \"Could not match position {}\".format(posting), None)) break # Reduce the pending lots. number = min(match_number, fnumber[0]) cost = fcost match_number -= number fnumber[0] -= number if fnumber[0] == ZERO: pending_lots.pop(0) # Add a corresponding posting. rposting = posting._replace( units=amount.Amount(-number, posting.units.currency), cost=copy.copy(cost)) new_postings.append(rposting) # Update the P/L. pnl += number * (posting.price.number - cost.number) # Add to the list of matches. matches.append(((findex, fposting), (eindex, rposting))) return new_postings, matches, pnl, errors beancount.plugins.check_average_cost \uf0c1 A plugin that ensures cost basis is preserved in unbooked transactions. This is intended to be used in accounts using the \"NONE\" booking method, to manually ensure that the sum total of the cost basis of reducing legs matches the average of what's in the account inventory. This is a partial first step toward implementing the \"AVERAGE\" booking method. In other words, this plugins provides assertions that will constrain you to approximate what the \"AVERAGE\" booking method will do, manually, and not to leak too much cost basis through unmatching bookings without checks. (Note the contrived context here: Ideally the \"NONE\" booking method would simply not exist.) beancount.plugins.check_average_cost.MatchBasisError ( tuple ) \uf0c1 MatchBasisError(source, message, entry) beancount.plugins.check_average_cost.MatchBasisError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_average_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.check_average_cost.MatchBasisError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of MatchBasisError(source, message, entry) beancount.plugins.check_average_cost.MatchBasisError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/check_average_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.check_average_cost.validate_average_cost(entries, options_map, config_str=None) \uf0c1 Check that reducing legs on unbooked postings are near the average cost basis. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 The configuration as a string version of a float. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_average_cost.py def validate_average_cost(entries, options_map, config_str=None): \"\"\"Check that reducing legs on unbooked postings are near the average cost basis. Args: entries: A list of directives. unused_options_map: An options map. config_str: The configuration as a string version of a float. Returns: A list of new errors, if any were found. \"\"\" # Initialize tolerance bounds. if config_str and config_str.strip(): # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, float): raise RuntimeError(\"Invalid configuration for check_average_cost: \" \"must be a float\") tolerance = config_obj else: tolerance = DEFAULT_TOLERANCE min_tolerance = D(1 - tolerance) max_tolerance = D(1 + tolerance) errors = [] ocmap = getters.get_account_open_close(entries) balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, Transaction): for posting in entry.postings: dopen = ocmap.get(posting.account, None) # Only process accounts with a NONE booking value. if dopen and dopen[0] and dopen[0].booking == Booking.NONE: balance = balances[(posting.account, posting.units.currency, posting.cost.currency if posting.cost else None)] if posting.units.number < ZERO: average = balance.average().get_only_position() if average is not None: number = average.cost.number min_valid = number * min_tolerance max_valid = number * max_tolerance if not (min_valid <= posting.cost.number <= max_valid): errors.append( MatchBasisError( entry.meta, (\"Cost basis on reducing posting is too far from \" \"the average cost ({} vs. {})\".format( posting.cost.number, average.cost.number)), entry)) balance.add_position(posting) return entries, errors beancount.plugins.check_closing \uf0c1 A plugin that automatically inserts a balance check on a tagged closing posting. Some postings are known to the user to be \"closing trades\", which means that the resulting position of the instrument just after the trade should be zero. For instance, this is the case for most ordinary options trading, only one lot of a particular instrument is held, and eventually expires or gets sold off in its entirely. One would like to confirm that, and the way to do this in Beancount is to insert a balance check. This plugin allows you to do that more simply, by inserting metadata. For example, this transaction: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD closing: TRUE Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL Would expand into the following two directives: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL 2018-02-17 balance Assets:US:Brokerage:Main:Options 0 QQQ180216C160 Insert the closing line when you know you're closing the position. beancount.plugins.check_closing.check_closing(entries, options_map) \uf0c1 Expand 'closing' metadata to a zero balance check. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_closing.py def check_closing(entries, options_map): \"\"\"Expand 'closing' metadata to a zero balance check. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.meta and posting.meta.get('closing', False): # Remove the metadata. meta = posting.meta.copy() del meta['closing'] entry = entry._replace(meta=meta) # Insert a balance. date = entry.date + datetime.timedelta(days=1) balance = data.Balance(data.new_metadata(\"\", 0), date, posting.account, amount.Amount(ZERO, posting.units.currency), None, None) new_entries.append(balance) new_entries.append(entry) return new_entries, [] beancount.plugins.check_commodity \uf0c1 A plugin that verifies that all seen commodities have a Commodity directive. This is useful if you're a bit pedantic and like to make sure that you're declared attributes for each of the commodities you use. It's useful if you use the portfolio export, for example. beancount.plugins.check_commodity.CheckCommodityError ( tuple ) \uf0c1 CheckCommodityError(source, message, entry) beancount.plugins.check_commodity.CheckCommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_commodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.check_commodity.CheckCommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CheckCommodityError(source, message, entry) beancount.plugins.check_commodity.CheckCommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/check_commodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.check_commodity.validate_commodity_directives(entries, options_map) \uf0c1 Find all commodities used and ensure they have a corresponding Commodity directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_commodity.py def validate_commodity_directives(entries, options_map): \"\"\"Find all commodities used and ensure they have a corresponding Commodity directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" commodities_used = options_map['commodities'] errors = [] meta = data.new_metadata('', 0) commodity_map = getters.get_commodity_map(entries, create_missing=False) for currency in commodities_used: commodity_entry = commodity_map.get(currency, None) if commodity_entry is None: errors.append( CheckCommodityError( meta, \"Missing Commodity directive for '{}'\".format(currency), None)) return entries, errors beancount.plugins.coherent_cost \uf0c1 This plugin validates that currencies held at cost aren't ever converted at price and vice-versa. This is usually the case, and using it will prevent users from making the mistake of selling a lot without specifying it via its cost basis. beancount.plugins.coherent_cost.CoherentCostError ( tuple ) \uf0c1 CoherentCostError(source, message, entry) beancount.plugins.coherent_cost.CoherentCostError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/coherent_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.coherent_cost.CoherentCostError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CoherentCostError(source, message, entry) beancount.plugins.coherent_cost.CoherentCostError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/coherent_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.coherent_cost.validate_coherent_cost(entries, unused_options_map) \uf0c1 Check that all currencies are either used at cost or not at all, but never both. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/coherent_cost.py def validate_coherent_cost(entries, unused_options_map): \"\"\"Check that all currencies are either used at cost or not at all, but never both. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] with_cost = {} without_cost = {} for entry in data.filter_txns(entries): for posting in entry.postings: target_set = without_cost if posting.cost is None else with_cost currency = posting.units.currency target_set.setdefault(currency, entry) for currency in set(with_cost) & set(without_cost): errors.append( CoherentCostError( without_cost[currency].meta, \"Currency '{}' is used both with and without cost\".format(currency), with_cost[currency])) # Note: We really ought to include both of the first transactions here. return entries, errors beancount.plugins.commodity_attr \uf0c1 A plugin that asserts that all Commodity directives have a particular attribute and that it is part of a set of enum values. The configuration must be a mapping of attribute name to list of valid values, like this: plugin \"beancount.plugins.commodity_attr\" \"{ 'sector': ['Technology', 'Financials', 'Energy'], 'name': None, }\" The plugin issues an error if a Commodity directive is missing the attribute, or if the attribute value is not in the valid set. If you'd like to just ensure the attribute is set, set the list of valid values to None, as in the 'name' attribute in the example above. beancount.plugins.commodity_attr.CommodityError ( tuple ) \uf0c1 CommodityError(source, message, entry) beancount.plugins.commodity_attr.CommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.commodity_attr.CommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CommodityError(source, message, entry) beancount.plugins.commodity_attr.CommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.commodity_attr.ConfigError ( tuple ) \uf0c1 ConfigError(source, message, entry) beancount.plugins.commodity_attr.ConfigError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.commodity_attr.ConfigError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ConfigError(source, message, entry) beancount.plugins.commodity_attr.ConfigError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.commodity_attr.validate_commodity_attr(entries, unused_options_map, config_str) \uf0c1 Check that all Commodity directives have a valid attribute. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 A configuration string. Returns: A list of new errors, if any were found. Source code in beancount/plugins/commodity_attr.py def validate_commodity_attr(entries, unused_options_map, config_str): \"\"\"Check that all Commodity directives have a valid attribute. Args: entries: A list of directives. unused_options_map: An options map. config_str: A configuration string. Returns: A list of new errors, if any were found. \"\"\" errors = [] # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): errors.append(ConfigError( data.new_metadata('', 0), \"Invalid configuration for commodity_attr plugin; skipping.\", None)) return entries, errors validmap = {attr: frozenset(values) if values is not None else None for attr, values in config_obj.items()} for entry in entries: if not isinstance(entry, data.Commodity): continue for attr, values in validmap.items(): value = entry.meta.get(attr, None) if value is None: errors.append(CommodityError( entry.meta, \"Missing attribute '{}' for Commodity directive {}\".format( attr, entry.currency), None)) continue if values and value not in values: errors.append(CommodityError( entry.meta, \"Invalid attribute '{}' for Commodity\".format(value) + \" directive {}; valid options: {}\".format( entry.currency, ', '.join(values)), None)) return entries, errors beancount.plugins.currency_accounts \uf0c1 An implementation of currency accounts. This is an automatic implementation of the method described here: https://www.mathstat.dal.ca/~selinger/accounting/tutorial.html You enable it just like this: plugin \"beancount.plugins.currency_accounts\" \"Equity:CurrencyAccounts\" Accounts will be automatically created under the given base account, with the currency name appended to it, e.g., Equity:CurrencyAccounts:CAD Equity:CurrencyAccounts:USD etc., where used. You can have a look at the account balances with a query like this: bean-query $L \"select account, sum(position), convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts' \" The sum total of the converted amounts should be a number not too large: bean-query $L \"select convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts'\" WARNING: This is a prototype. Note the FIXMEs in the code below, which indicate some potential problems. beancount.plugins.currency_accounts.get_neutralizing_postings(curmap, base_account, new_accounts) \uf0c1 Process an entry. Parameters: curmap \u2013 A dict of currency to a list of Postings of this transaction. base_account \u2013 A string, the root account name to insert. new_accounts \u2013 A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. Source code in beancount/plugins/currency_accounts.py def get_neutralizing_postings(curmap, base_account, new_accounts): \"\"\"Process an entry. Args: curmap: A dict of currency to a list of Postings of this transaction. base_account: A string, the root account name to insert. new_accounts: A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. \"\"\" new_postings = [] for currency, postings in curmap.items(): # Compute the per-currency balance. inv = inventory.Inventory() for posting in postings: inv.add_amount(convert.get_cost(posting)) if inv.is_empty(): new_postings.extend(postings) continue # Re-insert original postings and remove price conversions. # # Note: This may cause problems if the implicit_prices plugin is # configured to run after this one, or if you need the price annotations # for some scripting or serious work. # # FIXME: We need to handle these important cases (they're not frivolous, # this is a prototype), probably by inserting some exceptions with # collaborating code in the booking (e.g. insert some metadata that # disables price conversions on those postings). # # FIXME(2): Ouch! Some of the residual seeps through here, where there # are more than a single currency block. This needs fixing too. You can # easily mitigate some of this to some extent, by excluding transactions # which don't have any price conversion in them. for pos in postings: if pos.price is not None: pos = pos._replace(price=None) new_postings.append(pos) # Insert the currency trading accounts postings. amount = inv.get_only_position().units acc = account.join(base_account, currency) new_accounts.add(acc) new_postings.append( Posting(acc, -amount, None, None, None, None)) return new_postings beancount.plugins.currency_accounts.group_postings_by_weight_currency(entry) \uf0c1 Return where this entry might require adjustment. Source code in beancount/plugins/currency_accounts.py def group_postings_by_weight_currency(entry: Transaction): \"\"\"Return where this entry might require adjustment.\"\"\" curmap = collections.defaultdict(list) has_price = False for posting in entry.postings: currency = posting.units.currency if posting.cost: currency = posting.cost.currency if posting.price: assert posting.price.currency == currency elif posting.price: has_price = True currency = posting.price.currency if posting.price: has_price = True curmap[currency].append(posting) return curmap, has_price beancount.plugins.currency_accounts.insert_currency_trading_postings(entries, options_map, config) \uf0c1 Insert currency trading postings. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The base account name for currency trading accounts. Returns: A list of new errors, if any were found. Source code in beancount/plugins/currency_accounts.py def insert_currency_trading_postings(entries, options_map, config): \"\"\"Insert currency trading postings. Args: entries: A list of directives. unused_options_map: An options map. config: The base account name for currency trading accounts. Returns: A list of new errors, if any were found. \"\"\" base_account = config.strip() if not account.is_valid(base_account): base_account = DEFAULT_BASE_ACCOUNT errors = [] new_entries = [] new_accounts = set() for entry in entries: if isinstance(entry, Transaction): curmap, has_price = group_postings_by_weight_currency(entry) if has_price and len(curmap) > 1: new_postings = get_neutralizing_postings( curmap, base_account, new_accounts) entry = entry._replace(postings=new_postings) if META_PROCESSED: entry.meta[META_PROCESSED] = True new_entries.append(entry) earliest_date = entries[0].date open_entries = [ data.Open(data.new_metadata('', index), earliest_date, acc, None, None) for index, acc in enumerate(sorted(new_accounts))] return open_entries + new_entries, errors beancount.plugins.divert_expenses \uf0c1 For tagged transactions, convert expenses to a single account. This plugin allows you to select a tag and it automatically converts all the Expenses postings to use a single account. For example, with this input: plugin \"divert_expenses\" \"['kid', 'Expenses:Child']\" 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Food:Grocery 10.27 USD It will output: 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Child 10.27 USD You can limit the diversion to one posting only, like this: 2018-05-05 * \"CVS/PHARMACY\" \"\" #kai Liabilities:CreditCard -66.38 USD Expenses:Pharmacy 21.00 USD ;; Vitamins for Kai Expenses:Pharmacy 45.38 USD divert: FALSE See unit test for details. See this thread for context: https://docs.google.com/drawings/d/18fTrrGlmz0jFbfcGGHTffbdRwbmST8r9_3O26Dd1Xww/edit?usp=sharing beancount.plugins.divert_expenses.divert_expenses(entries, options_map, config_str) \uf0c1 Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. options_map \u2013 A parser options dict. config_str \u2013 A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. Source code in beancount/plugins/divert_expenses.py def divert_expenses(entries, options_map, config_str): \"\"\"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. options_map: A parser options dict. config_str: A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. \"\"\" # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") tag = config_obj['tag'] replacement_account = config_obj['account'] acctypes = options.get_account_types(options_map) new_entries = [] errors = [] for entry in entries: if isinstance(entry, Transaction) and tag in entry.tags: entry = replace_diverted_accounts(entry, replacement_account, acctypes) new_entries.append(entry) return new_entries, errors beancount.plugins.divert_expenses.replace_diverted_accounts(entry, replacement_account, acctypes) \uf0c1 Replace the Expenses accounts from the entry. Parameters: entry \u2013 A Transaction directive. replacement_account \u2013 A string, the account to use for replacement. acctypes \u2013 An AccountTypes instance. Returns: A possibly entry directive. Source code in beancount/plugins/divert_expenses.py def replace_diverted_accounts(entry, replacement_account, acctypes): \"\"\"Replace the Expenses accounts from the entry. Args: entry: A Transaction directive. replacement_account: A string, the account to use for replacement. acctypes: An AccountTypes instance. Returns: A possibly entry directive. \"\"\" new_postings = [] for posting in entry.postings: divert = posting.meta.get('divert', None) if posting.meta else None if (divert is True or ( divert is None and account_types.is_account_type(acctypes.expenses, posting.account))): posting = posting._replace(account=replacement_account, meta={'diverted_account': posting.account}) new_postings.append(posting) return entry._replace(postings=new_postings) beancount.plugins.exclude_tag \uf0c1 Exclude #virtual tags. This is used to demonstrate excluding a set of transactions from a particular tag. In this example module, the tag name is fixed, but if we integrated this we could provide a way to choose which tags to exclude. This is simply just another mechanism for selecting a subset of transactions. See discussion here for details: https://groups.google.com/d/msg/ledger-cli/N8Slh2t45K0/aAz0i3Be4LYJ beancount.plugins.exclude_tag.exclude_tag(entries, options_map) \uf0c1 Select all transactions that do not have a #virtual tag. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/exclude_tag.py def exclude_tag(entries, options_map): \"\"\"Select all transactions that do not have a #virtual tag. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" filtered_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or not entry.tags or EXCLUDED_TAG not in entry.tags)] return (filtered_entries, []) beancount.plugins.fill_account \uf0c1 Insert an posting with a default account when there is only a single posting. This is convenient to use in files which have mostly expenses, such as during a trip. Set the name of the default account to fill in as an option. beancount.plugins.fill_account.FillAccountError ( tuple ) \uf0c1 FillAccountError(source, message, entry) beancount.plugins.fill_account.FillAccountError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fill_account.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.fill_account.FillAccountError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of FillAccountError(source, message, entry) beancount.plugins.fill_account.FillAccountError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/fill_account.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.fill_account.fill_account(entries, unused_options_map, insert_account) \uf0c1 Insert an posting with a default account when there is only a single posting. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 A parser options dict. insert_account \u2013 A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/fill_account.py def fill_account(entries, unused_options_map, insert_account): \"\"\"Insert an posting with a default account when there is only a single posting. Args: entries: A list of directives. unused_options_map: A parser options dict. insert_account: A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" if not account.is_valid(insert_account): return entries, [ FillAccountError(data.new_metadata('', 0), \"Invalid account name: '{}'\".format(insert_account), None)] new_entries = [] for entry in entries: if isinstance(entry, data.Transaction) and len(entry.postings) == 1: inv = inventory.Inventory() for posting in entry.postings: if posting.cost is None: inv.add_amount(posting.units) else: inv.add_amount(convert.get_cost(posting)) inv.reduce(convert.get_units) new_postings = list(entry.postings) for pos in inv: new_postings.append(data.Posting(insert_account, -pos.units, None, None, None, None)) entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, [] beancount.plugins.fix_payees \uf0c1 Rename payees based on a set of rules. This can be used to clean up dirty imported payee names. This plugin accepts a list of rules in this format: plugin \"beancount.plugins.fix_payees\" \"[ (PAYEE, MATCH1, MATCH2, ...), ]\" Each of the \"MATCH\" clauses is a string, in the format: \"A:<regexp>\" : Match the account name. \"D:<regexp>\" : Match the payee or the narration. The plugin matches the Transactions in the file and if there is a case-insensitive match against the regular expression (we use re.search()), replaces the payee name by \"PAYEE\". If multiple rules match, only the first rule is used. For example: plugin \"beancount.plugins.fix_payees\" \"[ (\"T-Mobile USA\", \"A:Expenses:Communications:Phone\", \"D:t-mobile\"), (\"Con Edison\", \"A:Expenses:Home:Electricity\", \"D:con ?ed\"), (\"Birreria @ Eataly\", \"D:EATALY BIRRERIA\"), ]\" beancount.plugins.fix_payees.FixPayeesError ( tuple ) \uf0c1 FixPayeesError(source, message, entry) beancount.plugins.fix_payees.FixPayeesError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fix_payees.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.fix_payees.FixPayeesError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of FixPayeesError(source, message, entry) beancount.plugins.fix_payees.FixPayeesError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/fix_payees.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.fix_payees.fix_payees(entries, options_map, config) \uf0c1 Rename payees based on a set of rules. See module docstring for details. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config \u2013 A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. Source code in beancount/plugins/fix_payees.py def fix_payees(entries, options_map, config): \"\"\"Rename payees based on a set of rules. See module docstring for details. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config: A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. \"\"\" errors = [] if config.strip(): try: expr = ast.literal_eval(config) except (SyntaxError, ValueError): meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Syntax error in config: {}\".format(config), None)) return entries, errors else: return entries, errors # Pre-compile the regular expressions for performance. rules = [] for rule in ast.literal_eval(config): clauses = iter(rule) new_payee = next(clauses) regexps = [] for clause in clauses: match = re.match('([AD]):(.*)', clause) if not match: meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Invalid clause: {}\".format(clause), None)) continue command, regexp = match.groups() regexps.append((command, re.compile(regexp, re.I).search)) new_rule = [new_payee] + regexps rules.append(tuple(new_rule)) # Run the rules over the transaction objects. new_entries = [] replaced_entries = {rule[0]: [] for rule in rules} for entry in entries: if isinstance(entry, data.Transaction): for rule in rules: clauses = iter(rule) new_payee = next(clauses) # Attempt to match all the clauses. for clause in clauses: command, func = clause if command == 'D': if not ((entry.payee is not None and func(entry.payee)) or (entry.narration is not None and func(entry.narration))): break elif command == 'A': if not any(func(posting.account) for posting in entry.postings): break else: # Make the replacement. entry = entry._replace(payee=new_payee) replaced_entries[new_payee].append(entry) new_entries.append(entry) if _DEBUG: # Print debugging info. for payee, repl_entries in sorted(replaced_entries.items(), key=lambda x: len(x[1]), reverse=True): print('{:60}: {}'.format(payee, len(repl_entries))) return new_entries, errors beancount.plugins.forecast \uf0c1 An example of adding a forecasting feature to Beancount via a plugin. This entry filter plugin uses existing syntax to define and automatically inserted transactions in the future based on a convention. It serves mostly as an example of how you can experiment by creating and installing a local filter, and not so much as a serious forecasting feature (though the experiment is a good way to get something more general kickstarted eventually, I think the concept would generalize nicely and should eventually be added as a common feature of Beancount). A user can create a transaction like this: 2014-03-08 # \"Electricity bill [MONTHLY]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD and new transactions will be created monthly for the following year. Note the use of the '#' flag and the word 'MONTHLY' which defines the periodicity. The number of recurrences can optionally be specified either by providing an end date or by specifying the number of times that the transaction will be repeated. For example: 2014-03-08 # \"Electricity bill [MONTHLY UNTIL 2019-12-31]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [MONTHLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Transactions can also be repeated at yearly intervals, e.g.: 2014-03-08 # \"Electricity bill [YEARLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Other examples: 2014-03-08 # \"Electricity bill [WEEKLY SKIP 1 TIME REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [DAILY SKIP 3 TIMES REPEAT 1 TIME]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD beancount.plugins.forecast.forecast_plugin(entries, options_map) \uf0c1 An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file Returns: A tuple of entries and errors. Source code in beancount/plugins/forecast.py def forecast_plugin(entries, options_map): \"\"\"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Args: entries: a list of entry instances options_map: a dict of options parsed from the file Returns: A tuple of entries and errors. \"\"\" # Find the last entry's date. date_today = entries[-1].date # Filter out forecast entries from the list of valid entries. forecast_entries = [] filtered_entries = [] for entry in entries: outlist = (forecast_entries if (isinstance(entry, data.Transaction) and entry.flag == '#') else filtered_entries) outlist.append(entry) # Generate forecast entries up to the end of the current year. new_entries = [] for entry in forecast_entries: # Parse the periodicity. match = re.search(r'(^.*)\\[(MONTHLY|YEARLY|WEEKLY|DAILY)' r'(\\s+SKIP\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+REPEAT\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+UNTIL\\s+([0-9\\-]+))?\\]', entry.narration) if not match: new_entries.append(entry) continue forecast_narration = match.group(1).strip() forecast_interval = ( rrule.YEARLY if match.group(2).strip() == 'YEARLY' else rrule.WEEKLY if match.group(2).strip() == 'WEEKLY' else rrule.DAILY if match.group(2).strip() == 'DAILY' else rrule.MONTHLY) forecast_periodicity = {'dtstart': entry.date} if match.group(6): # e.g., [MONTHLY REPEAT 3 TIMES]: forecast_periodicity['count'] = int(match.group(6)) elif match.group(8): # e.g., [MONTHLY UNTIL 2020-01-01]: forecast_periodicity['until'] = datetime.datetime.strptime( match.group(8), '%Y-%m-%d').date() else: # e.g., [MONTHLY] forecast_periodicity['until'] = datetime.date( datetime.date.today().year, 12, 31) if match.group(4): # SKIP forecast_periodicity['interval'] = int(match.group(4)) + 1 # Generate a new entry for each forecast date. forecast_dates = [dt.date() for dt in rrule.rrule(forecast_interval, **forecast_periodicity)] for forecast_date in forecast_dates: forecast_entry = entry._replace(date=forecast_date, narration=forecast_narration) new_entries.append(forecast_entry) # Make sure the new entries inserted are sorted. new_entries.sort(key=data.entry_sortkey) return (filtered_entries + new_entries, []) beancount.plugins.implicit_prices \uf0c1 This plugin synthesizes Price directives for all Postings with a price or directive or if it is an augmenting posting, has a cost directive. beancount.plugins.implicit_prices.ImplicitPriceError ( tuple ) \uf0c1 ImplicitPriceError(source, message, entry) beancount.plugins.implicit_prices.ImplicitPriceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/implicit_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.implicit_prices.ImplicitPriceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ImplicitPriceError(source, message, entry) beancount.plugins.implicit_prices.ImplicitPriceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/implicit_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.implicit_prices.add_implicit_prices(entries, unused_options_map) \uf0c1 Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/implicit_prices.py def add_implicit_prices(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" new_entries = [] errors = [] # A dict of (date, currency, cost-currency) to price entry. new_price_entry_map = {} balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Always replicate the existing entries. new_entries.append(entry) if isinstance(entry, Transaction): # Inspect all the postings in the transaction. for posting in entry.postings: units = posting.units cost = posting.cost # Check if the position is matching against an existing # position. _, booking = balances[posting.account].add_position(posting) # Add prices when they're explicitly specified on a posting. An # explicitly specified price may occur in a conversion, e.g. # Assets:Account 100 USD @ 1.10 CAD # or, if a cost is also specified, as the current price of the # underlying instrument, e.g. # Assets:Account 100 HOOL {564.20} @ {581.97} USD if posting.price is not None: meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, posting.price) # Add costs, when we're not matching against an existing # position. This happens when we're just specifying the cost, # e.g. # Assets:Account 100 HOOL {564.20} elif (cost is not None and booking != inventory.Booking.REDUCED): meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, amount.Amount(cost.number, cost.currency)) else: price_entry = None if price_entry is not None: key = (price_entry.date, price_entry.currency, price_entry.amount.number, # Ideally should be removed. price_entry.amount.currency) try: new_price_entry_map[key] ## Do not fail for now. We still have many valid use ## cases of duplicate prices on the same date, for ## example, stock splits, or trades on two dates with ## two separate reported prices. We need to figure out a ## more elegant solution for this in the long term. ## Keeping both for now. We should ideally not use the ## number in the de-dup key above. # # dup_entry = new_price_entry_map[key] # if price_entry.amount.number == dup_entry.amount.number: # # Skip duplicates. # continue # else: # errors.append( # ImplicitPriceError( # entry.meta, # \"Duplicate prices for {} on {}\".format(entry, # dup_entry), # entry)) except KeyError: new_price_entry_map[key] = price_entry new_entries.append(price_entry) return new_entries, errors beancount.plugins.ira_contribs \uf0c1 Automatically adding IRA contributions postings. This plugin looks for increasing postings on specified accounts ('+' sign for Assets and Expenses accounts, '-' sign for the others), or postings with a particular flag on them and when it finds some, inserts a pair of postings on that transaction of the corresponding amounts in a different currency. The currency is intended to be an imaginary currency used to track the number of dollars contributed to a retirement account over time. For example, a possible configuration could be: plugin \"beancount.plugins.ira_contribs\" \"{ 'currency': 'IRAUSD', 'flag': 'M', 'accounts': { 'Income:US:Acme:Match401k': ( 'Assets:US:Federal:Match401k', 'Expenses:Taxes:TY{year}:US:Federal:Match401k'), ('C', 'Assets:US:Fidelity:PreTax401k:Cash'): ( 'Assets:US:Federal:PreTax401k', 'Expenses:Taxes:TY{year}:US:Federal:PreTax401k'), } }\" Note: In this example, the configuration that triggers on the \"Income:US:Acme:Match401k\" account does not require a flag for those accounts; the configuration for the \"Assets:US:Fidelity:PreTax401k:Cash\" account requires postings to have a \"C\" flag to trigger an insertion. Given a transaction like the following, which would be typical for a salary entry where the employer is automatically diverting some of the pre-tax money to a retirement account (in this example, at Fidelity): 2013-02-15 * \"ACME INC PAYROLL\" Income:US:Acme:Salary ... ... Assets:US:BofA:Checking ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD ... A posting with account 'Assets:US:Fidelity:PreTax401k:Cash', which is configured to match, would be found. The configuration above instructs the plugin to automatically insert new postings like this: 2013-02-15 * \"ACME INC PAYROLL\" ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD M Assets:US:Federal:PreTax401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:PreTax401k 620.50 IRAUSD ... Notice that the \"{year}\" string in the configuration's account names is automatically replaced by the current year in the account name. This is useful if you maintain separate tax accounts per year. Furthermore, as in the configuration example above, you may have multiple matching entries to trigger multiple insertions. For example, the employer may also match the employee's retirement contribution by depositing some money in the retirement account: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD In this example the funds get reported as invested immediately (an intermediate deposit into a cash account does not take place). The plugin configuration would match against the 'Income:US:Acme:Match401k' account and since it increases its value (the normal balance of an Income account is negative), postings would be inserted like this: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD M Assets:US:Federal:Match401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:Match401k 620.50 IRAUSD Note that the special dict keys 'currency' and 'flag' are used to specify which currency to use for the inserted postings, and if set, which flag to mark these postings with. beancount.plugins.ira_contribs.add_ira_contribs(entries, options_map, config_str) \uf0c1 Add legs for 401k employer match contributions. See module docstring for an example configuration. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config_str \u2013 A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. Source code in beancount/plugins/ira_contribs.py def add_ira_contribs(entries, options_map, config_str): \"\"\"Add legs for 401k employer match contributions. See module docstring for an example configuration. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config_str: A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. \"\"\" # Parse and extract configuration values. # FIXME: Use ast.literal_eval() here; you need to convert this code and the getters. # FIXME: Also, don't raise a RuntimeError, return an error object; review # this for all the plugins. # FIXME: This too is temporary. # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") # Currency of the inserted postings. currency = config_obj.pop('currency', 'UNKNOWN') # Flag to attach to the inserted postings. insert_flag = config_obj.pop('flag', None) # A dict of account names that trigger the insertion of postings to pairs of # inserted accounts when triggered. accounts = config_obj.pop('accounts', {}) # Convert the key in the accounts configuration for matching. account_transforms = {} for key, config in accounts.items(): if isinstance(key, str): flag = None account = key else: assert isinstance(key, tuple) flag, account = key account_transforms[account] = (flag, config) new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): orig_entry = entry for posting in entry.postings: if (posting.units is not MISSING and (posting.account in account_transforms) and (account_types.get_account_sign(posting.account) * posting.units.number > 0)): # Get the new account legs to insert. required_flag, (neg_account, pos_account) = account_transforms[posting.account] assert posting.cost is None # Check required flag if present. if (required_flag is None or (required_flag and required_flag == posting.flag)): # Insert income/expense entries for 401k. entry = add_postings( entry, amount.Amount(abs(posting.units.number), currency), neg_account.format(year=entry.date.year), pos_account.format(year=entry.date.year), insert_flag) if DEBUG and orig_entry is not entry: printer.print_entry(orig_entry) printer.print_entry(entry) new_entries.append(entry) return new_entries, [] beancount.plugins.ira_contribs.add_postings(entry, amount_, neg_account, pos_account, flag) \uf0c1 Insert positive and negative postings of a position in an entry. Parameters: entry \u2013 A Transaction instance. amount_ \u2013 An Amount instance to create the position, with positive number. neg_account \u2013 An account for the posting with the negative amount. pos_account \u2013 An account for the posting with the positive amount. flag \u2013 A string, that is to be set as flag for the new postings. Returns: A new, modified entry. Source code in beancount/plugins/ira_contribs.py def add_postings(entry, amount_, neg_account, pos_account, flag): \"\"\"Insert positive and negative postings of a position in an entry. Args: entry: A Transaction instance. amount_: An Amount instance to create the position, with positive number. neg_account: An account for the posting with the negative amount. pos_account: An account for the posting with the positive amount. flag: A string, that is to be set as flag for the new postings. Returns: A new, modified entry. \"\"\" return entry._replace(postings=entry.postings + [ data.Posting(neg_account, -amount_, None, None, flag, None), data.Posting(pos_account, amount_, None, None, flag, None), ]) beancount.plugins.leafonly \uf0c1 A plugin that issues errors when amounts are posted to non-leaf accounts, that is, accounts with child accounts. This is an extra constraint that you may want to apply optionally. If you install this plugin, it will issue errors for all accounts that have postings to non-leaf accounts. Some users may want to disallow this and enforce that only leaf accounts may have postings on them. beancount.plugins.leafonly.LeafOnlyError ( tuple ) \uf0c1 LeafOnlyError(source, message, entry) beancount.plugins.leafonly.LeafOnlyError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/leafonly.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.leafonly.LeafOnlyError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LeafOnlyError(source, message, entry) beancount.plugins.leafonly.LeafOnlyError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/leafonly.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.leafonly.validate_leaf_only(entries, unused_options_map) \uf0c1 Check for non-leaf accounts that have postings on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/leafonly.py def validate_leaf_only(entries, unused_options_map): \"\"\"Check for non-leaf accounts that have postings on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" real_root = realization.realize(entries, compute_balance=False) default_meta = data.new_metadata('', 0) open_close_map = None # Lazily computed. errors = [] for real_account in realization.iter_children(real_root): if len(real_account) > 0 and real_account.txn_postings: if open_close_map is None: open_close_map = getters.get_account_open_close(entries) try: open_entry = open_close_map[real_account.account][0] except KeyError: open_entry = None errors.append(LeafOnlyError( open_entry.meta if open_entry else default_meta, \"Non-leaf account '{}' has postings on it\".format(real_account.account), open_entry)) return entries, errors beancount.plugins.mark_unverified \uf0c1 Add metadata to Postings which occur after their last Balance directives. Some people use Balance directives as a way to indicate that all postings before them are verified. They want to compute balances in each account as of the date of that last Balance directives. One way to do that is to use this plugin to mark the postings which occur after and to then filter them out using a WHERE clause on that metadata: SELECT account, sum(position) WHERE NOT meta(\"unverified\") Note that doing such a filtering may result in a list of balances which may not add to zero. Also, postings for accounts without a single Balance directive on them will not be marked as unverified as all (otherwise all the postings would be marked, this would make no sense). beancount.plugins.mark_unverified.mark_unverified(entries, options_map) \uf0c1 Add metadata to postings after the last Balance entry. See module doc. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/mark_unverified.py def mark_unverified(entries, options_map): \"\"\"Add metadata to postings after the last Balance entry. See module doc. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" # The last Balance directive seen for each account. last_balances = {} for entry in entries: if isinstance(entry, data.Balance): last_balances[entry.account] = entry new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): postings = entry.postings new_postings = postings for index, posting in enumerate(postings): balance = last_balances.get(posting.account, None) if balance and balance.date <= entry.date: if new_postings is postings: new_postings = postings.copy() new_meta = posting.meta.copy() new_meta['unverified'] = True new_postings[index] = posting._replace(meta=new_meta) if new_postings is not postings: entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, [] beancount.plugins.merge_meta \uf0c1 Merge the metadata from a second file into the current set of entries. This is useful if you like to keep more sensitive private data, such as account numbers or passwords, in a second, possibly encrypted file. This can be used to generate a will, for instance, for your loved ones to be able to figure where all your assets are in case you pass away. You can store all the super secret stuff in a more closely guarded, hidden away separate file. The metadata from Open directives: Account name must match. Close directives: Account name must match. Commodity directives: Currency must match. are copied over. Metadata from the external file conflicting with that present in the main file overwrites it (external data wins). WARNING! If you include an encrypted file and the main file is not encrypted, the contents extraction from the encrypted file may appear in the cache. beancount.plugins.merge_meta.merge_meta(entries, options_map, config) \uf0c1 Load a secondary file and merge its metadata in our given set of entries. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with more metadata attached to them. Source code in beancount/plugins/merge_meta.py def merge_meta(entries, options_map, config): \"\"\"Load a secondary file and merge its metadata in our given set of entries. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with more metadata attached to them. \"\"\" external_filename = config new_entries = list(entries) ext_entries, ext_errors, ext_options_map = loader.load_file(external_filename) # Map Open and Close directives. oc_map = getters.get_account_open_close(entries) ext_oc_map = getters.get_account_open_close(ext_entries) for account in set(oc_map.keys()) & set(ext_oc_map.keys()): open_entry, close_entry = oc_map[account] ext_open_entry, ext_close_entry = ext_oc_map[account] if open_entry and ext_open_entry: open_entry.meta.update(ext_open_entry.meta) if close_entry and ext_close_entry: close_entry.meta.update(ext_close_entry.meta) # Map Commodity directives. comm_map = getters.get_commodity_map(entries, False) ext_comm_map = getters.get_commodity_map(ext_entries, False) for currency in set(comm_map) & set(ext_comm_map): comm_entry = comm_map[currency] ext_comm_entry = ext_comm_map[currency] if comm_entry and ext_comm_entry: comm_entry.meta.update(ext_comm_entry.meta) # Note: We cannot include the external file in the list of inputs so that a # change of it triggers a cache rebuild because side-effects on options_map # aren't cascaded through. This is something that should be defined better # in the plugin interface and perhaps improved upon. return new_entries, ext_errors beancount.plugins.noduplicates \uf0c1 This plugin validates that there are no duplicate transactions. beancount.plugins.noduplicates.validate_no_duplicates(entries, unused_options_map) \uf0c1 Check that the entries are unique, by computing hashes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/noduplicates.py def validate_no_duplicates(entries, unused_options_map): \"\"\"Check that the entries are unique, by computing hashes. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" unused_hashes, errors = compare.hash_entries(entries, exclude_meta=True) return entries, errors beancount.plugins.nounused \uf0c1 This plugin validates that there are no unused accounts. beancount.plugins.nounused.UnusedAccountError ( tuple ) \uf0c1 UnusedAccountError(source, message, entry) beancount.plugins.nounused.UnusedAccountError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/nounused.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.nounused.UnusedAccountError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UnusedAccountError(source, message, entry) beancount.plugins.nounused.UnusedAccountError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/nounused.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.nounused.validate_unused_accounts(entries, unused_options_map) \uf0c1 Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/nounused.py def validate_unused_accounts(entries, unused_options_map): \"\"\"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Find all the accounts referenced by entries which are not Open, and the # open directives for error reporting below. open_map = {} referenced_accounts = set() for entry in entries: if isinstance(entry, data.Open): open_map[entry.account] = entry continue referenced_accounts.update(getters.get_entry_accounts(entry)) # Create a list of suitable errors, with the location of the Open directives # corresponding to the unused accounts. errors = [UnusedAccountError(open_entry.meta, \"Unused account '{}'\".format(account), open_entry) for account, open_entry in open_map.items() if account not in referenced_accounts] return entries, errors beancount.plugins.onecommodity \uf0c1 A plugin that issues errors when more than one commodity is used in an account. For investments or trading accounts, it can make it easier to filter the action around a single stock by using the name of the stock as the leaf of the account name. Notes: The plugin will automatically skip accounts that have explicitly declared commodities in their Open directive. You can also set the metadata \"onecommodity: FALSE\" on an account's Open directive to skip the checks for that account. If provided, the configuration should be a regular expression restricting the set of accounts to check. beancount.plugins.onecommodity.OneCommodityError ( tuple ) \uf0c1 OneCommodityError(source, message, entry) beancount.plugins.onecommodity.OneCommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/onecommodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.onecommodity.OneCommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of OneCommodityError(source, message, entry) beancount.plugins.onecommodity.OneCommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/onecommodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.onecommodity.validate_one_commodity(entries, unused_options_map, config=None) \uf0c1 Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. Source code in beancount/plugins/onecommodity.py def validate_one_commodity(entries, unused_options_map, config=None): \"\"\"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Args: entries: A list of directives. unused_options_map: An options map. config: The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. \"\"\" accounts_re = re.compile(config) if config else None # Mappings of account name to lists of currencies for each units and cost. units_map = collections.defaultdict(set) cost_map = collections.defaultdict(set) # Mappings to use just for getting a relevant source. units_source_map = {} cost_source_map = {} # Gather the set of accounts to skip from the Open directives. skip_accounts = set() for entry in entries: if not isinstance(entry, data.Open): continue if (not entry.meta.get(\"onecommodity\", True) or (accounts_re and not accounts_re.match(entry.account)) or (entry.currencies and len(entry.currencies) > 1)): skip_accounts.add(entry.account) # Accumulate all the commodities used. for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.account in skip_accounts: continue units = posting.units units_map[posting.account].add(units.currency) if len(units_map[posting.account]) > 1: units_source_map[posting.account] = entry cost = posting.cost if cost: cost_map[posting.account].add(cost.currency) if len(cost_map[posting.account]) > 1: units_source_map[posting.account] = entry elif isinstance(entry, data.Balance): if entry.account in skip_accounts: continue units_map[entry.account].add(entry.amount.currency) if len(units_map[entry.account]) > 1: units_source_map[entry.account] = entry elif isinstance(entry, data.Open): if entry.currencies and len(entry.currencies) > 1: skip_accounts.add(entry.account) # Check units. errors = [] for account, currencies in units_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( units_source_map[account].meta, \"More than one currency in account '{}': {}\".format( account, ','.join(currencies)), None)) # Check costs. for account, currencies in cost_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( cost_source_map[account].meta, \"More than one cost currency in account '{}': {}\".format( account, ','.join(currencies)), None)) return entries, errors beancount.plugins.pedantic \uf0c1 A plugin of plugins which triggers are all the pedantic plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. beancount.plugins.sellgains \uf0c1 A plugin that cross-checks declared gains against prices on lot sales. When you sell stock, the gains can be automatically implied by the corresponding cash amounts. For example, in the following transaction the 2nd and 3rd postings should match the value of the stock sold: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL -10.125 USD The cost basis is checked against: 2141.36 + 008 + -10.125. That is, the balance checks computes -81 x 26.3125 = -2131.3125 + 2141.36 + 0.08 + -10.125 and checks that the residual is below a small tolerance. But... usually the income leg isn't given to you in statements. Beancount can automatically infer it using the balance, which is convenient, like this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL Additionally, most often you have the sales prices given to you on your transaction confirmation statement, so you can enter this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} @ 26.4375 USD Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL So in theory, if the price is given (26.4375 USD), we could verify that the proceeds from the sale at the given price match non-Income postings. That is, verify that -81 x 26.4375 = -2141.4375 + 2141.36 + 0.08 + is below a small tolerance value. So this plugin does this. In general terms, it does the following: For transactions with postings that have a cost and a price, it verifies that the sum of the positions on all postings to non-income accounts is below tolerance. This provides yet another level of verification and allows you to elide the income amounts, knowing that the price is there to provide an extra level of error-checking in case you enter a typo. beancount.plugins.sellgains.SellGainsError ( tuple ) \uf0c1 SellGainsError(source, message, entry) beancount.plugins.sellgains.SellGainsError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/sellgains.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.sellgains.SellGainsError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of SellGainsError(source, message, entry) beancount.plugins.sellgains.SellGainsError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/sellgains.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.sellgains.validate_sell_gains(entries, options_map) \uf0c1 Check the sum of asset account totals for lots sold with a price on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/sellgains.py def validate_sell_gains(entries, options_map): \"\"\"Check the sum of asset account totals for lots sold with a price on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] acc_types = options.get_account_types(options_map) proceed_types = set([acc_types.assets, acc_types.liabilities, acc_types.equity, acc_types.expenses]) for entry in entries: if not isinstance(entry, data.Transaction): continue # Find transactions whose lots at cost all have a price. postings_at_cost = [posting for posting in entry.postings if posting.cost is not None] if not postings_at_cost or not all(posting.price is not None for posting in postings_at_cost): continue # Accumulate the total expected proceeds and the sum of the asset and # expenses legs. total_price = inventory.Inventory() total_proceeds = inventory.Inventory() for posting in entry.postings: # If the posting is held at cost, add the priced value to the balance. if posting.cost is not None: assert posting.price is not None price = posting.price total_price.add_amount(amount.mul(price, -posting.units.number)) else: # Otherwise, use the weight and ignore postings to Income accounts. atype = account_types.get_account_type(posting.account) if atype in proceed_types: total_proceeds.add_amount(convert.get_weight(posting)) # Compare inventories, currency by currency. dict_price = {pos.units.currency: pos.units.number for pos in total_price} dict_proceeds = {pos.units.currency: pos.units.number for pos in total_proceeds} tolerances = interpolate.infer_tolerances(entry.postings, options_map) invalid = False for currency, price_number in dict_price.items(): # Accept a looser than usual tolerance because rounding occurs # differently. Also, it would be difficult for the user to satisfy # two sets of constraints manually. tolerance = tolerances.get(currency) * EXTRA_TOLERANCE_MULTIPLIER proceeds_number = dict_proceeds.pop(currency, ZERO) diff = abs(price_number - proceeds_number) if diff > tolerance: invalid = True break if invalid or dict_proceeds: errors.append( SellGainsError( entry.meta, \"Invalid price vs. proceeds/gains: {} vs. {}\".format( total_price, total_proceeds), entry)) return entries, errors beancount.plugins.split_expenses \uf0c1 Split expenses of a Beancount ledger between multiple people. This plugin is given a list of names. It assumes that any Expenses account whose components do not include any of the given names are to be split between the members. It goes through all the transactions and converts all such postings into multiple postings, one for each member. For example, given the names 'Martin' and 'Caroline', the following transaction: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation Will be converted to this: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation:Martin 134.50 USD Expenses:Accommodation:Caroline 134.50 USD After these transformations, all account names should include the name of a member. You can generate reports for a particular person by filtering postings to accounts with a component by their name. beancount.plugins.split_expenses.get_participants(filename, options_map) \uf0c1 Get the list of participants from the plugin configuration in the input file. Parameters: options_map \u2013 The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Exceptions: KeyError \u2013 If the configuration does not contain configuration for the list Source code in beancount/plugins/split_expenses.py def get_participants(filename, options_map): \"\"\"Get the list of participants from the plugin configuration in the input file. Args: options_map: The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Raises: KeyError: If the configuration does not contain configuration for the list of participants. \"\"\" plugin_options = dict(options_map[\"plugin\"]) try: return plugin_options[\"beancount.plugins.split_expenses\"].split() except KeyError: raise KeyError(\"Could not find the split_expenses plugin configuration.\") beancount.plugins.split_expenses.main() \uf0c1 Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. Source code in beancount/plugins/split_expenses.py def main(): \"\"\"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') parser.add_argument('-c', '--currency', action='store', help=\"Convert all the amounts to a single common currency\") oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output-text', '--text', action='store', help=\"Render results to text boxes\") oparser.add_argument('--output-csv', '--csv', action='store', help=\"Render results to CSV files\") oparser.add_argument('--output-stdout', '--stdout', action='store_true', help=\"Render results to stdout\") args = parser.parse_args() # Ensure the directories exist. for directory in [args.output_text, args.output_csv]: if directory and not path.exists(directory): os.makedirs(directory, exist_ok=True) # Load the input file and get the list of participants. entries, errors, options_map = loader.load_file(args.filename) participants = get_participants(args.filename, options_map) for participant in participants: print(\"Participant: {}\".format(participant)) save_query(\"balances\", participant, entries, options_map, r\"\"\" SELECT PARENT(account) AS account, CONV[SUM(position)] AS amount WHERE account ~ ':\\b{}' GROUP BY 1 ORDER BY 2 DESC \"\"\", participant, boxed=False, args=args) save_query(\"expenses\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, PARENT(account) AS account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Expenses.*\\b{}' \"\"\", participant, args=args) save_query(\"income\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Income.*\\b{}' \"\"\", participant, args=args) save_query(\"final\", None, entries, options_map, r\"\"\" SELECT GREP('\\b({})\\b', account) AS participant, CONV[SUM(position)] AS balance GROUP BY 1 ORDER BY 2 \"\"\", '|'.join(participants), args=args) # FIXME: Make this output to CSV files and upload to a spreadsheet. # FIXME: Add a fixed with option. This requires changing adding this to the # the renderer to be able to have elastic space and line splitting.. beancount.plugins.split_expenses.save_query(title, participant, entries, options_map, sql_query, *format_args, *, boxed=True, spaced=False, args=None) \uf0c1 Save the multiple files for this query. Parameters: title \u2013 A string, the title of this particular report to render. participant \u2013 A string, the name of the participant under consideration. entries \u2013 A list of directives (as per the loader). options_map \u2013 A dict of options (as per the loader). sql_query \u2013 A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args \u2013 A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed \u2013 A boolean, true if we should render the results in a fancy-looking ASCII box. spaced \u2013 If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args \u2013 A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. Source code in beancount/plugins/split_expenses.py def save_query(title, participant, entries, options_map, sql_query, *format_args, boxed=True, spaced=False, args=None): \"\"\"Save the multiple files for this query. Args: title: A string, the title of this particular report to render. participant: A string, the name of the participant under consideration. entries: A list of directives (as per the loader). options_map: A dict of options (as per the loader). sql_query: A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args: A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed: A boolean, true if we should render the results in a fancy-looking ASCII box. spaced: If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args: A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. \"\"\" # Replace CONV() to convert the currencies or not; if so, replace to # CONVERT(..., currency). replacement = (r'\\1' if args.currency is None else r'CONVERT(\\1, \"{}\")'.format(args.currency)) sql_query = re.sub(r'CONV\\[(.*?)\\]', replacement, sql_query) # Run the query. rtypes, rrows = query.run_query(entries, options_map, sql_query, *format_args, numberify=True) # The base of all filenames. filebase = title.replace(' ', '_') fmtopts = dict(boxed=boxed, spaced=spaced) # Output the text files. if args.output_text: basedir = (path.join(args.output_text, participant) if participant else args.output_text) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.txt') with open(filename, 'w') as file: query_render.render_text(rtypes, rrows, options_map['dcontext'], file, **fmtopts) # Output the CSV files. if args.output_csv: basedir = (path.join(args.output_csv, participant) if participant else args.output_csv) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.csv') with open(filename, 'w') as file: query_render.render_csv(rtypes, rrows, options_map['dcontext'], file, expand=False) if args.output_stdout: # Write out the query to stdout. query_render.render_text(rtypes, rrows, options_map['dcontext'], sys.stdout, **fmtopts) beancount.plugins.split_expenses.split_expenses(entries, options_map, config) \uf0c1 Split postings according to expenses (see module docstring for details). Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. Source code in beancount/plugins/split_expenses.py def split_expenses(entries, options_map, config): \"\"\"Split postings according to expenses (see module docstring for details). Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. \"\"\" # Validate and sanitize configuration. if isinstance(config, str): members = config.split() elif isinstance(config, (tuple, list)): members = config else: raise RuntimeError(\"Invalid plugin configuration: configuration for split_expenses \" \"should be a string or a sequence.\") acctypes = options.get_account_types(options_map) def is_expense_account(account): return account_types.get_account_type(account) == acctypes.expenses # A predicate to quickly identify if an account contains the name of a # member. is_individual_account = re.compile('|'.join(map(re.escape, members))).search # Existing and previously unseen accounts. new_accounts = set() # Filter the entries and transform transactions. new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): new_postings = [] for posting in entry.postings: if (is_expense_account(posting.account) and not is_individual_account(posting.account)): # Split this posting into multiple postings. split_units = amount.Amount(posting.units.number / len(members), posting.units.currency) for member in members: # Mark the account as new if never seen before. subaccount = account.join(posting.account, member) new_accounts.add(subaccount) # Ensure the modified postings are marked as # automatically calculated, so that the resulting # calculated amounts aren't used to affect inferred # tolerances. meta = posting.meta.copy() if posting.meta else {} meta[interpolate.AUTOMATIC_META] = True # Add a new posting for each member, to a new account # with the name of this member. new_postings.append( posting._replace(meta=meta, account=subaccount, units=split_units, cost=posting.cost)) else: new_postings.append(posting) # Modify the entry in-place, replace its postings. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Create Open directives for new subaccounts if necessary. oc_map = getters.get_account_open_close(entries) open_date = entries[0].date meta = data.new_metadata('', 0) open_entries = [] for new_account in new_accounts: if new_account not in oc_map: entry = data.Open(meta, open_date, new_account, None, None) open_entries.append(entry) return open_entries + new_entries, [] beancount.plugins.tag_pending \uf0c1 An example of tracking unpaid payables or receivables. A user with lots of invoices to track may want to produce a report of pending or incomplete payables or receivables. Beancount does not by default offer such a dedicated feature, but it is easy to build one by using existing link attributes on transactions. This is an example on how to implement that with a plugin. For example, assuming the user enters linked transactions like this: 2013-03-28 * \"Bill for datacenter electricity\" ^invoice-27a30ab61191 Expenses:Electricity 450.82 USD Liabilities:AccountsPayable 2013-04-15 * \"Paying electricity company\" ^invoice-27a30ab61191 Assets:Checking -450.82 USD Liabilities:AccountsPayable Transactions are grouped by link (\"invoice-27a30ab61191\") and then the intersection of their common accounts is automatically calculated (\"Liabilities:AccountsPayable\"). We then add up the balance of all the postings for this account in this link group and check if the sum is zero. If there is a residual amount in this balance, we mark the associated entries as incomplete by inserting a #PENDING tag on them. The user can then use that tag to navigate to the corresponding view in the web interface, or just find the entries and produce a listing of them. beancount.plugins.tag_pending.tag_pending_plugin(entries, options_map) \uf0c1 A plugin that finds and tags pending transactions. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/tag_pending.py def tag_pending_plugin(entries, options_map): \"\"\"A plugin that finds and tags pending transactions. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" return (tag_pending_transactions(entries, 'PENDING'), []) beancount.plugins.tag_pending.tag_pending_transactions(entries, tag_name='PENDING') \uf0c1 Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Parameters: entries \u2013 A list of directives/transactions to process. tag_name \u2013 A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. Source code in beancount/plugins/tag_pending.py def tag_pending_transactions(entries, tag_name='PENDING'): \"\"\"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Args: entries: A list of directives/transactions to process. tag_name: A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. \"\"\" link_groups = basicops.group_entries_by_link(entries) pending_entry_ids = set() for link, link_entries in link_groups.items(): assert link_entries if len(link_entries) == 1: # If a single entry is present, it is assumed incomplete. pending_entry_ids.add(id(link_entries[0])) else: # Compute the sum total balance of the common accounts. common_accounts = basicops.get_common_accounts(link_entries) common_balance = inventory.Inventory() for entry in link_entries: for posting in entry.postings: if posting.account in common_accounts: common_balance.add_position(posting) # Mark entries as pending if a residual balance is found. if not common_balance.is_empty(): for entry in link_entries: pending_entry_ids.add(id(entry)) # Insert tags if marked. return [(entry._replace(tags=(entry.tags or set()) | set((tag_name,))) if id(entry) in pending_entry_ids else entry) for entry in entries] beancount.plugins.unique_prices \uf0c1 This module adds validation that there is a single price defined per date and base/quote currencies. If multiple conflicting price values are declared, an error is generated. Note that multiple price entries with the same number do not generate an error. This is meant to be turned on if you want to use a very strict mode for entering prices, and may not be realistic usage. For example, if you have (1) a transaction with an implicitly generated price during the day (from its cost) and (2) a separate explicit price directive that declares a different price for the day's closing price, this would generate an error. I'm not certain this will be useful in the long run, so placing it in a plugin. beancount.plugins.unique_prices.UniquePricesError ( tuple ) \uf0c1 UniquePricesError(source, message, entry) beancount.plugins.unique_prices.UniquePricesError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unique_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.unique_prices.UniquePricesError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UniquePricesError(source, message, entry) beancount.plugins.unique_prices.UniquePricesError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/unique_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.unique_prices.validate_unique_prices(entries, unused_options_map) \uf0c1 Check that there is only a single price per day for a particular base/quote. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. Source code in beancount/plugins/unique_prices.py def validate_unique_prices(entries, unused_options_map): \"\"\"Check that there is only a single price per day for a particular base/quote. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. \"\"\" new_entries = [] errors = [] prices = collections.defaultdict(list) for entry in entries: if not isinstance(entry, data.Price): continue key = (entry.date, entry.currency, entry.amount.currency) prices[key].append(entry) errors = [] for price_entries in prices.values(): if len(price_entries) > 1: number_map = {price_entry.amount.number: price_entry for price_entry in price_entries} if len(number_map) > 1: # Note: This should be a list of entries for better error # reporting. (Later.) error_entry = next(iter(number_map.values())) errors.append( UniquePricesError(error_entry.meta, \"Disagreeing price entries\", price_entries)) return new_entries, errors beancount.plugins.unrealized \uf0c1 Compute unrealized gains. The configuration for this plugin is a single string, the name of the subaccount to add to post the unrealized gains to, like this: plugin \"beancount.plugins.unrealized\" \"Unrealized\" If you don't specify a name for the subaccount (the configuration value is optional), by default it inserts the unrealized gains in the same account that is being adjusted. beancount.plugins.unrealized.UnrealizedError ( tuple ) \uf0c1 UnrealizedError(source, message, entry) beancount.plugins.unrealized.UnrealizedError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unrealized.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.unrealized.UnrealizedError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UnrealizedError(source, message, entry) beancount.plugins.unrealized.UnrealizedError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/unrealized.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.unrealized.add_unrealized_gains(entries, options_map, subaccount=None) \uf0c1 Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. subaccount \u2013 A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/unrealized.py def add_unrealized_gains(entries, options_map, subaccount=None): \"\"\"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. subaccount: A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" errors = [] meta = data.new_metadata('', 0) account_types = options.get_account_types(options_map) # Assert the subaccount name is in valid format. if subaccount: validation_account = account.join(account_types.assets, subaccount) if not account.is_valid(validation_account): errors.append( UnrealizedError(meta, \"Invalid subaccount name: '{}'\".format(subaccount), None)) return entries, errors if not entries: return (entries, errors) # Group positions by (account, cost, cost_currency). price_map = prices.build_price_map(entries) holdings_list = holdings.get_final_holdings(entries, price_map=price_map) # Group positions by (account, cost, cost_currency). holdings_list = holdings.aggregate_holdings_by( holdings_list, lambda h: (h.account, h.currency, h.cost_currency)) # Get the latest prices from the entries. price_map = prices.build_price_map(entries) # Create transactions to account for each position. new_entries = [] latest_date = entries[-1].date for index, holding in enumerate(holdings_list): if (holding.currency == holding.cost_currency or holding.cost_currency is None): continue # Note: since we're only considering positions held at cost, the # transaction that created the position *must* have created at least one # price point for that commodity, so we never expect for a price not to # be available, which is reasonable. if holding.price_number is None: # An entry without a price might indicate that this is a holding # resulting from leaked cost basis. {0ed05c502e63, b/16} if holding.number: errors.append( UnrealizedError(meta, \"A valid price for {h.currency}/{h.cost_currency} \" \"could not be found\".format(h=holding), None)) continue # Compute the PnL; if there is no profit or loss, we create a # corresponding entry anyway. pnl = holding.market_value - holding.book_value if holding.number == ZERO: # If the number of units sum to zero, the holdings should have been # zero. errors.append( UnrealizedError( meta, \"Number of units of {} in {} in holdings sum to zero \" \"for account {} and should not\".format( holding.currency, holding.cost_currency, holding.account), None)) continue # Compute the name of the accounts and add the requested subaccount name # if requested. asset_account = holding.account income_account = account.join(account_types.income, account.sans_root(holding.account)) if subaccount: asset_account = account.join(asset_account, subaccount) income_account = account.join(income_account, subaccount) # Create a new transaction to account for this difference in gain. gain_loss_str = \"gain\" if pnl > ZERO else \"loss\" narration = (\"Unrealized {} for {h.number} units of {h.currency} \" \"(price: {h.price_number:.4f} {h.cost_currency} as of {h.price_date}, \" \"average cost: {h.cost_number:.4f} {h.cost_currency})\").format( gain_loss_str, h=holding) entry = data.Transaction(data.new_metadata(meta[\"filename\"], lineno=1000 + index), latest_date, flags.FLAG_UNREALIZED, None, narration, EMPTY_SET, EMPTY_SET, []) # Book this as income, converting the account name to be the same, but as income. # Note: this is a rather convenient but arbitrary choice--maybe it would be best to # let the user decide to what account to book it, but I don't a nice way to let the # user specify this. # # Note: we never set a price because we don't want these to end up in Conversions. entry.postings.extend([ data.Posting( asset_account, amount.Amount(pnl, holding.cost_currency), None, None, None, None), data.Posting( income_account, amount.Amount(-pnl, holding.cost_currency), None, None, None, None) ]) new_entries.append(entry) # Ensure that the accounts we're going to use to book the postings exist, by # creating open entries for those that we generated that weren't already # existing accounts. new_accounts = {posting.account for entry in new_entries for posting in entry.postings} open_entries = getters.get_account_open_close(entries) new_open_entries = [] for account_ in sorted(new_accounts): if account_ not in open_entries: meta = data.new_metadata(meta[\"filename\"], index) open_entry = data.Open(meta, latest_date, account_, None, None) new_open_entries.append(open_entry) return (entries + new_open_entries + new_entries, errors) beancount.plugins.unrealized.get_unrealized_entries(entries) \uf0c1 Return entries automatically created for unrealized gains. Parameters: entries \u2013 A list of directives. Returns: A list of directives, all of which are in the original list. Source code in beancount/plugins/unrealized.py def get_unrealized_entries(entries): \"\"\"Return entries automatically created for unrealized gains. Args: entries: A list of directives. Returns: A list of directives, all of which are in the original list. \"\"\" return [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.flag == flags.FLAG_UNREALIZED)]","title":"beancount.plugins"},{"location":"api_reference/beancount.plugins.html#beancountplugins","text":"Example plugins for filtering transactions. These are various examples of how to filter entries in various creative ways. IMPORTANT: These are not meant to be complete features, rather just experiments in problem-solving using Beancount, work-in-progress that can be selectively installed via a --plugin option, or one-offs to answer questions on the mailing-list.","title":"beancount.plugins"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto","text":"A plugin of plugins which triggers are all the automatic and lax plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. You can just import the \"auto\" plugin. Put that in a macro. Also see: the 'pedantic' plugin.","title":"auto"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto_accounts","text":"This module automatically inserts Open directives for accounts not opened (at the date of the first entry) and automatically removes open directives for unused accounts. This can be used as a convenience for doing demos, or when setting up your initial transactions, as an intermediate step.","title":"auto_accounts"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto_accounts.auto_insert_open","text":"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/auto_accounts.py def auto_insert_open(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" opened_accounts = {entry.account for entry in entries if isinstance(entry, data.Open)} new_entries = [] accounts_first, _ = getters.get_accounts_use_map(entries) for index, (account, date_first_used) in enumerate(sorted(accounts_first.items())): if account not in opened_accounts: meta = data.new_metadata('', index) new_entries.append(data.Open(meta, date_first_used, account, None, None)) if new_entries: new_entries.extend(entries) new_entries.sort(key=data.entry_sortkey) else: new_entries = entries return new_entries, []","title":"auto_insert_open()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions","text":"A plugin that automatically converts postings at price to postings held at cost, applying an automatic booking algorithm in assigning the cost bases and matching lots. This plugin restricts itself to applying these transformations within a particular account, which you provide. For each of those accounts, it also requires a corresponding Income account to book the profit/loss of reducing lots (i.e., sales): plugin \"beancount.plugins.book_conversions\" \"Assets:Bitcoin,Income:Bitcoin\" Then, simply input the transactions with price conversion. We use \"Bitcoins\" in this example, converting Bitcoin purchases that were carried out as currency into maintaining these with cost basis, for tax reporting purposes: 2015-09-04 * \"Buy some bitcoins\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.333507 BTC @ 230.76 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.345747 BTC @ 230.11 USD 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -6.000000 BTC @ 230.50 USD Expenses:Something The result is that cost bases are inserted on augmenting lots: 2015-09-04 * \"Buy some bitcoins\" Assets:Bitcoin 4.333507 BTC {230.76 USD} @ 230.76 USD Assets:Bank -1000.00 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bitcoin 4.345747 BTC {230.11 USD} @ 230.11 USD Assets:Bank -1000.00 USD While on reducing lots, matching FIFO lots are automatically found and the corresponding cost basis added: 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -4.333507 BTC {230.76 USD} @ 230.50 USD Assets:Bitcoin -1.666493 BTC {230.11 USD} @ 230.50 USD Income:Bitcoin 0.47677955 USD Expenses:Something 1383.00000000 USD Note that multiple lots were required to fulfill the sale quantity here. As in this example, this may result in multiple lots being created for a single one. Finally, Beancount will eventually support booking methods built-in, but this is a quick method that shows how to hack your own booking method via transformations of the postings that run in a plugin. Implementation notes: This code uses the FIFO method only for now. However, it would be very easy to customize it to provide other booking methods, e.g. LIFO, or otherwise. This will be added eventually, and I'm hoping to reuse the same inventory abstractions that will be used to implement the fallback booking methods from the booking proposal review (http://furius.ca/beancount/doc/proposal-booking). Instead of keeping a list of (Position, Transaction) pairs for the pending FIFO lots, we really ought to use a beancount.core.inventory.Inventory instance. However, the class does not contain sufficient data to carry out FIFO booking at the moment. A newer implementation, living in the \"booking\" branch, does, and will be used in the future. This code assumes that a positive number of units is an augmenting lot and a reducing one has a negative number of units, though we never call them that way on purpose (to eventually allow this code to handle short positions). This is not strictly true; however, we would need an Inventory in order to figrue this out. This will be done in the future and is not difficult to do. IMPORTANT: This plugin was developed before the booking methods (FIFO, LIFO, and others) were fully implemented in Beancount. It was built to answer a question on the mailing-list about FIFO booking. You probably don't need to use them anymore. Always prefer to use the native syntax instead of this.","title":"book_conversions"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError","text":"BookConversionError(source, message, entry)","title":"BookConversionError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__new__","text":"Create new instance of BookConversionError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError","text":"ConfigError(source, message, entry)","title":"ConfigError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__new__","text":"Create new instance of ConfigError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.augment_inventory","text":"Add the lots from the given posting to the running inventory. Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. entry \u2013 The parent transaction. eindex \u2013 The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. Source code in beancount/plugins/book_conversions.py def augment_inventory(pending_lots, posting, entry, eindex): \"\"\"Add the lots from the given posting to the running inventory. Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. entry: The parent transaction. eindex: The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. \"\"\" number = posting.units.number new_posting = posting._replace( units=copy.copy(posting.units), cost=position.Cost(posting.price.number, posting.price.currency, entry.date, None)) pending_lots.append(([number], new_posting, eindex)) return new_posting","title":"augment_inventory()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.book_price_conversions","text":"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Parameters: entries \u2013 A list of entry instances. assets_account \u2013 An account string, the name of the account to process. income_account \u2013 An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. Source code in beancount/plugins/book_conversions.py def book_price_conversions(entries, assets_account, income_account): \"\"\"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Args: entries: A list of entry instances. assets_account: An account string, the name of the account to process. income_account: An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. \"\"\" # Pairs of (Position, Transaction) instances used to match augmenting # entries with reducing ones. pending_lots = [] # A list of pairs of matching (augmenting, reducing) postings. all_matches = [] new_entries = [] errors = [] for eindex, entry in enumerate(entries): # Figure out if this transaction has postings in Bitcoins without a cost. # The purpose of this plugin is to fixup those. if isinstance(entry, data.Transaction) and any(is_matching(posting, assets_account) for posting in entry.postings): # Segregate the reducing lots, augmenting lots and other lots. augmenting, reducing, other = [], [], [] for pindex, posting in enumerate(entry.postings): if is_matching(posting, assets_account): out = augmenting if posting.units.number >= ZERO else reducing else: out = other out.append(posting) # We will create a replacement list of postings with costs filled # in, possibly more than the original list, to account for the # different lots. new_postings = [] # Convert all the augmenting postings to cost basis. for posting in augmenting: new_postings.append(augment_inventory(pending_lots, posting, entry, eindex)) # Then process reducing postings. if reducing: # Process all the reducing postings, booking them to matching lots. pnl = inventory.Inventory() for posting in reducing: rpostings, matches, posting_pnl, new_errors = ( reduce_inventory(pending_lots, posting, eindex)) new_postings.extend(rpostings) all_matches.extend(matches) errors.extend(new_errors) pnl.add_amount(amount.Amount(posting_pnl, posting.price.currency)) # If some reducing lots were seen in this transaction, insert an # Income leg to absorb the P/L. We need to do this for each currency # which incurred P/L. if not pnl.is_empty(): for pos in pnl: meta = data.new_metadata('', 0) new_postings.append( data.Posting(income_account, -pos.units, None, None, None, meta)) # Third, add back all the other unrelated legs in. for posting in other: new_postings.append(posting) # Create a replacement entry. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Add matching metadata to all matching postings. mod_matches = link_entries_with_metadata(new_entries, all_matches) # Resolve the indexes to their possibly modified Transaction instances. matches = [(data.TxnPosting(new_entries[aug_index], aug_posting), data.TxnPosting(new_entries[red_index], red_posting)) for (aug_index, aug_posting), (red_index, red_posting) in mod_matches] return new_entries, errors, matches","title":"book_price_conversions()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.book_price_conversions_plugin","text":"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. config \u2013 A string, in \",\" format. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. Source code in beancount/plugins/book_conversions.py def book_price_conversions_plugin(entries, options_map, config): \"\"\"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. config: A string, in \",\" format. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. \"\"\" # The expected configuration is two account names, separated by whitespace. errors = [] try: assets_account, income_account = re.split(r'[,; \\t]', config) if not account.is_valid(assets_account) or not account.is_valid(income_account): raise ValueError(\"Invalid account string\") except ValueError as exc: errors.append( ConfigError( None, ('Invalid configuration: \"{}\": {}, skipping booking').format(config, exc), None)) return entries, errors new_entries, errors, _ = book_price_conversions(entries, assets_account, income_account) return new_entries, errors","title":"book_price_conversions_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.extract_trades","text":"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Parameters: entries \u2013 The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). Source code in beancount/plugins/book_conversions.py def extract_trades(entries): \"\"\"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Args: entries: The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). \"\"\" trade_map = collections.defaultdict(list) for index, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue for posting in entry.postings: links_str = posting.meta.get(META, None) if links_str: links = links_str.split(',') for link in links: trade_map[link].append((index, entry, posting)) # Sort matches according to the index of the first entry, drop the index # used for doing this, and convert the objects to tuples.. trades = [(data.TxnPosting(augmenting[1], augmenting[2]), data.TxnPosting(reducing[1], reducing[2])) for augmenting, reducing in sorted(trade_map.values())] # Sanity check. for matches in trades: assert len(matches) == 2 return trades","title":"extract_trades()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.is_matching","text":"\"Identify if the given posting is one to be booked. Parameters: posting \u2013 An instance of a Posting. account \u2013 The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. Source code in beancount/plugins/book_conversions.py def is_matching(posting, account): \"\"\"\"Identify if the given posting is one to be booked. Args: posting: An instance of a Posting. account: The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. \"\"\" return (posting.account == account and posting.cost is None and posting.price is not None)","title":"is_matching()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.link_entries_with_metadata","text":"Modify the entries in-place to add matching links to postings. Parameters: entries \u2013 The list of entries to modify. all_matches \u2013 A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. Source code in beancount/plugins/book_conversions.py def link_entries_with_metadata(entries, all_matches): \"\"\"Modify the entries in-place to add matching links to postings. Args: entries: The list of entries to modify. all_matches: A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. \"\"\" # Allocate trade names and compute a map of posting to trade names. link_map = collections.defaultdict(list) for (aug_index, aug_posting), (red_index, red_posting) in all_matches: link = 'trade-{}'.format(str(uuid.uuid4()).split('-')[-1]) link_map[id(aug_posting)].append(link) link_map[id(red_posting)].append(link) # Modify the postings. postings_repl_map = {} for entry in entries: if isinstance(entry, data.Transaction): for index, posting in enumerate(entry.postings): links = link_map.pop(id(posting), None) if links: new_posting = posting._replace(meta=posting.meta.copy()) new_posting.meta[META] = ','.join(links) entry.postings[index] = new_posting postings_repl_map[id(posting)] = new_posting # Just a sanity check. assert not link_map, \"Internal error: not all matches found.\" # Return a list of the modified postings (mapping the old matches to the # newly created ones). return [((aug_index, postings_repl_map[id(aug_posting)]), (red_index, postings_repl_map[id(red_posting)])) for (aug_index, aug_posting), (red_index, red_posting) in all_matches]","title":"link_entries_with_metadata()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.main","text":"Extract trades from metadata-annotated postings and report on them. Source code in beancount/plugins/book_conversions.py def main(): \"\"\"Extract trades from metadata-annotated postings and report on them. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output', action='store', help=\"Filename to output results to (default goes to stdout)\") oparser.add_argument('-f', '--format', default='text', choices=['text', 'csv'], help=\"Output format to render to (text, csv)\") args = parser.parse_args() # Load the input file. entries, errors, options_map = loader.load_file(args.filename) # Get the list of trades. trades = extract_trades(entries) # Produce a table of all the trades. columns = ('units currency cost_currency ' 'buy_date buy_price sell_date sell_price pnl').split() header = ['Units', 'Currency', 'Cost Currency', 'Buy Date', 'Buy Price', 'Sell Date', 'Sell Price', 'P/L'] body = [] for aug, red in trades: units = -red.posting.units.number buy_price = aug.posting.price.number sell_price = red.posting.price.number pnl = (units * (sell_price - buy_price)).quantize(buy_price) body.append([ -red.posting.units.number, red.posting.units.currency, red.posting.price.currency, aug.txn.date.isoformat(), buy_price, red.txn.date.isoformat(), sell_price, pnl ]) trades_table = table.Table(columns, header, body) # Render the table as text or CSV. outfile = open(args.output, 'w') if args.output else sys.stdout table.render_table(trades_table, outfile, args.format)","title":"main()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.reduce_inventory","text":"Match a reducing posting against a list of lots (using FIFO order). Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. eindex \u2013 The index of the parent transaction housing this posting. Returns: A tuple of postings \u2013 A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. Source code in beancount/plugins/book_conversions.py def reduce_inventory(pending_lots, posting, eindex): \"\"\"Match a reducing posting against a list of lots (using FIFO order). Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. eindex: The index of the parent transaction housing this posting. Returns: A tuple of postings: A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. \"\"\" new_postings = [] matches = [] pnl = ZERO errors = [] match_number = -posting.units.number match_currency = posting.units.currency cost_currency = posting.price.currency while match_number != ZERO: # Find the first lot with matching currency. for fnumber, fposting, findex in pending_lots: funits = fposting.units fcost = fposting.cost if (funits.currency == match_currency and fcost and fcost.currency == cost_currency): assert fnumber[0] > ZERO, \"Internal error, zero lot\" break else: errors.append( BookConversionError(posting.meta, \"Could not match position {}\".format(posting), None)) break # Reduce the pending lots. number = min(match_number, fnumber[0]) cost = fcost match_number -= number fnumber[0] -= number if fnumber[0] == ZERO: pending_lots.pop(0) # Add a corresponding posting. rposting = posting._replace( units=amount.Amount(-number, posting.units.currency), cost=copy.copy(cost)) new_postings.append(rposting) # Update the P/L. pnl += number * (posting.price.number - cost.number) # Add to the list of matches. matches.append(((findex, fposting), (eindex, rposting))) return new_postings, matches, pnl, errors","title":"reduce_inventory()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost","text":"A plugin that ensures cost basis is preserved in unbooked transactions. This is intended to be used in accounts using the \"NONE\" booking method, to manually ensure that the sum total of the cost basis of reducing legs matches the average of what's in the account inventory. This is a partial first step toward implementing the \"AVERAGE\" booking method. In other words, this plugins provides assertions that will constrain you to approximate what the \"AVERAGE\" booking method will do, manually, and not to leak too much cost basis through unmatching bookings without checks. (Note the contrived context here: Ideally the \"NONE\" booking method would simply not exist.)","title":"check_average_cost"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError","text":"MatchBasisError(source, message, entry)","title":"MatchBasisError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_average_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__new__","text":"Create new instance of MatchBasisError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/check_average_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.validate_average_cost","text":"Check that reducing legs on unbooked postings are near the average cost basis. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 The configuration as a string version of a float. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_average_cost.py def validate_average_cost(entries, options_map, config_str=None): \"\"\"Check that reducing legs on unbooked postings are near the average cost basis. Args: entries: A list of directives. unused_options_map: An options map. config_str: The configuration as a string version of a float. Returns: A list of new errors, if any were found. \"\"\" # Initialize tolerance bounds. if config_str and config_str.strip(): # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, float): raise RuntimeError(\"Invalid configuration for check_average_cost: \" \"must be a float\") tolerance = config_obj else: tolerance = DEFAULT_TOLERANCE min_tolerance = D(1 - tolerance) max_tolerance = D(1 + tolerance) errors = [] ocmap = getters.get_account_open_close(entries) balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, Transaction): for posting in entry.postings: dopen = ocmap.get(posting.account, None) # Only process accounts with a NONE booking value. if dopen and dopen[0] and dopen[0].booking == Booking.NONE: balance = balances[(posting.account, posting.units.currency, posting.cost.currency if posting.cost else None)] if posting.units.number < ZERO: average = balance.average().get_only_position() if average is not None: number = average.cost.number min_valid = number * min_tolerance max_valid = number * max_tolerance if not (min_valid <= posting.cost.number <= max_valid): errors.append( MatchBasisError( entry.meta, (\"Cost basis on reducing posting is too far from \" \"the average cost ({} vs. {})\".format( posting.cost.number, average.cost.number)), entry)) balance.add_position(posting) return entries, errors","title":"validate_average_cost()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_closing","text":"A plugin that automatically inserts a balance check on a tagged closing posting. Some postings are known to the user to be \"closing trades\", which means that the resulting position of the instrument just after the trade should be zero. For instance, this is the case for most ordinary options trading, only one lot of a particular instrument is held, and eventually expires or gets sold off in its entirely. One would like to confirm that, and the way to do this in Beancount is to insert a balance check. This plugin allows you to do that more simply, by inserting metadata. For example, this transaction: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD closing: TRUE Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL Would expand into the following two directives: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL 2018-02-17 balance Assets:US:Brokerage:Main:Options 0 QQQ180216C160 Insert the closing line when you know you're closing the position.","title":"check_closing"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_closing.check_closing","text":"Expand 'closing' metadata to a zero balance check. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_closing.py def check_closing(entries, options_map): \"\"\"Expand 'closing' metadata to a zero balance check. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.meta and posting.meta.get('closing', False): # Remove the metadata. meta = posting.meta.copy() del meta['closing'] entry = entry._replace(meta=meta) # Insert a balance. date = entry.date + datetime.timedelta(days=1) balance = data.Balance(data.new_metadata(\"\", 0), date, posting.account, amount.Amount(ZERO, posting.units.currency), None, None) new_entries.append(balance) new_entries.append(entry) return new_entries, []","title":"check_closing()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity","text":"A plugin that verifies that all seen commodities have a Commodity directive. This is useful if you're a bit pedantic and like to make sure that you're declared attributes for each of the commodities you use. It's useful if you use the portfolio export, for example.","title":"check_commodity"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError","text":"CheckCommodityError(source, message, entry)","title":"CheckCommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_commodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__new__","text":"Create new instance of CheckCommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/check_commodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.validate_commodity_directives","text":"Find all commodities used and ensure they have a corresponding Commodity directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_commodity.py def validate_commodity_directives(entries, options_map): \"\"\"Find all commodities used and ensure they have a corresponding Commodity directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" commodities_used = options_map['commodities'] errors = [] meta = data.new_metadata('', 0) commodity_map = getters.get_commodity_map(entries, create_missing=False) for currency in commodities_used: commodity_entry = commodity_map.get(currency, None) if commodity_entry is None: errors.append( CheckCommodityError( meta, \"Missing Commodity directive for '{}'\".format(currency), None)) return entries, errors","title":"validate_commodity_directives()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost","text":"This plugin validates that currencies held at cost aren't ever converted at price and vice-versa. This is usually the case, and using it will prevent users from making the mistake of selling a lot without specifying it via its cost basis.","title":"coherent_cost"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError","text":"CoherentCostError(source, message, entry)","title":"CoherentCostError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/coherent_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__new__","text":"Create new instance of CoherentCostError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/coherent_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.validate_coherent_cost","text":"Check that all currencies are either used at cost or not at all, but never both. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/coherent_cost.py def validate_coherent_cost(entries, unused_options_map): \"\"\"Check that all currencies are either used at cost or not at all, but never both. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] with_cost = {} without_cost = {} for entry in data.filter_txns(entries): for posting in entry.postings: target_set = without_cost if posting.cost is None else with_cost currency = posting.units.currency target_set.setdefault(currency, entry) for currency in set(with_cost) & set(without_cost): errors.append( CoherentCostError( without_cost[currency].meta, \"Currency '{}' is used both with and without cost\".format(currency), with_cost[currency])) # Note: We really ought to include both of the first transactions here. return entries, errors","title":"validate_coherent_cost()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr","text":"A plugin that asserts that all Commodity directives have a particular attribute and that it is part of a set of enum values. The configuration must be a mapping of attribute name to list of valid values, like this: plugin \"beancount.plugins.commodity_attr\" \"{ 'sector': ['Technology', 'Financials', 'Energy'], 'name': None, }\" The plugin issues an error if a Commodity directive is missing the attribute, or if the attribute value is not in the valid set. If you'd like to just ensure the attribute is set, set the list of valid values to None, as in the 'name' attribute in the example above.","title":"commodity_attr"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError","text":"CommodityError(source, message, entry)","title":"CommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__new__","text":"Create new instance of CommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError","text":"ConfigError(source, message, entry)","title":"ConfigError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__new__","text":"Create new instance of ConfigError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.validate_commodity_attr","text":"Check that all Commodity directives have a valid attribute. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 A configuration string. Returns: A list of new errors, if any were found. Source code in beancount/plugins/commodity_attr.py def validate_commodity_attr(entries, unused_options_map, config_str): \"\"\"Check that all Commodity directives have a valid attribute. Args: entries: A list of directives. unused_options_map: An options map. config_str: A configuration string. Returns: A list of new errors, if any were found. \"\"\" errors = [] # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): errors.append(ConfigError( data.new_metadata('', 0), \"Invalid configuration for commodity_attr plugin; skipping.\", None)) return entries, errors validmap = {attr: frozenset(values) if values is not None else None for attr, values in config_obj.items()} for entry in entries: if not isinstance(entry, data.Commodity): continue for attr, values in validmap.items(): value = entry.meta.get(attr, None) if value is None: errors.append(CommodityError( entry.meta, \"Missing attribute '{}' for Commodity directive {}\".format( attr, entry.currency), None)) continue if values and value not in values: errors.append(CommodityError( entry.meta, \"Invalid attribute '{}' for Commodity\".format(value) + \" directive {}; valid options: {}\".format( entry.currency, ', '.join(values)), None)) return entries, errors","title":"validate_commodity_attr()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts","text":"An implementation of currency accounts. This is an automatic implementation of the method described here: https://www.mathstat.dal.ca/~selinger/accounting/tutorial.html You enable it just like this: plugin \"beancount.plugins.currency_accounts\" \"Equity:CurrencyAccounts\" Accounts will be automatically created under the given base account, with the currency name appended to it, e.g., Equity:CurrencyAccounts:CAD Equity:CurrencyAccounts:USD etc., where used. You can have a look at the account balances with a query like this: bean-query $L \"select account, sum(position), convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts' \" The sum total of the converted amounts should be a number not too large: bean-query $L \"select convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts'\" WARNING: This is a prototype. Note the FIXMEs in the code below, which indicate some potential problems.","title":"currency_accounts"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.get_neutralizing_postings","text":"Process an entry. Parameters: curmap \u2013 A dict of currency to a list of Postings of this transaction. base_account \u2013 A string, the root account name to insert. new_accounts \u2013 A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. Source code in beancount/plugins/currency_accounts.py def get_neutralizing_postings(curmap, base_account, new_accounts): \"\"\"Process an entry. Args: curmap: A dict of currency to a list of Postings of this transaction. base_account: A string, the root account name to insert. new_accounts: A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. \"\"\" new_postings = [] for currency, postings in curmap.items(): # Compute the per-currency balance. inv = inventory.Inventory() for posting in postings: inv.add_amount(convert.get_cost(posting)) if inv.is_empty(): new_postings.extend(postings) continue # Re-insert original postings and remove price conversions. # # Note: This may cause problems if the implicit_prices plugin is # configured to run after this one, or if you need the price annotations # for some scripting or serious work. # # FIXME: We need to handle these important cases (they're not frivolous, # this is a prototype), probably by inserting some exceptions with # collaborating code in the booking (e.g. insert some metadata that # disables price conversions on those postings). # # FIXME(2): Ouch! Some of the residual seeps through here, where there # are more than a single currency block. This needs fixing too. You can # easily mitigate some of this to some extent, by excluding transactions # which don't have any price conversion in them. for pos in postings: if pos.price is not None: pos = pos._replace(price=None) new_postings.append(pos) # Insert the currency trading accounts postings. amount = inv.get_only_position().units acc = account.join(base_account, currency) new_accounts.add(acc) new_postings.append( Posting(acc, -amount, None, None, None, None)) return new_postings","title":"get_neutralizing_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.group_postings_by_weight_currency","text":"Return where this entry might require adjustment. Source code in beancount/plugins/currency_accounts.py def group_postings_by_weight_currency(entry: Transaction): \"\"\"Return where this entry might require adjustment.\"\"\" curmap = collections.defaultdict(list) has_price = False for posting in entry.postings: currency = posting.units.currency if posting.cost: currency = posting.cost.currency if posting.price: assert posting.price.currency == currency elif posting.price: has_price = True currency = posting.price.currency if posting.price: has_price = True curmap[currency].append(posting) return curmap, has_price","title":"group_postings_by_weight_currency()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.insert_currency_trading_postings","text":"Insert currency trading postings. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The base account name for currency trading accounts. Returns: A list of new errors, if any were found. Source code in beancount/plugins/currency_accounts.py def insert_currency_trading_postings(entries, options_map, config): \"\"\"Insert currency trading postings. Args: entries: A list of directives. unused_options_map: An options map. config: The base account name for currency trading accounts. Returns: A list of new errors, if any were found. \"\"\" base_account = config.strip() if not account.is_valid(base_account): base_account = DEFAULT_BASE_ACCOUNT errors = [] new_entries = [] new_accounts = set() for entry in entries: if isinstance(entry, Transaction): curmap, has_price = group_postings_by_weight_currency(entry) if has_price and len(curmap) > 1: new_postings = get_neutralizing_postings( curmap, base_account, new_accounts) entry = entry._replace(postings=new_postings) if META_PROCESSED: entry.meta[META_PROCESSED] = True new_entries.append(entry) earliest_date = entries[0].date open_entries = [ data.Open(data.new_metadata('', index), earliest_date, acc, None, None) for index, acc in enumerate(sorted(new_accounts))] return open_entries + new_entries, errors","title":"insert_currency_trading_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses","text":"For tagged transactions, convert expenses to a single account. This plugin allows you to select a tag and it automatically converts all the Expenses postings to use a single account. For example, with this input: plugin \"divert_expenses\" \"['kid', 'Expenses:Child']\" 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Food:Grocery 10.27 USD It will output: 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Child 10.27 USD You can limit the diversion to one posting only, like this: 2018-05-05 * \"CVS/PHARMACY\" \"\" #kai Liabilities:CreditCard -66.38 USD Expenses:Pharmacy 21.00 USD ;; Vitamins for Kai Expenses:Pharmacy 45.38 USD divert: FALSE See unit test for details. See this thread for context: https://docs.google.com/drawings/d/18fTrrGlmz0jFbfcGGHTffbdRwbmST8r9_3O26Dd1Xww/edit?usp=sharing","title":"divert_expenses"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses.divert_expenses","text":"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. options_map \u2013 A parser options dict. config_str \u2013 A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. Source code in beancount/plugins/divert_expenses.py def divert_expenses(entries, options_map, config_str): \"\"\"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. options_map: A parser options dict. config_str: A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. \"\"\" # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") tag = config_obj['tag'] replacement_account = config_obj['account'] acctypes = options.get_account_types(options_map) new_entries = [] errors = [] for entry in entries: if isinstance(entry, Transaction) and tag in entry.tags: entry = replace_diverted_accounts(entry, replacement_account, acctypes) new_entries.append(entry) return new_entries, errors","title":"divert_expenses()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses.replace_diverted_accounts","text":"Replace the Expenses accounts from the entry. Parameters: entry \u2013 A Transaction directive. replacement_account \u2013 A string, the account to use for replacement. acctypes \u2013 An AccountTypes instance. Returns: A possibly entry directive. Source code in beancount/plugins/divert_expenses.py def replace_diverted_accounts(entry, replacement_account, acctypes): \"\"\"Replace the Expenses accounts from the entry. Args: entry: A Transaction directive. replacement_account: A string, the account to use for replacement. acctypes: An AccountTypes instance. Returns: A possibly entry directive. \"\"\" new_postings = [] for posting in entry.postings: divert = posting.meta.get('divert', None) if posting.meta else None if (divert is True or ( divert is None and account_types.is_account_type(acctypes.expenses, posting.account))): posting = posting._replace(account=replacement_account, meta={'diverted_account': posting.account}) new_postings.append(posting) return entry._replace(postings=new_postings)","title":"replace_diverted_accounts()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.exclude_tag","text":"Exclude #virtual tags. This is used to demonstrate excluding a set of transactions from a particular tag. In this example module, the tag name is fixed, but if we integrated this we could provide a way to choose which tags to exclude. This is simply just another mechanism for selecting a subset of transactions. See discussion here for details: https://groups.google.com/d/msg/ledger-cli/N8Slh2t45K0/aAz0i3Be4LYJ","title":"exclude_tag"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.exclude_tag.exclude_tag","text":"Select all transactions that do not have a #virtual tag. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/exclude_tag.py def exclude_tag(entries, options_map): \"\"\"Select all transactions that do not have a #virtual tag. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" filtered_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or not entry.tags or EXCLUDED_TAG not in entry.tags)] return (filtered_entries, [])","title":"exclude_tag()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account","text":"Insert an posting with a default account when there is only a single posting. This is convenient to use in files which have mostly expenses, such as during a trip. Set the name of the default account to fill in as an option.","title":"fill_account"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError","text":"FillAccountError(source, message, entry)","title":"FillAccountError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fill_account.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__new__","text":"Create new instance of FillAccountError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/fill_account.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.fill_account","text":"Insert an posting with a default account when there is only a single posting. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 A parser options dict. insert_account \u2013 A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/fill_account.py def fill_account(entries, unused_options_map, insert_account): \"\"\"Insert an posting with a default account when there is only a single posting. Args: entries: A list of directives. unused_options_map: A parser options dict. insert_account: A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" if not account.is_valid(insert_account): return entries, [ FillAccountError(data.new_metadata('', 0), \"Invalid account name: '{}'\".format(insert_account), None)] new_entries = [] for entry in entries: if isinstance(entry, data.Transaction) and len(entry.postings) == 1: inv = inventory.Inventory() for posting in entry.postings: if posting.cost is None: inv.add_amount(posting.units) else: inv.add_amount(convert.get_cost(posting)) inv.reduce(convert.get_units) new_postings = list(entry.postings) for pos in inv: new_postings.append(data.Posting(insert_account, -pos.units, None, None, None, None)) entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, []","title":"fill_account()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees","text":"Rename payees based on a set of rules. This can be used to clean up dirty imported payee names. This plugin accepts a list of rules in this format: plugin \"beancount.plugins.fix_payees\" \"[ (PAYEE, MATCH1, MATCH2, ...), ]\" Each of the \"MATCH\" clauses is a string, in the format: \"A:<regexp>\" : Match the account name. \"D:<regexp>\" : Match the payee or the narration. The plugin matches the Transactions in the file and if there is a case-insensitive match against the regular expression (we use re.search()), replaces the payee name by \"PAYEE\". If multiple rules match, only the first rule is used. For example: plugin \"beancount.plugins.fix_payees\" \"[ (\"T-Mobile USA\", \"A:Expenses:Communications:Phone\", \"D:t-mobile\"), (\"Con Edison\", \"A:Expenses:Home:Electricity\", \"D:con ?ed\"), (\"Birreria @ Eataly\", \"D:EATALY BIRRERIA\"), ]\"","title":"fix_payees"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError","text":"FixPayeesError(source, message, entry)","title":"FixPayeesError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fix_payees.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__new__","text":"Create new instance of FixPayeesError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/fix_payees.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.fix_payees","text":"Rename payees based on a set of rules. See module docstring for details. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config \u2013 A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. Source code in beancount/plugins/fix_payees.py def fix_payees(entries, options_map, config): \"\"\"Rename payees based on a set of rules. See module docstring for details. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config: A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. \"\"\" errors = [] if config.strip(): try: expr = ast.literal_eval(config) except (SyntaxError, ValueError): meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Syntax error in config: {}\".format(config), None)) return entries, errors else: return entries, errors # Pre-compile the regular expressions for performance. rules = [] for rule in ast.literal_eval(config): clauses = iter(rule) new_payee = next(clauses) regexps = [] for clause in clauses: match = re.match('([AD]):(.*)', clause) if not match: meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Invalid clause: {}\".format(clause), None)) continue command, regexp = match.groups() regexps.append((command, re.compile(regexp, re.I).search)) new_rule = [new_payee] + regexps rules.append(tuple(new_rule)) # Run the rules over the transaction objects. new_entries = [] replaced_entries = {rule[0]: [] for rule in rules} for entry in entries: if isinstance(entry, data.Transaction): for rule in rules: clauses = iter(rule) new_payee = next(clauses) # Attempt to match all the clauses. for clause in clauses: command, func = clause if command == 'D': if not ((entry.payee is not None and func(entry.payee)) or (entry.narration is not None and func(entry.narration))): break elif command == 'A': if not any(func(posting.account) for posting in entry.postings): break else: # Make the replacement. entry = entry._replace(payee=new_payee) replaced_entries[new_payee].append(entry) new_entries.append(entry) if _DEBUG: # Print debugging info. for payee, repl_entries in sorted(replaced_entries.items(), key=lambda x: len(x[1]), reverse=True): print('{:60}: {}'.format(payee, len(repl_entries))) return new_entries, errors","title":"fix_payees()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.forecast","text":"An example of adding a forecasting feature to Beancount via a plugin. This entry filter plugin uses existing syntax to define and automatically inserted transactions in the future based on a convention. It serves mostly as an example of how you can experiment by creating and installing a local filter, and not so much as a serious forecasting feature (though the experiment is a good way to get something more general kickstarted eventually, I think the concept would generalize nicely and should eventually be added as a common feature of Beancount). A user can create a transaction like this: 2014-03-08 # \"Electricity bill [MONTHLY]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD and new transactions will be created monthly for the following year. Note the use of the '#' flag and the word 'MONTHLY' which defines the periodicity. The number of recurrences can optionally be specified either by providing an end date or by specifying the number of times that the transaction will be repeated. For example: 2014-03-08 # \"Electricity bill [MONTHLY UNTIL 2019-12-31]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [MONTHLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Transactions can also be repeated at yearly intervals, e.g.: 2014-03-08 # \"Electricity bill [YEARLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Other examples: 2014-03-08 # \"Electricity bill [WEEKLY SKIP 1 TIME REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [DAILY SKIP 3 TIMES REPEAT 1 TIME]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD","title":"forecast"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.forecast.forecast_plugin","text":"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file Returns: A tuple of entries and errors. Source code in beancount/plugins/forecast.py def forecast_plugin(entries, options_map): \"\"\"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Args: entries: a list of entry instances options_map: a dict of options parsed from the file Returns: A tuple of entries and errors. \"\"\" # Find the last entry's date. date_today = entries[-1].date # Filter out forecast entries from the list of valid entries. forecast_entries = [] filtered_entries = [] for entry in entries: outlist = (forecast_entries if (isinstance(entry, data.Transaction) and entry.flag == '#') else filtered_entries) outlist.append(entry) # Generate forecast entries up to the end of the current year. new_entries = [] for entry in forecast_entries: # Parse the periodicity. match = re.search(r'(^.*)\\[(MONTHLY|YEARLY|WEEKLY|DAILY)' r'(\\s+SKIP\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+REPEAT\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+UNTIL\\s+([0-9\\-]+))?\\]', entry.narration) if not match: new_entries.append(entry) continue forecast_narration = match.group(1).strip() forecast_interval = ( rrule.YEARLY if match.group(2).strip() == 'YEARLY' else rrule.WEEKLY if match.group(2).strip() == 'WEEKLY' else rrule.DAILY if match.group(2).strip() == 'DAILY' else rrule.MONTHLY) forecast_periodicity = {'dtstart': entry.date} if match.group(6): # e.g., [MONTHLY REPEAT 3 TIMES]: forecast_periodicity['count'] = int(match.group(6)) elif match.group(8): # e.g., [MONTHLY UNTIL 2020-01-01]: forecast_periodicity['until'] = datetime.datetime.strptime( match.group(8), '%Y-%m-%d').date() else: # e.g., [MONTHLY] forecast_periodicity['until'] = datetime.date( datetime.date.today().year, 12, 31) if match.group(4): # SKIP forecast_periodicity['interval'] = int(match.group(4)) + 1 # Generate a new entry for each forecast date. forecast_dates = [dt.date() for dt in rrule.rrule(forecast_interval, **forecast_periodicity)] for forecast_date in forecast_dates: forecast_entry = entry._replace(date=forecast_date, narration=forecast_narration) new_entries.append(forecast_entry) # Make sure the new entries inserted are sorted. new_entries.sort(key=data.entry_sortkey) return (filtered_entries + new_entries, [])","title":"forecast_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices","text":"This plugin synthesizes Price directives for all Postings with a price or directive or if it is an augmenting posting, has a cost directive.","title":"implicit_prices"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError","text":"ImplicitPriceError(source, message, entry)","title":"ImplicitPriceError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/implicit_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__new__","text":"Create new instance of ImplicitPriceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/implicit_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.add_implicit_prices","text":"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/implicit_prices.py def add_implicit_prices(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" new_entries = [] errors = [] # A dict of (date, currency, cost-currency) to price entry. new_price_entry_map = {} balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Always replicate the existing entries. new_entries.append(entry) if isinstance(entry, Transaction): # Inspect all the postings in the transaction. for posting in entry.postings: units = posting.units cost = posting.cost # Check if the position is matching against an existing # position. _, booking = balances[posting.account].add_position(posting) # Add prices when they're explicitly specified on a posting. An # explicitly specified price may occur in a conversion, e.g. # Assets:Account 100 USD @ 1.10 CAD # or, if a cost is also specified, as the current price of the # underlying instrument, e.g. # Assets:Account 100 HOOL {564.20} @ {581.97} USD if posting.price is not None: meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, posting.price) # Add costs, when we're not matching against an existing # position. This happens when we're just specifying the cost, # e.g. # Assets:Account 100 HOOL {564.20} elif (cost is not None and booking != inventory.Booking.REDUCED): meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, amount.Amount(cost.number, cost.currency)) else: price_entry = None if price_entry is not None: key = (price_entry.date, price_entry.currency, price_entry.amount.number, # Ideally should be removed. price_entry.amount.currency) try: new_price_entry_map[key] ## Do not fail for now. We still have many valid use ## cases of duplicate prices on the same date, for ## example, stock splits, or trades on two dates with ## two separate reported prices. We need to figure out a ## more elegant solution for this in the long term. ## Keeping both for now. We should ideally not use the ## number in the de-dup key above. # # dup_entry = new_price_entry_map[key] # if price_entry.amount.number == dup_entry.amount.number: # # Skip duplicates. # continue # else: # errors.append( # ImplicitPriceError( # entry.meta, # \"Duplicate prices for {} on {}\".format(entry, # dup_entry), # entry)) except KeyError: new_price_entry_map[key] = price_entry new_entries.append(price_entry) return new_entries, errors","title":"add_implicit_prices()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs","text":"Automatically adding IRA contributions postings. This plugin looks for increasing postings on specified accounts ('+' sign for Assets and Expenses accounts, '-' sign for the others), or postings with a particular flag on them and when it finds some, inserts a pair of postings on that transaction of the corresponding amounts in a different currency. The currency is intended to be an imaginary currency used to track the number of dollars contributed to a retirement account over time. For example, a possible configuration could be: plugin \"beancount.plugins.ira_contribs\" \"{ 'currency': 'IRAUSD', 'flag': 'M', 'accounts': { 'Income:US:Acme:Match401k': ( 'Assets:US:Federal:Match401k', 'Expenses:Taxes:TY{year}:US:Federal:Match401k'), ('C', 'Assets:US:Fidelity:PreTax401k:Cash'): ( 'Assets:US:Federal:PreTax401k', 'Expenses:Taxes:TY{year}:US:Federal:PreTax401k'), } }\" Note: In this example, the configuration that triggers on the \"Income:US:Acme:Match401k\" account does not require a flag for those accounts; the configuration for the \"Assets:US:Fidelity:PreTax401k:Cash\" account requires postings to have a \"C\" flag to trigger an insertion. Given a transaction like the following, which would be typical for a salary entry where the employer is automatically diverting some of the pre-tax money to a retirement account (in this example, at Fidelity): 2013-02-15 * \"ACME INC PAYROLL\" Income:US:Acme:Salary ... ... Assets:US:BofA:Checking ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD ... A posting with account 'Assets:US:Fidelity:PreTax401k:Cash', which is configured to match, would be found. The configuration above instructs the plugin to automatically insert new postings like this: 2013-02-15 * \"ACME INC PAYROLL\" ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD M Assets:US:Federal:PreTax401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:PreTax401k 620.50 IRAUSD ... Notice that the \"{year}\" string in the configuration's account names is automatically replaced by the current year in the account name. This is useful if you maintain separate tax accounts per year. Furthermore, as in the configuration example above, you may have multiple matching entries to trigger multiple insertions. For example, the employer may also match the employee's retirement contribution by depositing some money in the retirement account: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD In this example the funds get reported as invested immediately (an intermediate deposit into a cash account does not take place). The plugin configuration would match against the 'Income:US:Acme:Match401k' account and since it increases its value (the normal balance of an Income account is negative), postings would be inserted like this: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD M Assets:US:Federal:Match401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:Match401k 620.50 IRAUSD Note that the special dict keys 'currency' and 'flag' are used to specify which currency to use for the inserted postings, and if set, which flag to mark these postings with.","title":"ira_contribs"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs.add_ira_contribs","text":"Add legs for 401k employer match contributions. See module docstring for an example configuration. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config_str \u2013 A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. Source code in beancount/plugins/ira_contribs.py def add_ira_contribs(entries, options_map, config_str): \"\"\"Add legs for 401k employer match contributions. See module docstring for an example configuration. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config_str: A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. \"\"\" # Parse and extract configuration values. # FIXME: Use ast.literal_eval() here; you need to convert this code and the getters. # FIXME: Also, don't raise a RuntimeError, return an error object; review # this for all the plugins. # FIXME: This too is temporary. # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") # Currency of the inserted postings. currency = config_obj.pop('currency', 'UNKNOWN') # Flag to attach to the inserted postings. insert_flag = config_obj.pop('flag', None) # A dict of account names that trigger the insertion of postings to pairs of # inserted accounts when triggered. accounts = config_obj.pop('accounts', {}) # Convert the key in the accounts configuration for matching. account_transforms = {} for key, config in accounts.items(): if isinstance(key, str): flag = None account = key else: assert isinstance(key, tuple) flag, account = key account_transforms[account] = (flag, config) new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): orig_entry = entry for posting in entry.postings: if (posting.units is not MISSING and (posting.account in account_transforms) and (account_types.get_account_sign(posting.account) * posting.units.number > 0)): # Get the new account legs to insert. required_flag, (neg_account, pos_account) = account_transforms[posting.account] assert posting.cost is None # Check required flag if present. if (required_flag is None or (required_flag and required_flag == posting.flag)): # Insert income/expense entries for 401k. entry = add_postings( entry, amount.Amount(abs(posting.units.number), currency), neg_account.format(year=entry.date.year), pos_account.format(year=entry.date.year), insert_flag) if DEBUG and orig_entry is not entry: printer.print_entry(orig_entry) printer.print_entry(entry) new_entries.append(entry) return new_entries, []","title":"add_ira_contribs()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs.add_postings","text":"Insert positive and negative postings of a position in an entry. Parameters: entry \u2013 A Transaction instance. amount_ \u2013 An Amount instance to create the position, with positive number. neg_account \u2013 An account for the posting with the negative amount. pos_account \u2013 An account for the posting with the positive amount. flag \u2013 A string, that is to be set as flag for the new postings. Returns: A new, modified entry. Source code in beancount/plugins/ira_contribs.py def add_postings(entry, amount_, neg_account, pos_account, flag): \"\"\"Insert positive and negative postings of a position in an entry. Args: entry: A Transaction instance. amount_: An Amount instance to create the position, with positive number. neg_account: An account for the posting with the negative amount. pos_account: An account for the posting with the positive amount. flag: A string, that is to be set as flag for the new postings. Returns: A new, modified entry. \"\"\" return entry._replace(postings=entry.postings + [ data.Posting(neg_account, -amount_, None, None, flag, None), data.Posting(pos_account, amount_, None, None, flag, None), ])","title":"add_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly","text":"A plugin that issues errors when amounts are posted to non-leaf accounts, that is, accounts with child accounts. This is an extra constraint that you may want to apply optionally. If you install this plugin, it will issue errors for all accounts that have postings to non-leaf accounts. Some users may want to disallow this and enforce that only leaf accounts may have postings on them.","title":"leafonly"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError","text":"LeafOnlyError(source, message, entry)","title":"LeafOnlyError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/leafonly.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__new__","text":"Create new instance of LeafOnlyError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/leafonly.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.validate_leaf_only","text":"Check for non-leaf accounts that have postings on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/leafonly.py def validate_leaf_only(entries, unused_options_map): \"\"\"Check for non-leaf accounts that have postings on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" real_root = realization.realize(entries, compute_balance=False) default_meta = data.new_metadata('', 0) open_close_map = None # Lazily computed. errors = [] for real_account in realization.iter_children(real_root): if len(real_account) > 0 and real_account.txn_postings: if open_close_map is None: open_close_map = getters.get_account_open_close(entries) try: open_entry = open_close_map[real_account.account][0] except KeyError: open_entry = None errors.append(LeafOnlyError( open_entry.meta if open_entry else default_meta, \"Non-leaf account '{}' has postings on it\".format(real_account.account), open_entry)) return entries, errors","title":"validate_leaf_only()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.mark_unverified","text":"Add metadata to Postings which occur after their last Balance directives. Some people use Balance directives as a way to indicate that all postings before them are verified. They want to compute balances in each account as of the date of that last Balance directives. One way to do that is to use this plugin to mark the postings which occur after and to then filter them out using a WHERE clause on that metadata: SELECT account, sum(position) WHERE NOT meta(\"unverified\") Note that doing such a filtering may result in a list of balances which may not add to zero. Also, postings for accounts without a single Balance directive on them will not be marked as unverified as all (otherwise all the postings would be marked, this would make no sense).","title":"mark_unverified"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.mark_unverified.mark_unverified","text":"Add metadata to postings after the last Balance entry. See module doc. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/mark_unverified.py def mark_unverified(entries, options_map): \"\"\"Add metadata to postings after the last Balance entry. See module doc. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" # The last Balance directive seen for each account. last_balances = {} for entry in entries: if isinstance(entry, data.Balance): last_balances[entry.account] = entry new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): postings = entry.postings new_postings = postings for index, posting in enumerate(postings): balance = last_balances.get(posting.account, None) if balance and balance.date <= entry.date: if new_postings is postings: new_postings = postings.copy() new_meta = posting.meta.copy() new_meta['unverified'] = True new_postings[index] = posting._replace(meta=new_meta) if new_postings is not postings: entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, []","title":"mark_unverified()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.merge_meta","text":"Merge the metadata from a second file into the current set of entries. This is useful if you like to keep more sensitive private data, such as account numbers or passwords, in a second, possibly encrypted file. This can be used to generate a will, for instance, for your loved ones to be able to figure where all your assets are in case you pass away. You can store all the super secret stuff in a more closely guarded, hidden away separate file. The metadata from Open directives: Account name must match. Close directives: Account name must match. Commodity directives: Currency must match. are copied over. Metadata from the external file conflicting with that present in the main file overwrites it (external data wins). WARNING! If you include an encrypted file and the main file is not encrypted, the contents extraction from the encrypted file may appear in the cache.","title":"merge_meta"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.merge_meta.merge_meta","text":"Load a secondary file and merge its metadata in our given set of entries. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with more metadata attached to them. Source code in beancount/plugins/merge_meta.py def merge_meta(entries, options_map, config): \"\"\"Load a secondary file and merge its metadata in our given set of entries. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with more metadata attached to them. \"\"\" external_filename = config new_entries = list(entries) ext_entries, ext_errors, ext_options_map = loader.load_file(external_filename) # Map Open and Close directives. oc_map = getters.get_account_open_close(entries) ext_oc_map = getters.get_account_open_close(ext_entries) for account in set(oc_map.keys()) & set(ext_oc_map.keys()): open_entry, close_entry = oc_map[account] ext_open_entry, ext_close_entry = ext_oc_map[account] if open_entry and ext_open_entry: open_entry.meta.update(ext_open_entry.meta) if close_entry and ext_close_entry: close_entry.meta.update(ext_close_entry.meta) # Map Commodity directives. comm_map = getters.get_commodity_map(entries, False) ext_comm_map = getters.get_commodity_map(ext_entries, False) for currency in set(comm_map) & set(ext_comm_map): comm_entry = comm_map[currency] ext_comm_entry = ext_comm_map[currency] if comm_entry and ext_comm_entry: comm_entry.meta.update(ext_comm_entry.meta) # Note: We cannot include the external file in the list of inputs so that a # change of it triggers a cache rebuild because side-effects on options_map # aren't cascaded through. This is something that should be defined better # in the plugin interface and perhaps improved upon. return new_entries, ext_errors","title":"merge_meta()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.noduplicates","text":"This plugin validates that there are no duplicate transactions.","title":"noduplicates"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.noduplicates.validate_no_duplicates","text":"Check that the entries are unique, by computing hashes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/noduplicates.py def validate_no_duplicates(entries, unused_options_map): \"\"\"Check that the entries are unique, by computing hashes. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" unused_hashes, errors = compare.hash_entries(entries, exclude_meta=True) return entries, errors","title":"validate_no_duplicates()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused","text":"This plugin validates that there are no unused accounts.","title":"nounused"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError","text":"UnusedAccountError(source, message, entry)","title":"UnusedAccountError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/nounused.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__new__","text":"Create new instance of UnusedAccountError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/nounused.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.validate_unused_accounts","text":"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/nounused.py def validate_unused_accounts(entries, unused_options_map): \"\"\"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Find all the accounts referenced by entries which are not Open, and the # open directives for error reporting below. open_map = {} referenced_accounts = set() for entry in entries: if isinstance(entry, data.Open): open_map[entry.account] = entry continue referenced_accounts.update(getters.get_entry_accounts(entry)) # Create a list of suitable errors, with the location of the Open directives # corresponding to the unused accounts. errors = [UnusedAccountError(open_entry.meta, \"Unused account '{}'\".format(account), open_entry) for account, open_entry in open_map.items() if account not in referenced_accounts] return entries, errors","title":"validate_unused_accounts()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity","text":"A plugin that issues errors when more than one commodity is used in an account. For investments or trading accounts, it can make it easier to filter the action around a single stock by using the name of the stock as the leaf of the account name. Notes: The plugin will automatically skip accounts that have explicitly declared commodities in their Open directive. You can also set the metadata \"onecommodity: FALSE\" on an account's Open directive to skip the checks for that account. If provided, the configuration should be a regular expression restricting the set of accounts to check.","title":"onecommodity"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError","text":"OneCommodityError(source, message, entry)","title":"OneCommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/onecommodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__new__","text":"Create new instance of OneCommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/onecommodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.validate_one_commodity","text":"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. Source code in beancount/plugins/onecommodity.py def validate_one_commodity(entries, unused_options_map, config=None): \"\"\"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Args: entries: A list of directives. unused_options_map: An options map. config: The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. \"\"\" accounts_re = re.compile(config) if config else None # Mappings of account name to lists of currencies for each units and cost. units_map = collections.defaultdict(set) cost_map = collections.defaultdict(set) # Mappings to use just for getting a relevant source. units_source_map = {} cost_source_map = {} # Gather the set of accounts to skip from the Open directives. skip_accounts = set() for entry in entries: if not isinstance(entry, data.Open): continue if (not entry.meta.get(\"onecommodity\", True) or (accounts_re and not accounts_re.match(entry.account)) or (entry.currencies and len(entry.currencies) > 1)): skip_accounts.add(entry.account) # Accumulate all the commodities used. for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.account in skip_accounts: continue units = posting.units units_map[posting.account].add(units.currency) if len(units_map[posting.account]) > 1: units_source_map[posting.account] = entry cost = posting.cost if cost: cost_map[posting.account].add(cost.currency) if len(cost_map[posting.account]) > 1: units_source_map[posting.account] = entry elif isinstance(entry, data.Balance): if entry.account in skip_accounts: continue units_map[entry.account].add(entry.amount.currency) if len(units_map[entry.account]) > 1: units_source_map[entry.account] = entry elif isinstance(entry, data.Open): if entry.currencies and len(entry.currencies) > 1: skip_accounts.add(entry.account) # Check units. errors = [] for account, currencies in units_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( units_source_map[account].meta, \"More than one currency in account '{}': {}\".format( account, ','.join(currencies)), None)) # Check costs. for account, currencies in cost_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( cost_source_map[account].meta, \"More than one cost currency in account '{}': {}\".format( account, ','.join(currencies)), None)) return entries, errors","title":"validate_one_commodity()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.pedantic","text":"A plugin of plugins which triggers are all the pedantic plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests.","title":"pedantic"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains","text":"A plugin that cross-checks declared gains against prices on lot sales. When you sell stock, the gains can be automatically implied by the corresponding cash amounts. For example, in the following transaction the 2nd and 3rd postings should match the value of the stock sold: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL -10.125 USD The cost basis is checked against: 2141.36 + 008 + -10.125. That is, the balance checks computes -81 x 26.3125 = -2131.3125 + 2141.36 + 0.08 + -10.125 and checks that the residual is below a small tolerance. But... usually the income leg isn't given to you in statements. Beancount can automatically infer it using the balance, which is convenient, like this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL Additionally, most often you have the sales prices given to you on your transaction confirmation statement, so you can enter this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} @ 26.4375 USD Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL So in theory, if the price is given (26.4375 USD), we could verify that the proceeds from the sale at the given price match non-Income postings. That is, verify that -81 x 26.4375 = -2141.4375 + 2141.36 + 0.08 + is below a small tolerance value. So this plugin does this. In general terms, it does the following: For transactions with postings that have a cost and a price, it verifies that the sum of the positions on all postings to non-income accounts is below tolerance. This provides yet another level of verification and allows you to elide the income amounts, knowing that the price is there to provide an extra level of error-checking in case you enter a typo.","title":"sellgains"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError","text":"SellGainsError(source, message, entry)","title":"SellGainsError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/sellgains.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__new__","text":"Create new instance of SellGainsError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/sellgains.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.validate_sell_gains","text":"Check the sum of asset account totals for lots sold with a price on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/sellgains.py def validate_sell_gains(entries, options_map): \"\"\"Check the sum of asset account totals for lots sold with a price on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] acc_types = options.get_account_types(options_map) proceed_types = set([acc_types.assets, acc_types.liabilities, acc_types.equity, acc_types.expenses]) for entry in entries: if not isinstance(entry, data.Transaction): continue # Find transactions whose lots at cost all have a price. postings_at_cost = [posting for posting in entry.postings if posting.cost is not None] if not postings_at_cost or not all(posting.price is not None for posting in postings_at_cost): continue # Accumulate the total expected proceeds and the sum of the asset and # expenses legs. total_price = inventory.Inventory() total_proceeds = inventory.Inventory() for posting in entry.postings: # If the posting is held at cost, add the priced value to the balance. if posting.cost is not None: assert posting.price is not None price = posting.price total_price.add_amount(amount.mul(price, -posting.units.number)) else: # Otherwise, use the weight and ignore postings to Income accounts. atype = account_types.get_account_type(posting.account) if atype in proceed_types: total_proceeds.add_amount(convert.get_weight(posting)) # Compare inventories, currency by currency. dict_price = {pos.units.currency: pos.units.number for pos in total_price} dict_proceeds = {pos.units.currency: pos.units.number for pos in total_proceeds} tolerances = interpolate.infer_tolerances(entry.postings, options_map) invalid = False for currency, price_number in dict_price.items(): # Accept a looser than usual tolerance because rounding occurs # differently. Also, it would be difficult for the user to satisfy # two sets of constraints manually. tolerance = tolerances.get(currency) * EXTRA_TOLERANCE_MULTIPLIER proceeds_number = dict_proceeds.pop(currency, ZERO) diff = abs(price_number - proceeds_number) if diff > tolerance: invalid = True break if invalid or dict_proceeds: errors.append( SellGainsError( entry.meta, \"Invalid price vs. proceeds/gains: {} vs. {}\".format( total_price, total_proceeds), entry)) return entries, errors","title":"validate_sell_gains()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses","text":"Split expenses of a Beancount ledger between multiple people. This plugin is given a list of names. It assumes that any Expenses account whose components do not include any of the given names are to be split between the members. It goes through all the transactions and converts all such postings into multiple postings, one for each member. For example, given the names 'Martin' and 'Caroline', the following transaction: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation Will be converted to this: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation:Martin 134.50 USD Expenses:Accommodation:Caroline 134.50 USD After these transformations, all account names should include the name of a member. You can generate reports for a particular person by filtering postings to accounts with a component by their name.","title":"split_expenses"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.get_participants","text":"Get the list of participants from the plugin configuration in the input file. Parameters: options_map \u2013 The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Exceptions: KeyError \u2013 If the configuration does not contain configuration for the list Source code in beancount/plugins/split_expenses.py def get_participants(filename, options_map): \"\"\"Get the list of participants from the plugin configuration in the input file. Args: options_map: The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Raises: KeyError: If the configuration does not contain configuration for the list of participants. \"\"\" plugin_options = dict(options_map[\"plugin\"]) try: return plugin_options[\"beancount.plugins.split_expenses\"].split() except KeyError: raise KeyError(\"Could not find the split_expenses plugin configuration.\")","title":"get_participants()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.main","text":"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. Source code in beancount/plugins/split_expenses.py def main(): \"\"\"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') parser.add_argument('-c', '--currency', action='store', help=\"Convert all the amounts to a single common currency\") oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output-text', '--text', action='store', help=\"Render results to text boxes\") oparser.add_argument('--output-csv', '--csv', action='store', help=\"Render results to CSV files\") oparser.add_argument('--output-stdout', '--stdout', action='store_true', help=\"Render results to stdout\") args = parser.parse_args() # Ensure the directories exist. for directory in [args.output_text, args.output_csv]: if directory and not path.exists(directory): os.makedirs(directory, exist_ok=True) # Load the input file and get the list of participants. entries, errors, options_map = loader.load_file(args.filename) participants = get_participants(args.filename, options_map) for participant in participants: print(\"Participant: {}\".format(participant)) save_query(\"balances\", participant, entries, options_map, r\"\"\" SELECT PARENT(account) AS account, CONV[SUM(position)] AS amount WHERE account ~ ':\\b{}' GROUP BY 1 ORDER BY 2 DESC \"\"\", participant, boxed=False, args=args) save_query(\"expenses\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, PARENT(account) AS account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Expenses.*\\b{}' \"\"\", participant, args=args) save_query(\"income\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Income.*\\b{}' \"\"\", participant, args=args) save_query(\"final\", None, entries, options_map, r\"\"\" SELECT GREP('\\b({})\\b', account) AS participant, CONV[SUM(position)] AS balance GROUP BY 1 ORDER BY 2 \"\"\", '|'.join(participants), args=args) # FIXME: Make this output to CSV files and upload to a spreadsheet. # FIXME: Add a fixed with option. This requires changing adding this to the # the renderer to be able to have elastic space and line splitting..","title":"main()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.save_query","text":"Save the multiple files for this query. Parameters: title \u2013 A string, the title of this particular report to render. participant \u2013 A string, the name of the participant under consideration. entries \u2013 A list of directives (as per the loader). options_map \u2013 A dict of options (as per the loader). sql_query \u2013 A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args \u2013 A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed \u2013 A boolean, true if we should render the results in a fancy-looking ASCII box. spaced \u2013 If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args \u2013 A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. Source code in beancount/plugins/split_expenses.py def save_query(title, participant, entries, options_map, sql_query, *format_args, boxed=True, spaced=False, args=None): \"\"\"Save the multiple files for this query. Args: title: A string, the title of this particular report to render. participant: A string, the name of the participant under consideration. entries: A list of directives (as per the loader). options_map: A dict of options (as per the loader). sql_query: A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args: A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed: A boolean, true if we should render the results in a fancy-looking ASCII box. spaced: If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args: A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. \"\"\" # Replace CONV() to convert the currencies or not; if so, replace to # CONVERT(..., currency). replacement = (r'\\1' if args.currency is None else r'CONVERT(\\1, \"{}\")'.format(args.currency)) sql_query = re.sub(r'CONV\\[(.*?)\\]', replacement, sql_query) # Run the query. rtypes, rrows = query.run_query(entries, options_map, sql_query, *format_args, numberify=True) # The base of all filenames. filebase = title.replace(' ', '_') fmtopts = dict(boxed=boxed, spaced=spaced) # Output the text files. if args.output_text: basedir = (path.join(args.output_text, participant) if participant else args.output_text) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.txt') with open(filename, 'w') as file: query_render.render_text(rtypes, rrows, options_map['dcontext'], file, **fmtopts) # Output the CSV files. if args.output_csv: basedir = (path.join(args.output_csv, participant) if participant else args.output_csv) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.csv') with open(filename, 'w') as file: query_render.render_csv(rtypes, rrows, options_map['dcontext'], file, expand=False) if args.output_stdout: # Write out the query to stdout. query_render.render_text(rtypes, rrows, options_map['dcontext'], sys.stdout, **fmtopts)","title":"save_query()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.split_expenses","text":"Split postings according to expenses (see module docstring for details). Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. Source code in beancount/plugins/split_expenses.py def split_expenses(entries, options_map, config): \"\"\"Split postings according to expenses (see module docstring for details). Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. \"\"\" # Validate and sanitize configuration. if isinstance(config, str): members = config.split() elif isinstance(config, (tuple, list)): members = config else: raise RuntimeError(\"Invalid plugin configuration: configuration for split_expenses \" \"should be a string or a sequence.\") acctypes = options.get_account_types(options_map) def is_expense_account(account): return account_types.get_account_type(account) == acctypes.expenses # A predicate to quickly identify if an account contains the name of a # member. is_individual_account = re.compile('|'.join(map(re.escape, members))).search # Existing and previously unseen accounts. new_accounts = set() # Filter the entries and transform transactions. new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): new_postings = [] for posting in entry.postings: if (is_expense_account(posting.account) and not is_individual_account(posting.account)): # Split this posting into multiple postings. split_units = amount.Amount(posting.units.number / len(members), posting.units.currency) for member in members: # Mark the account as new if never seen before. subaccount = account.join(posting.account, member) new_accounts.add(subaccount) # Ensure the modified postings are marked as # automatically calculated, so that the resulting # calculated amounts aren't used to affect inferred # tolerances. meta = posting.meta.copy() if posting.meta else {} meta[interpolate.AUTOMATIC_META] = True # Add a new posting for each member, to a new account # with the name of this member. new_postings.append( posting._replace(meta=meta, account=subaccount, units=split_units, cost=posting.cost)) else: new_postings.append(posting) # Modify the entry in-place, replace its postings. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Create Open directives for new subaccounts if necessary. oc_map = getters.get_account_open_close(entries) open_date = entries[0].date meta = data.new_metadata('', 0) open_entries = [] for new_account in new_accounts: if new_account not in oc_map: entry = data.Open(meta, open_date, new_account, None, None) open_entries.append(entry) return open_entries + new_entries, []","title":"split_expenses()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending","text":"An example of tracking unpaid payables or receivables. A user with lots of invoices to track may want to produce a report of pending or incomplete payables or receivables. Beancount does not by default offer such a dedicated feature, but it is easy to build one by using existing link attributes on transactions. This is an example on how to implement that with a plugin. For example, assuming the user enters linked transactions like this: 2013-03-28 * \"Bill for datacenter electricity\" ^invoice-27a30ab61191 Expenses:Electricity 450.82 USD Liabilities:AccountsPayable 2013-04-15 * \"Paying electricity company\" ^invoice-27a30ab61191 Assets:Checking -450.82 USD Liabilities:AccountsPayable Transactions are grouped by link (\"invoice-27a30ab61191\") and then the intersection of their common accounts is automatically calculated (\"Liabilities:AccountsPayable\"). We then add up the balance of all the postings for this account in this link group and check if the sum is zero. If there is a residual amount in this balance, we mark the associated entries as incomplete by inserting a #PENDING tag on them. The user can then use that tag to navigate to the corresponding view in the web interface, or just find the entries and produce a listing of them.","title":"tag_pending"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending.tag_pending_plugin","text":"A plugin that finds and tags pending transactions. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/tag_pending.py def tag_pending_plugin(entries, options_map): \"\"\"A plugin that finds and tags pending transactions. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" return (tag_pending_transactions(entries, 'PENDING'), [])","title":"tag_pending_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending.tag_pending_transactions","text":"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Parameters: entries \u2013 A list of directives/transactions to process. tag_name \u2013 A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. Source code in beancount/plugins/tag_pending.py def tag_pending_transactions(entries, tag_name='PENDING'): \"\"\"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Args: entries: A list of directives/transactions to process. tag_name: A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. \"\"\" link_groups = basicops.group_entries_by_link(entries) pending_entry_ids = set() for link, link_entries in link_groups.items(): assert link_entries if len(link_entries) == 1: # If a single entry is present, it is assumed incomplete. pending_entry_ids.add(id(link_entries[0])) else: # Compute the sum total balance of the common accounts. common_accounts = basicops.get_common_accounts(link_entries) common_balance = inventory.Inventory() for entry in link_entries: for posting in entry.postings: if posting.account in common_accounts: common_balance.add_position(posting) # Mark entries as pending if a residual balance is found. if not common_balance.is_empty(): for entry in link_entries: pending_entry_ids.add(id(entry)) # Insert tags if marked. return [(entry._replace(tags=(entry.tags or set()) | set((tag_name,))) if id(entry) in pending_entry_ids else entry) for entry in entries]","title":"tag_pending_transactions()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices","text":"This module adds validation that there is a single price defined per date and base/quote currencies. If multiple conflicting price values are declared, an error is generated. Note that multiple price entries with the same number do not generate an error. This is meant to be turned on if you want to use a very strict mode for entering prices, and may not be realistic usage. For example, if you have (1) a transaction with an implicitly generated price during the day (from its cost) and (2) a separate explicit price directive that declares a different price for the day's closing price, this would generate an error. I'm not certain this will be useful in the long run, so placing it in a plugin.","title":"unique_prices"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError","text":"UniquePricesError(source, message, entry)","title":"UniquePricesError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unique_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__new__","text":"Create new instance of UniquePricesError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/unique_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.validate_unique_prices","text":"Check that there is only a single price per day for a particular base/quote. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. Source code in beancount/plugins/unique_prices.py def validate_unique_prices(entries, unused_options_map): \"\"\"Check that there is only a single price per day for a particular base/quote. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. \"\"\" new_entries = [] errors = [] prices = collections.defaultdict(list) for entry in entries: if not isinstance(entry, data.Price): continue key = (entry.date, entry.currency, entry.amount.currency) prices[key].append(entry) errors = [] for price_entries in prices.values(): if len(price_entries) > 1: number_map = {price_entry.amount.number: price_entry for price_entry in price_entries} if len(number_map) > 1: # Note: This should be a list of entries for better error # reporting. (Later.) error_entry = next(iter(number_map.values())) errors.append( UniquePricesError(error_entry.meta, \"Disagreeing price entries\", price_entries)) return new_entries, errors","title":"validate_unique_prices()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized","text":"Compute unrealized gains. The configuration for this plugin is a single string, the name of the subaccount to add to post the unrealized gains to, like this: plugin \"beancount.plugins.unrealized\" \"Unrealized\" If you don't specify a name for the subaccount (the configuration value is optional), by default it inserts the unrealized gains in the same account that is being adjusted.","title":"unrealized"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError","text":"UnrealizedError(source, message, entry)","title":"UnrealizedError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unrealized.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__new__","text":"Create new instance of UnrealizedError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/unrealized.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.add_unrealized_gains","text":"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. subaccount \u2013 A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/unrealized.py def add_unrealized_gains(entries, options_map, subaccount=None): \"\"\"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. subaccount: A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" errors = [] meta = data.new_metadata('', 0) account_types = options.get_account_types(options_map) # Assert the subaccount name is in valid format. if subaccount: validation_account = account.join(account_types.assets, subaccount) if not account.is_valid(validation_account): errors.append( UnrealizedError(meta, \"Invalid subaccount name: '{}'\".format(subaccount), None)) return entries, errors if not entries: return (entries, errors) # Group positions by (account, cost, cost_currency). price_map = prices.build_price_map(entries) holdings_list = holdings.get_final_holdings(entries, price_map=price_map) # Group positions by (account, cost, cost_currency). holdings_list = holdings.aggregate_holdings_by( holdings_list, lambda h: (h.account, h.currency, h.cost_currency)) # Get the latest prices from the entries. price_map = prices.build_price_map(entries) # Create transactions to account for each position. new_entries = [] latest_date = entries[-1].date for index, holding in enumerate(holdings_list): if (holding.currency == holding.cost_currency or holding.cost_currency is None): continue # Note: since we're only considering positions held at cost, the # transaction that created the position *must* have created at least one # price point for that commodity, so we never expect for a price not to # be available, which is reasonable. if holding.price_number is None: # An entry without a price might indicate that this is a holding # resulting from leaked cost basis. {0ed05c502e63, b/16} if holding.number: errors.append( UnrealizedError(meta, \"A valid price for {h.currency}/{h.cost_currency} \" \"could not be found\".format(h=holding), None)) continue # Compute the PnL; if there is no profit or loss, we create a # corresponding entry anyway. pnl = holding.market_value - holding.book_value if holding.number == ZERO: # If the number of units sum to zero, the holdings should have been # zero. errors.append( UnrealizedError( meta, \"Number of units of {} in {} in holdings sum to zero \" \"for account {} and should not\".format( holding.currency, holding.cost_currency, holding.account), None)) continue # Compute the name of the accounts and add the requested subaccount name # if requested. asset_account = holding.account income_account = account.join(account_types.income, account.sans_root(holding.account)) if subaccount: asset_account = account.join(asset_account, subaccount) income_account = account.join(income_account, subaccount) # Create a new transaction to account for this difference in gain. gain_loss_str = \"gain\" if pnl > ZERO else \"loss\" narration = (\"Unrealized {} for {h.number} units of {h.currency} \" \"(price: {h.price_number:.4f} {h.cost_currency} as of {h.price_date}, \" \"average cost: {h.cost_number:.4f} {h.cost_currency})\").format( gain_loss_str, h=holding) entry = data.Transaction(data.new_metadata(meta[\"filename\"], lineno=1000 + index), latest_date, flags.FLAG_UNREALIZED, None, narration, EMPTY_SET, EMPTY_SET, []) # Book this as income, converting the account name to be the same, but as income. # Note: this is a rather convenient but arbitrary choice--maybe it would be best to # let the user decide to what account to book it, but I don't a nice way to let the # user specify this. # # Note: we never set a price because we don't want these to end up in Conversions. entry.postings.extend([ data.Posting( asset_account, amount.Amount(pnl, holding.cost_currency), None, None, None, None), data.Posting( income_account, amount.Amount(-pnl, holding.cost_currency), None, None, None, None) ]) new_entries.append(entry) # Ensure that the accounts we're going to use to book the postings exist, by # creating open entries for those that we generated that weren't already # existing accounts. new_accounts = {posting.account for entry in new_entries for posting in entry.postings} open_entries = getters.get_account_open_close(entries) new_open_entries = [] for account_ in sorted(new_accounts): if account_ not in open_entries: meta = data.new_metadata(meta[\"filename\"], index) open_entry = data.Open(meta, latest_date, account_, None, None) new_open_entries.append(open_entry) return (entries + new_open_entries + new_entries, errors)","title":"add_unrealized_gains()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.get_unrealized_entries","text":"Return entries automatically created for unrealized gains. Parameters: entries \u2013 A list of directives. Returns: A list of directives, all of which are in the original list. Source code in beancount/plugins/unrealized.py def get_unrealized_entries(entries): \"\"\"Return entries automatically created for unrealized gains. Args: entries: A list of directives. Returns: A list of directives, all of which are in the original list. \"\"\" return [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.flag == flags.FLAG_UNREALIZED)]","title":"get_unrealized_entries()"},{"location":"api_reference/beancount.scripts.html","text":"beancount.scripts \uf0c1 Implementation of the various scripts available from bin. This is structured this way because we want all the significant codes under a single directory, for analysis, grepping and unit testing. beancount.scripts.bake \uf0c1 Bake a Beancount input file's web files to a directory hierarchy. You provide a Beancount filename, an output directory, and this script runs a server and a scraper that puts all the files in the directory, and if your output name has an archive suffix, we automatically the fetched directory contents to the archive and delete them. beancount.scripts.bake.archive(command_template, directory, archive, quiet=False) \uf0c1 Archive the directory to the given tar/gz archive filename. Parameters: command_template \u2013 A string, the command template to format with in order to compute the command to run. directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. quiet \u2013 A boolean, True to suppress output. Exceptions: IOError \u2013 if the directory does not exist or if the archive name already Source code in beancount/scripts/bake.py def archive(command_template, directory, archive, quiet=False): \"\"\"Archive the directory to the given tar/gz archive filename. Args: command_template: A string, the command template to format with in order to compute the command to run. directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. quiet: A boolean, True to suppress output. Raises: IOError: if the directory does not exist or if the archive name already exists. \"\"\" directory = path.abspath(directory) archive = path.abspath(archive) if not path.exists(directory): raise IOError(\"Directory to archive '{}' does not exist\".format( directory)) if path.exists(archive): raise IOError(\"Output archive name '{}' already exists\".format( archive)) command = command_template.format(directory=directory, dirname=path.dirname(directory), basename=path.basename(directory), archive=archive) pipe = subprocess.Popen(shlex.split(command), shell=False, cwd=path.dirname(directory), stdout=subprocess.PIPE if quiet else None, stderr=subprocess.PIPE if quiet else None) _, _ = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Archive failure\") beancount.scripts.bake.archive_zip(directory, archive) \uf0c1 Archive the directory to the given tar/gz archive filename. Parameters: directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. Source code in beancount/scripts/bake.py def archive_zip(directory, archive): \"\"\"Archive the directory to the given tar/gz archive filename. Args: directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. \"\"\" # Figure out optimal level of compression among the supported ones in this # installation. for spec, compression in [ ('lzma', zipfile.ZIP_LZMA), ('bz2', zipfile.ZIP_BZIP2), ('zlib', zipfile.ZIP_DEFLATED)]: if importlib.util.find_spec(spec): zip_compression = compression break else: # Default is no compression. zip_compression = zipfile.ZIP_STORED with file_utils.chdir(directory), zipfile.ZipFile( archive, 'w', compression=zip_compression) as archfile: for root, dirs, files in os.walk(directory): for filename in files: relpath = path.relpath(path.join(root, filename), directory) archfile.write(relpath) beancount.scripts.bake.bake_to_directory(webargs, output_dir, render_all_pages=True) \uf0c1 Serve and bake a Beancount's web to a directory. Parameters: webargs \u2013 An argparse parsed options object with the web app arguments. output_dir \u2013 A directory name. We don't check here whether it exists or not. quiet \u2013 A boolean, True to suppress web server fetch log. render_all_pages \u2013 If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. Source code in beancount/scripts/bake.py def bake_to_directory(webargs, output_dir, render_all_pages=True): \"\"\"Serve and bake a Beancount's web to a directory. Args: webargs: An argparse parsed options object with the web app arguments. output_dir: A directory name. We don't check here whether it exists or not. quiet: A boolean, True to suppress web server fetch log. render_all_pages: If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. \"\"\" callback = functools.partial(save_scraped_document, output_dir) if render_all_pages: ignore_regexps = None else: regexps = [ # Skip the context pages, too slow. r'/context/', # Skip the link pages, too slow. r'/link/', # Skip the component pages... too many. r'/view/component/', # Skip served documents. r'/.*/doc/', # Skip monthly pages. r'/view/year/\\d\\d\\d\\d/month/', ] ignore_regexps = '({})'.format('|'.join(regexps)) processed_urls, skipped_urls = web.scrape_webapp(webargs, callback, ignore_regexps) beancount.scripts.bake.normalize_filename(url) \uf0c1 Convert URL paths to filenames. Add .html extension if needed. Parameters: url \u2013 A string, the url to convert. Returns: A string, possibly with an extension appended. Source code in beancount/scripts/bake.py def normalize_filename(url): \"\"\"Convert URL paths to filenames. Add .html extension if needed. Args: url: A string, the url to convert. Returns: A string, possibly with an extension appended. \"\"\" if url.endswith('/'): return path.join(url, 'index.html') elif BINARY_MATCH(url): return url else: return url if url.endswith('.html') else (url + '.html') beancount.scripts.bake.relativize_links(html, current_url) \uf0c1 Make all the links in the contents string relative to an URL. Parameters: html \u2013 An lxml document node. current_url \u2013 A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. Source code in beancount/scripts/bake.py def relativize_links(html, current_url): \"\"\"Make all the links in the contents string relative to an URL. Args: html: An lxml document node. current_url: A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. \"\"\" current_dir = path.dirname(current_url) for element, attribute, link, pos in lxml.html.iterlinks(html): if path.isabs(link): relative_link = path.relpath(normalize_filename(link), current_dir) element.set(attribute, relative_link) beancount.scripts.bake.remove_links(html, targets) \uf0c1 Convert a list of anchors () from an HTML tree to spans (). Parameters: html \u2013 An lxml document node. targets \u2013 A set of string, targets to be removed. Source code in beancount/scripts/bake.py def remove_links(html, targets): \"\"\"Convert a list of anchors () from an HTML tree to spans (). Args: html: An lxml document node. targets: A set of string, targets to be removed. \"\"\" for element, attribute, link, pos in lxml.html.iterlinks(html): if link in targets: del element.attrib[attribute] element.tag = 'span' element.set('class', 'removed-link') beancount.scripts.bake.save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls) \uf0c1 Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Parameters: output_dir \u2013 A string, the output directory to write. url \u2013 A string, the originally requested URL. response \u2013 An http response as per urlopen. contents \u2013 Bytes, the content of a response. html_root \u2013 An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls \u2013 A set of the links from the file that were skipped. Source code in beancount/scripts/bake.py def save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls): \"\"\"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Args: output_dir: A string, the output directory to write. url: A string, the originally requested URL. response: An http response as per urlopen. contents: Bytes, the content of a response. html_root: An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls: A set of the links from the file that were skipped. \"\"\" if response.status != 200: logging.error(\"Invalid status: %s\", response.status) # Ignore directories. if url.endswith('/'): return # Note that we're saving the file under the non-redirected URL, because this # will have to be opened using files and there are no redirects that way. if response.info().get_content_type() == 'text/html': if html_root is None: html_root = lxml.html.document_fromstring(contents) remove_links(html_root, skipped_urls) relativize_links(html_root, url) contents = lxml.html.tostring(html_root, method=\"html\") # Compute output filename and write out the relativized contents. output_filename = path.join(output_dir, normalize_filename(url).lstrip('/')) os.makedirs(path.dirname(output_filename), exist_ok=True) with open(output_filename, 'wb') as outfile: outfile.write(contents) beancount.scripts.check \uf0c1 Parse, check and realize a beancount input file. This also measures the time it takes to run all these steps. beancount.scripts.deps \uf0c1 Check the installation dependencies and report the version numbers of each. This is meant to be used as an error diagnostic tool. beancount.scripts.deps.check_cdecimal() \uf0c1 Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_cdecimal(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" # Note: this code mirrors and should be kept in-sync with that at the top of # beancount.core.number. # Try the built-in installation. import decimal if is_fast_decimal(decimal): return ('cdecimal', '{} (built-in)'.format(decimal.__version__), True) # Try an explicitly installed version. try: import cdecimal if is_fast_decimal(cdecimal): return ('cdecimal', getattr(cdecimal, '__version__', 'OKAY'), True) except ImportError: pass # Not found. return ('cdecimal', None, False) beancount.scripts.deps.check_dependencies() \uf0c1 Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. Source code in beancount/scripts/deps.py def check_dependencies(): \"\"\"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. \"\"\" return [ # Check for a complete installation of Python itself. check_python(), check_cdecimal(), # Modules we really do need installed. check_import('dateutil'), check_import('bottle'), check_import('ply', module_name='ply.yacc', min_version='3.4'), check_import('lxml', module_name='lxml.etree', min_version='3'), # Optionally required to upload data to Google Drive. check_import('googleapiclient'), check_import('oauth2client'), check_import('httplib2'), # Optionally required to support various price source fetchers. check_import('requests', min_version='2.0'), # Optionally required to support imports (identify, extract, file) code. check_python_magic(), check_import('beautifulsoup4', module_name='bs4', min_version='4'), ] beancount.scripts.deps.check_import(package_name, min_version=None, module_name=None) \uf0c1 Check that a particular module name is installed. Parameters: package_name \u2013 A string, the name of the package and module to be imported to verify this works. This should have a version attribute on it. min_version \u2013 If not None, a string, the minimum version number we require. module_name \u2013 The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_import(package_name, min_version=None, module_name=None): \"\"\"Check that a particular module name is installed. Args: package_name: A string, the name of the package and module to be imported to verify this works. This should have a __version__ attribute on it. min_version: If not None, a string, the minimum version number we require. module_name: The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" if module_name is None: module_name = package_name try: __import__(module_name) module = sys.modules[module_name] if min_version is not None: version = module.__version__ assert isinstance(version, str) is_sufficient = (parse_version(version) >= parse_version(min_version) if min_version else True) else: version, is_sufficient = None, True except ImportError: version, is_sufficient = None, False return (package_name, version, is_sufficient) beancount.scripts.deps.check_python() \uf0c1 Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" return ('python3', '.'.join(map(str, sys.version_info[:3])), sys.version_info[:2] >= (3, 3)) beancount.scripts.deps.check_python_magic() \uf0c1 Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python_magic(): \"\"\"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" try: import magic # Check that python-magic and not filemagic is installed. if not hasattr(magic, 'from_file'): # 'filemagic' is installed; install python-magic. raise ImportError return ('python-magic', 'OK', True) except ImportError: return ('python-magic', None, False) beancount.scripts.deps.is_fast_decimal(decimal_module) \uf0c1 Return true if a fast C decimal implementation is installed. Source code in beancount/scripts/deps.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType) beancount.scripts.deps.list_dependencies(file=<_io.TextIOWrapper name='' mode='w' encoding='utf-8'>) \uf0c1 Check the dependencies and produce a listing on the given file. Parameters: file \u2013 A file object to write the output to. Source code in beancount/scripts/deps.py def list_dependencies(file=sys.stderr): \"\"\"Check the dependencies and produce a listing on the given file. Args: file: A file object to write the output to. \"\"\" print(\"Dependencies:\") for package, version, sufficient in check_dependencies(): print(\" {:16}: {} {}\".format( package, version or 'NOT INSTALLED', \"(INSUFFICIENT)\" if version and not sufficient else \"\"), file=file) beancount.scripts.deps.parse_version(version_str) \uf0c1 Parse the version string into a comparable tuple. Source code in beancount/scripts/deps.py def parse_version(version_str: str) -> str: \"\"\"Parse the version string into a comparable tuple.\"\"\" return [int(v) for v in version_str.split('.')] beancount.scripts.directories \uf0c1 Check that document directories mirror a list of accounts correctly. beancount.scripts.directories.ValidateDirectoryError ( Exception ) \uf0c1 A directory validation error. beancount.scripts.directories.validate_directories(entries, document_dirs) \uf0c1 Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: entries \u2013 A list of directives. document_dirs \u2013 A list of string, the directory roots to walk and validate. Source code in beancount/scripts/directories.py def validate_directories(entries, document_dirs): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: entries: A list of directives. document_dirs: A list of string, the directory roots to walk and validate. \"\"\" # Get the list of accounts declared in the ledge. accounts = getters.get_accounts(entries) # For each of the roots, validate the hierarchy of directories. for document_dir in document_dirs: errors = validate_directory(accounts, document_dir) for error in errors: print(\"ERROR: {}\".format(error)) beancount.scripts.directories.validate_directory(accounts, document_dir) \uf0c1 Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Parameters: account \u2013 A set or dict of account names. document_dir \u2013 A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. Source code in beancount/scripts/directories.py def validate_directory(accounts, document_dir): \"\"\"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Args: account: A set or dict of account names. document_dir: A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. \"\"\" # Generate all parent accounts in the account_set we're checking against, so # that parent directories with no corresponding account don't warn. accounts_with_parents = set(accounts) for account_ in accounts: while True: parent = account.parent(account_) if not parent: break if parent in accounts_with_parents: break accounts_with_parents.add(parent) account_ = parent errors = [] for directory, account_name, _, _ in account.walk(document_dir): if account_name not in accounts_with_parents: errors.append(ValidateDirectoryError( \"Invalid directory '{}': no corresponding account '{}'\".format( directory, account_name))) return errors beancount.scripts.doctor \uf0c1 Debugging tool for those finding bugs in Beancount. This tool is able to dump lexer/parser state, and will provide other services in the name of debugging. beancount.scripts.doctor.RenderError ( tuple ) \uf0c1 RenderError(source, message, entry) beancount.scripts.doctor.RenderError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/doctor.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.doctor.RenderError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of RenderError(source, message, entry) beancount.scripts.doctor.RenderError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/doctor.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.doctor.do_checkdeps(*unused_args) \uf0c1 Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.') beancount.scripts.doctor.do_context(filename, args) \uf0c1 Describe the context that a particular transaction is applied to. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). Source code in beancount/scripts/doctor.py def do_context(filename, args): \"\"\"Describe the context that a particular transaction is applied to. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). \"\"\" from beancount.reports import context from beancount import loader # Check we have the required number of arguments. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") # Load the input files. entries, errors, options_map = loader.load_file(filename) # Parse the arguments, get the line number. match = re.match(r\"(.+):(\\d+)$\", args[0]) if match: search_filename = path.abspath(match.group(1)) lineno = int(match.group(2)) elif re.match(r\"(\\d+)$\", args[0]): # Note: Make sure to use the absolute filename used by the parser to # resolve the file. search_filename = options_map['filename'] lineno = int(args[0]) else: raise SystemExit(\"Invalid format for location.\") str_context = context.render_file_context(entries, options_map, search_filename, lineno) sys.stdout.write(str_context) beancount.scripts.doctor.do_deps(*unused_args) \uf0c1 Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.') beancount.scripts.doctor.do_directories(filename, args) \uf0c1 Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: filename \u2013 A string, the Beancount input filename. args \u2013 The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. Source code in beancount/scripts/doctor.py def do_directories(filename, args): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: filename: A string, the Beancount input filename. args: The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. \"\"\" from beancount import loader from beancount.scripts import directories entries, _, __ = loader.load_file(filename) directories.validate_directories(entries, args) beancount.scripts.doctor.do_display_context(filename, args) \uf0c1 Print out the precision inferred from the parsed numbers in the input file. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_display_context(filename, args): \"\"\"Print out the precision inferred from the parsed numbers in the input file. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount import loader entries, errors, options_map = loader.load_file(filename) dcontext = options_map['dcontext'] sys.stdout.write(str(dcontext)) beancount.scripts.doctor.do_dump_lexer(filename, unused_args) \uf0c1 Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text))) beancount.scripts.doctor.do_lex(filename, unused_args) \uf0c1 Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text))) beancount.scripts.doctor.do_linked(filename, args) \uf0c1 Print out a list of transactions linked to the one at the given line. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_linked(filename, args): \"\"\"Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. if closest_entry is None: raise SystemExit(\"No entry could be found before {}:{}\".format(filename, lineno)) links = (closest_entry.links if isinstance(closest_entry, data.Transaction) else data.EMPTY_SET) if not links: linked_entries = [closest_entry] else: # Find all linked entries. # # Note that there is an option here: You can either just look at the links # on the closest entry, or you can include the links of the linked # transactions as well. Whichever one you want depends on how you use your # links. Best would be to query the user (in Emacs) when there are many # links present. follow_links = True if not follow_links: linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] else: links = set(links) linked_entries = [] while True: num_linked = len(linked_entries) linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] if len(linked_entries) == num_linked: break for entry in linked_entries: if entry.links: links.update(entry.links) # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) dformat = options_map['dcontext'].build(alignment=display_context.Align.DOT, reserved=2) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income)) beancount.scripts.doctor.do_list_options(*unused_args) \uf0c1 Print out a list of the available options. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_list_options(*unused_args): \"\"\"Print out a list of the available options. Args: unused_args: Ignored. \"\"\" from beancount.parser import options print(options.list_options()) beancount.scripts.doctor.do_missing_open(filename, args) \uf0c1 Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_missing_open(filename, args): \"\"\"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import printer from beancount.core import data from beancount.core import getters from beancount import loader entries, errors, options_map = loader.load_file(filename) # Get accounts usage and open directives. first_use_map, _ = getters.get_accounts_use_map(entries) open_close_map = getters.get_account_open_close(entries) new_entries = [] for account, first_use_date in first_use_map.items(): if account not in open_close_map: new_entries.append( data.Open(data.new_metadata(filename, 0), first_use_date, account, None, None)) dcontext = options_map['dcontext'] printer.print_entries(data.sorted(new_entries), dcontext) beancount.scripts.doctor.do_parse(filename, unused_args) \uf0c1 Run the parser in debug mode. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_parse(filename, unused_args): \"\"\"Run the parser in debug mode. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import parser entries, errors, _ = parser.parse_file(filename, yydebug=1) beancount.scripts.doctor.do_print_options(filename, *args) \uf0c1 Print out the actual options parsed from a file. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_print_options(filename, *args): \"\"\"Print out the actual options parsed from a file. Args: unused_args: Ignored. \"\"\" from beancount import loader _, __, options_map = loader.load_file(filename) for key, value in sorted(options_map.items()): print('{}: {}'.format(key, value)) beancount.scripts.doctor.do_roundtrip(filename, unused_args) \uf0c1 Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_roundtrip(filename, unused_args): \"\"\"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import printer from beancount.core import compare from beancount import loader round1_filename = round2_filename = None try: logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') logging.info(\"Read the entries\") entries, errors, options_map = loader.load_file(filename) printer.print_errors(errors, file=sys.stderr) logging.info(\"Print them out to a file\") basename, extension = path.splitext(filename) round1_filename = ''.join([basename, '.roundtrip1', extension]) with open(round1_filename, 'w') as outfile: printer.print_entries(entries, file=outfile) logging.info(\"Read the entries from that file\") # Note that we don't want to run any of the auto-generation here, but # parsing now returns incomplete objects and we assume idempotence on a # file that was output from the printer after having been processed, so # it shouldn't add anything new. That is, a processed file printed and # resolve when parsed again should contain the same entries, i.e. # nothing new should be generated. entries_roundtrip, errors, options_map = loader.load_file(round1_filename) # Print out the list of errors from parsing the results. if errors: print(',----------------------------------------------------------------------') printer.print_errors(errors, file=sys.stdout) print('`----------------------------------------------------------------------') logging.info(\"Print what you read to yet another file\") round2_filename = ''.join([basename, '.roundtrip2', extension]) with open(round2_filename, 'w') as outfile: printer.print_entries(entries_roundtrip, file=outfile) logging.info(\"Compare the original entries with the re-read ones\") same, missing1, missing2 = compare.compare_entries(entries, entries_roundtrip) if same: logging.info('Entries are the same. Congratulations.') else: logging.error('Entries differ!') print() print('\\n\\nMissing from original:') for entry in entries: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() print('\\n\\nMissing from round-trip:') for entry in missing2: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() finally: for rfilename in (round1_filename, round2_filename): if path.exists(rfilename): os.remove(rfilename) beancount.scripts.doctor.do_validate_html(directory, args) \uf0c1 Validate all the HTML files under a directory hierarchy. Parameters: directory \u2013 A string, the root directory whose contents to validate. args \u2013 A tuple of the rest of arguments. Source code in beancount/scripts/doctor.py def do_validate_html(directory, args): \"\"\"Validate all the HTML files under a directory hierarchy. Args: directory: A string, the root directory whose contents to validate. args: A tuple of the rest of arguments. \"\"\" from beancount.web import scrape files, missing, empty = scrape.validate_local_links_in_dir(directory) logging.info('%d files processed', len(files)) for target in missing: logging.error('Missing %s', target) for target in empty: logging.error('Empty %s', target) beancount.scripts.doctor.get_commands() \uf0c1 Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). Source code in beancount/scripts/doctor.py def get_commands(): \"\"\"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). \"\"\" commands = [] for attr_name, attr_value in globals().items(): match = re.match('do_(.*)', attr_name) if match: commands.append((match.group(1), misc_utils.first_paragraph(attr_value.__doc__))) return commands beancount.scripts.example \uf0c1 Generate a decently-sized example history, based on some rules. This script is used to generate some meaningful input to Beancount, input that looks as realistic as possible for a moderately complex mock individual. This can also be used as an input generator for a stress test for performance evaluation. beancount.scripts.example.check_non_negative(entries, account, currency) \uf0c1 Check that the balance of the given account never goes negative. Parameters: entries \u2013 A list of directives. account \u2013 An account string, the account to check the balance for. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 if the balance goes negative. Source code in beancount/scripts/example.py def check_non_negative(entries, account, currency): \"\"\"Check that the balance of the given account never goes negative. Args: entries: A list of directives. account: An account string, the account to check the balance for. currency: A string, the currency to check minimums for. Raises: AssertionError: if the balance goes negative. \"\"\" previous_date = None for txn_posting, balances in postings_for(data.sorted(entries), [account], before=True): balance = balances[account] date = txn_posting.txn.date if date != previous_date: assert all(pos.units.number >= ZERO for pos in balance.get_positions()), ( \"Negative balance: {} at: {}\".format(balance, txn_posting.txn.date)) previous_date = date beancount.scripts.example.compute_trip_dates(date_begin, date_end) \uf0c1 Generate dates at reasonable intervals for trips during the given time period. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of dates for the trips within the period. Source code in beancount/scripts/example.py def compute_trip_dates(date_begin, date_end): \"\"\"Generate dates at reasonable intervals for trips during the given time period. Args: date_begin: The start date. date_end: The end date. Yields: Pairs of dates for the trips within the period. \"\"\" # Min and max number of days remaining at home. days_at_home = (4*30, 13*30) # Length of trip. days_trip = (8, 22) # Number of days to ensure no trip at the beginning and the end. days_buffer = 21 date_begin += datetime.timedelta(days=days_buffer) date_end -= datetime.timedelta(days=days_buffer) date = date_begin while 1: duration_at_home = datetime.timedelta(days=random.randint(*days_at_home)) duration_trip = datetime.timedelta(days=random.randint(*days_trip)) date_trip_begin = date + duration_at_home date_trip_end = date_trip_begin + duration_trip if date_trip_end >= date_end: break yield (date_trip_begin, date_trip_end) date = date_trip_end beancount.scripts.example.contextualize_file(contents, employer) \uf0c1 Replace generic strings in the generated file with realistic strings. Parameters: contents \u2013 A string, the generic file contents. Returns: A string, the contextualized version. Source code in beancount/scripts/example.py def contextualize_file(contents, employer): \"\"\"Replace generic strings in the generated file with realistic strings. Args: contents: A string, the generic file contents. Returns: A string, the contextualized version. \"\"\" replacements = { 'CC': 'US', 'Bank1': 'BofA', 'Bank1_Institution': 'Bank of America', 'Bank1_Address': '123 America Street, LargeTown, USA', 'Bank1_Phone': '+1.012.345.6789', 'CreditCard1': 'Chase:Slate', 'CreditCard2': 'Amex:BlueCash', 'Employer1': employer, 'Retirement': 'Vanguard', 'Retirement_Institution': 'Vanguard Group', 'Retirement_Address': \"P.O. Box 1110, Valley Forge, PA 19482-1110\", 'Retirement_Phone': \"+1.800.523.1188\", 'Investment': 'ETrade', # Commodities 'CCY': 'USD', 'VACHR': 'VACHR', 'DEFCCY': 'IRAUSD', 'MFUND1': 'VBMPX', 'MFUND2': 'RGAGX', 'STK1': 'ITOT', 'STK2': 'VEA', 'STK3': 'VHT', 'STK4': 'GLD', } new_contents = replace(contents, replacements) return new_contents, replacements beancount.scripts.example.date_iter(date_begin, date_end) \uf0c1 Generate a sequence of dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_iter(date_begin, date_end): \"\"\"Generate a sequence of dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date. \"\"\" assert date_begin <= date_end date = date_begin one_day = datetime.timedelta(days=1) while date < date_end: date += one_day yield date beancount.scripts.example.date_random_seq(date_begin, date_end, days_min, days_max) \uf0c1 Generate a sequence of dates with some random increase in days. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. days_min \u2013 The minimum number of days to advance on each iteration. days_max \u2013 The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_random_seq(date_begin, date_end, days_min, days_max): \"\"\"Generate a sequence of dates with some random increase in days. Args: date_begin: The start date. date_end: The end date. days_min: The minimum number of days to advance on each iteration. days_max: The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. \"\"\" assert days_min > 0 assert days_min <= days_max date = date_begin while date < date_end: nb_days_forward = random.randint(days_min, days_max) date += datetime.timedelta(days=nb_days_forward) if date >= date_end: break yield date beancount.scripts.example.delay_dates(date_iter, delay_days_min, delay_days_max) \uf0c1 Delay the dates from the given iterator by some uniformly drawn number of days. Parameters: date_iter \u2013 An iterator of datetime.date instances. delay_days_min \u2013 The minimum amount of advance days for the transaction. delay_days_max \u2013 The maximum amount of advance days for the transaction. Yields: datetime.date instances. Source code in beancount/scripts/example.py def delay_dates(date_iter, delay_days_min, delay_days_max): \"\"\"Delay the dates from the given iterator by some uniformly drawn number of days. Args: date_iter: An iterator of datetime.date instances. delay_days_min: The minimum amount of advance days for the transaction. delay_days_max: The maximum amount of advance days for the transaction. Yields: datetime.date instances. \"\"\" dates = list(date_iter) last_date = dates[-1] last_date = last_date.date() if isinstance(last_date, datetime.datetime) else last_date for dtime in dates: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime date += datetime.timedelta(days=random.randint(delay_days_min, delay_days_max)) if date >= last_date: break yield date beancount.scripts.example.generate_balance_checks(entries, account, date_iter) \uf0c1 Generate balance check entries to the given frequency. Parameters: entries \u2013 A list of directives that contain all the transactions for the accounts. account \u2013 The name of the account for which to generate. date_iter \u2013 Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. Source code in beancount/scripts/example.py def generate_balance_checks(entries, account, date_iter): \"\"\"Generate balance check entries to the given frequency. Args: entries: A list of directives that contain all the transactions for the accounts. account: The name of the account for which to generate. date_iter: Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. \"\"\" balance_checks = [] date_iter = iter(date_iter) next_date = next(date_iter) with misc_utils.swallow(StopIteration): for txn_posting, balance in postings_for(entries, [account], before=True): while txn_posting.txn.date >= next_date: amount = balance[account].get_currency_units('CCY').number balance_checks.extend(parse(\"\"\" {next_date} balance {account} {amount} CCY \"\"\", **locals())) next_date = next(date_iter) return balance_checks beancount.scripts.example.generate_banking(entries, date_begin, date_end, amount_initial) \uf0c1 Generate a checking account opening. Parameters: entries \u2013 A list of entries which affect this account. date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. amount_initial \u2013 A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking(entries, date_begin, date_end, amount_initial): \"\"\"Generate a checking account opening. Args: entries: A list of entries which affect this account. date_begin: A date instance, the beginning date. date_end: A date instance, the end date. amount_initial: A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. \"\"\" amount_initial_neg = -amount_initial new_entries = parse(\"\"\" {date_begin} open Assets:CC:Bank1 institution: \"Bank1_Institution\" address: \"Bank1_Address\" phone: \"Bank1_Phone\" {date_begin} open Assets:CC:Bank1:Checking CCY account: \"00234-48574897\" ;; {date_begin} open Assets:CC:Bank1:Savings CCY {date_begin} * \"Opening Balance for checking account\" Assets:CC:Bank1:Checking {amount_initial} CCY Equity:Opening-Balances {amount_initial_neg} CCY \"\"\", **locals()) date_balance = date_begin + datetime.timedelta(days=1) account = 'Assets:CC:Bank1:Checking' for txn_posting, balances in postings_for(data.sorted(entries + new_entries), [account], before=True): if txn_posting.txn.date >= date_balance: break amount_balance = balances[account].get_currency_units('CCY').number bal_entries = parse(\"\"\" {date_balance} balance Assets:CC:Bank1:Checking {amount_balance} CCY \"\"\", **locals()) return new_entries + bal_entries beancount.scripts.example.generate_banking_expenses(date_begin, date_end, account, rent_amount) \uf0c1 Generate expenses paid out of a checking account, typically living expenses. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. account \u2013 The checking account to generate expenses to. rent_amount \u2013 The amount of rent. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking_expenses(date_begin, date_end, account, rent_amount): \"\"\"Generate expenses paid out of a checking account, typically living expenses. Args: date_begin: The start date. date_end: The end date. account: The checking account to generate expenses to. rent_amount: The amount of rent. Returns: A list of directives. \"\"\" fee_expenses = generate_periodic_expenses( rrule.rrule(rrule.MONTHLY, bymonthday=4, dtstart=date_begin, until=date_end), \"BANK FEES\", \"Monthly bank fee\", account, 'Expenses:Financial:Fees', lambda: D('4.00')) rent_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 2, 5), \"RiverBank Properties\", \"Paying the rent\", account, 'Expenses:Home:Rent', lambda: random.normalvariate(float(rent_amount), 0)) electricity_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 7, 8), \"EDISON POWER\", \"\", account, 'Expenses:Home:Electricity', lambda: random.normalvariate(65, 0)) internet_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 20, 22), \"Wine-Tarner Cable\", \"\", account, 'Expenses:Home:Internet', lambda: random.normalvariate(80, 0.10)) phone_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 17, 19), \"Verizon Wireless\", \"\", account, 'Expenses:Home:Phone', lambda: random.normalvariate(60, 10)) return data.sorted(fee_expenses + rent_expenses + electricity_expenses + internet_expenses + phone_expenses) beancount.scripts.example.generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from) \uf0c1 Generate entries to clear the value of an account. Parameters: date_iter \u2013 An iterator of datetime.date instances. payee \u2013 A string, the payee name to use on the transactions. narration \u2013 A string, the narration to use on the transactions. entries \u2013 A list of entries. account_clear \u2013 The account to clear. account_from \u2013 The source account to clear 'account_clear' from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from): \"\"\"Generate entries to clear the value of an account. Args: date_iter: An iterator of datetime.date instances. payee: A string, the payee name to use on the transactions. narration: A string, the narration to use on the transactions. entries: A list of entries. account_clear: The account to clear. account_from: The source account to clear 'account_clear' from. Returns: A list of directives. \"\"\" # The next date we're looking for. next_date = next(iter(date_iter)) # Iterate over all the postings of the account to clear. new_entries = [] for txn_posting, balances in postings_for(entries, [account_clear]): balance_clear = balances[account_clear] # Check if we need to clear. if next_date <= txn_posting.txn.date: pos_amount = balance_clear.get_currency_units('CCY') neg_amount = -pos_amount new_entries.extend(parse(\"\"\" {next_date} * \"{payee}\" \"{narration}\" {account_clear} {neg_amount.number:.2f} CCY {account_from} {pos_amount.number:.2f} CCY \"\"\", **locals())) balance_clear.add_amount(neg_amount) # Advance to the next date we're looking for. try: next_date = next(iter(date_iter)) except StopIteration: break return new_entries beancount.scripts.example.generate_commodity_entries(date_birth) \uf0c1 Create a list of Commodity entries for all the currencies we're using. Parameters: date_birth \u2013 A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. Source code in beancount/scripts/example.py def generate_commodity_entries(date_birth): \"\"\"Create a list of Commodity entries for all the currencies we're using. Args: date_birth: A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. \"\"\" return parse(\"\"\" 1792-01-01 commodity USD name: \"US Dollar\" export: \"CASH\" {date_birth} commodity VACHR name: \"Employer Vacation Hours\" export: \"IGNORE\" {date_birth} commodity IRAUSD name: \"US 401k and IRA Contributions\" export: \"IGNORE\" 2009-05-01 commodity RGAGX name: \"American Funds The Growth Fund of America Class R-6\" export: \"MUTF:RGAGX\" price: \"USD:google/MUTF:RGAGX\" 1995-09-18 commodity VBMPX name: \"Vanguard Total Bond Market Index Fund Institutional Plus Shares\" export: \"MUTF:VBMPX\" price: \"USD:google/MUTF:VBMPX\" 2004-01-20 commodity ITOT name: \"iShares Core S&P Total U.S. Stock Market ETF\" export: \"NYSEARCA:ITOT\" price: \"USD:google/NYSEARCA:ITOT\" 2007-07-20 commodity VEA name: \"Vanguard FTSE Developed Markets ETF\" export: \"NYSEARCA:VEA\" price: \"USD:google/NYSEARCA:VEA\" 2004-01-26 commodity VHT name: \"Vanguard Health Care ETF\" export: \"NYSEARCA:VHT\" price: \"USD:google/NYSEARCA:VHT\" 2004-11-01 commodity GLD name: \"SPDR Gold Trust (ETF)\" export: \"NYSEARCA:GLD\" price: \"USD:google/NYSEARCA:GLD\" 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" \"\"\", **locals()) beancount.scripts.example.generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end) \uf0c1 Generate bi-weekly entries for payroll salary income. Parameters: employer_name \u2013 A string, the human-readable name of the employer. employer_address \u2013 A string, the address of the employer. annual_salary \u2013 A Decimal, the annual salary of the employee. account_deposit \u2013 An account string, the account to deposit the salary to. account_retirement \u2013 An account string, the account to deposit retirement contributions to. date_begin \u2013 The start date. date_end \u2013 The end date. Returns: A list of directives, including open directives for the account. Source code in beancount/scripts/example.py def generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end): \"\"\"Generate bi-weekly entries for payroll salary income. Args: employer_name: A string, the human-readable name of the employer. employer_address: A string, the address of the employer. annual_salary: A Decimal, the annual salary of the employee. account_deposit: An account string, the account to deposit the salary to. account_retirement: An account string, the account to deposit retirement contributions to. date_begin: The start date. date_end: The end date. Returns: A list of directives, including open directives for the account. \"\"\" preamble = parse(\"\"\" {date_begin} event \"employer\" \"{employer_name}, {employer_address}\" {date_begin} open Income:CC:Employer1:Salary CCY ;{date_begin} open Income:CC:Employer1:AnnualBonus CCY {date_begin} open Income:CC:Employer1:GroupTermLife CCY {date_begin} open Income:CC:Employer1:Vacation VACHR {date_begin} open Assets:CC:Employer1:Vacation VACHR {date_begin} open Expenses:Vacation VACHR {date_begin} open Expenses:Health:Life:GroupTermLife {date_begin} open Expenses:Health:Medical:Insurance {date_begin} open Expenses:Health:Dental:Insurance {date_begin} open Expenses:Health:Vision:Insurance ;{date_begin} open Expenses:Vacation:Employer \"\"\", **locals()) date_prev = None contrib_retirement = ZERO contrib_socsec = ZERO biweekly_pay = annual_salary / 26 gross = biweekly_pay medicare = gross * D('0.0231') federal = gross * D('0.2303') state = gross * D('0.0791') city = gross * D('0.0379') sdi = D('1.12') lifeinsurance = D('24.32') dental = D('2.90') medical = D('27.38') vision = D('42.30') fixed = (medicare + federal + state + city + sdi + dental + medical + vision) # Calculate vacation hours per-pay. with decimal.localcontext() as ctx: ctx.prec = 4 vacation_hrs = (ANNUAL_VACATION_DAYS * D('8')) / D('26') transactions = [] for dtime in misc_utils.skipiter( rrule.rrule(rrule.WEEKLY, byweekday=rrule.TH, dtstart=date_begin, until=date_end), 2): date = dtime.date() year = date.year if not date_prev or date_prev.year != date.year: contrib_retirement = RETIREMENT_LIMITS.get(date.year, RETIREMENT_LIMITS[None]) contrib_socsec = D('7000') date_prev = date retirement_uncapped = math.ceil((gross * D('0.25')) / 100) * 100 retirement = min(contrib_retirement, retirement_uncapped) contrib_retirement -= retirement socsec_uncapped = gross * D('0.0610') socsec = min(contrib_socsec, socsec_uncapped) contrib_socsec -= socsec with decimal.localcontext() as ctx: ctx.prec = 6 deposit = (gross - retirement - fixed - socsec) retirement_neg = -retirement gross_neg = -gross lifeinsurance_neg = -lifeinsurance vacation_hrs_neg = -vacation_hrs template = \"\"\" {date} * \"{employer_name}\" \"Payroll\" {account_deposit} {deposit:.2f} CCY {account_retirement} {retirement:.2f} CCY Assets:CC:Federal:PreTax401k {retirement_neg:.2f} DEFCCY Expenses:Taxes:Y{year}:CC:Federal:PreTax401k {retirement:.2f} DEFCCY Income:CC:Employer1:Salary {gross_neg:.2f} CCY Income:CC:Employer1:GroupTermLife {lifeinsurance_neg:.2f} CCY Expenses:Health:Life:GroupTermLife {lifeinsurance:.2f} CCY Expenses:Health:Dental:Insurance {dental} CCY Expenses:Health:Medical:Insurance {medical} CCY Expenses:Health:Vision:Insurance {vision} CCY Expenses:Taxes:Y{year}:CC:Medicare {medicare:.2f} CCY Expenses:Taxes:Y{year}:CC:Federal {federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {state:.2f} CCY Expenses:Taxes:Y{year}:CC:CityNYC {city:.2f} CCY Expenses:Taxes:Y{year}:CC:SDI {sdi:.2f} CCY Expenses:Taxes:Y{year}:CC:SocSec {socsec:.2f} CCY Assets:CC:Employer1:Vacation {vacation_hrs:.2f} VACHR Income:CC:Employer1:Vacation {vacation_hrs_neg:.2f} VACHR \"\"\" if retirement == ZERO: # Remove retirement lines. template = '\\n'.join(line for line in template.splitlines() if not re.search(r'\\bretirement\\b', line)) transactions.extend(parse(template, **locals())) return preamble + transactions beancount.scripts.example.generate_expense_accounts(date_birth) \uf0c1 Generate directives for expense accounts. Parameters: date_birth \u2013 Birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_expense_accounts(date_birth): \"\"\"Generate directives for expense accounts. Args: date_birth: Birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" {date_birth} open Expenses:Food:Groceries {date_birth} open Expenses:Food:Restaurant {date_birth} open Expenses:Food:Coffee {date_birth} open Expenses:Food:Alcohol {date_birth} open Expenses:Transport:Tram {date_birth} open Expenses:Home:Rent {date_birth} open Expenses:Home:Electricity {date_birth} open Expenses:Home:Internet {date_birth} open Expenses:Home:Phone {date_birth} open Expenses:Financial:Fees {date_birth} open Expenses:Financial:Commissions \"\"\", **locals()) beancount.scripts.example.generate_open_entries(date, accounts, currency=None) \uf0c1 Generate a list of Open entries for the given accounts: Parameters: date \u2013 A datetime.date instance for the open entries. accounts \u2013 A list of account strings. currency \u2013 An optional currency constraint. Returns: A list of Open directives. Source code in beancount/scripts/example.py def generate_open_entries(date, accounts, currency=None): \"\"\"Generate a list of Open entries for the given accounts: Args: date: A datetime.date instance for the open entries. accounts: A list of account strings. currency: An optional currency constraint. Returns: A list of Open directives. \"\"\" assert isinstance(accounts, (list, tuple)) return parse(''.join( '{date} open {account} {currency}\\n'.format(date=date, account=account, currency=currency or '') for account in accounts)) beancount.scripts.example.generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment) \uf0c1 Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Parameters: entries \u2013 A list of existing entries that affect this account so far. The generated entries will also affect this account. account \u2013 An account string, the account to monitor. account_out \u2013 An account string, the savings account to make transfers to. transfer_minimum \u2013 The minimum amount of funds to always leave in this account after a transfer. transfer_threshold \u2013 The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment \u2013 A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. Source code in beancount/scripts/example.py def generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment): \"\"\"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Args: entries: A list of existing entries that affect this account so far. The generated entries will also affect this account. account: An account string, the account to monitor. account_out: An account string, the savings account to make transfers to. transfer_minimum: The minimum amount of funds to always leave in this account after a transfer. transfer_threshold: The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment: A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. \"\"\" last_date = entries[-1].date # Reverse the balance amounts taking into account the minimum balance for # all time in the future. amounts = [(balances[account].get_currency_units('CCY').number, txn_posting) for txn_posting, balances in postings_for(entries, [account])] reversed_amounts = [] last_amount, _ = amounts[-1] for current_amount, _ in reversed(amounts): if current_amount < last_amount: reversed_amounts.append(current_amount) last_amount = current_amount else: reversed_amounts.append(last_amount) capped_amounts = reversed(reversed_amounts) # Create transfers outward where the future allows it. new_entries = [] offset_amount = ZERO for current_amount, (_, txn_posting) in zip(capped_amounts, amounts): if txn_posting.txn.date >= last_date: break adjusted_amount = current_amount - offset_amount if adjusted_amount > (transfer_minimum + transfer_threshold): amount_transfer = round_to(adjusted_amount - transfer_minimum, transfer_increment) date = txn_posting.txn.date + datetime.timedelta(days=1) amount_transfer_neg = -amount_transfer new_entries.extend(parse(\"\"\" {date} * \"Transfering accumulated savings to other account\" {account} {amount_transfer_neg:2f} CCY {account_out} {amount_transfer:2f} CCY \"\"\", **locals())) offset_amount += amount_transfer return new_entries beancount.scripts.example.generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator) \uf0c1 Generate periodic expense transactions. Parameters: date_iter \u2013 An iterator for dates or datetimes. payee \u2013 A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration \u2013 A string, the narration to use on the transactions. account_from \u2013 An account string the debited account. account_to \u2013 An account string the credited account. amount_generator \u2013 A callable object to generate variates. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator): \"\"\"Generate periodic expense transactions. Args: date_iter: An iterator for dates or datetimes. payee: A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration: A string, the narration to use on the transactions. account_from: An account string the debited account. account_to: An account string the credited account. amount_generator: A callable object to generate variates. Returns: A list of directives. \"\"\" new_entries = [] for dtime in date_iter: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime amount = D(amount_generator()) txn_payee = (payee if isinstance(payee, str) else random.choice(payee)) txn_narration = (narration if isinstance(narration, str) else random.choice(narration)) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{txn_payee}\" \"{txn_narration}\" {account_from} {amount_neg:.2f} CCY {account_to} {amount:.2f} CCY \"\"\", **locals())) return new_entries beancount.scripts.example.generate_prices(date_begin, date_end, currencies, cost_currency) \uf0c1 Generate weekly or monthly price entries for the given currencies. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. currencies \u2013 A list of currency strings to generate prices for. cost_currency \u2013 A string, the cost currency. Returns: A list of Price directives. Source code in beancount/scripts/example.py def generate_prices(date_begin, date_end, currencies, cost_currency): \"\"\"Generate weekly or monthly price entries for the given currencies. Args: date_begin: The start date. date_end: The end date. currencies: A list of currency strings to generate prices for. cost_currency: A string, the cost currency. Returns: A list of Price directives. \"\"\" digits = D('0.01') entries = [] counter = itertools.count() for currency in currencies: start_price = random.uniform(30, 200) growth = random.uniform(0.02, 0.13) # %/year mu = growth * (7 / 365) sigma = random.uniform(0.005, 0.02) # Vol for dtime, price_float in zip(rrule.rrule(rrule.WEEKLY, byweekday=rrule.FR, dtstart=date_begin, until=date_end), price_series(start_price, mu, sigma)): price = D(price_float).quantize(digits) meta = data.new_metadata(generate_prices.__name__, next(counter)) entry = data.Price(meta, dtime.date(), currency, amount.Amount(price, cost_currency)) entries.append(entry) return entries beancount.scripts.example.generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking) \uf0c1 Generate expenses paid out of a credit card account, including payments to the credit card. Parameters: date_birth \u2013 The user's birth date. date_begin \u2013 The start date. date_end \u2013 The end date. account_credit \u2013 The credit card account to generate expenses against. account_checking \u2013 The checking account to generate payments from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking): \"\"\"Generate expenses paid out of a credit card account, including payments to the credit card. Args: date_birth: The user's birth date. date_begin: The start date. date_end: The end date. account_credit: The credit card account to generate expenses against. account_checking: The checking account to generate payments from. Returns: A list of directives. \"\"\" restaurant_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 1, 5), RESTAURANT_NAMES, RESTAURANT_NARRATIONS, account_credit, 'Expenses:Food:Restaurant', lambda: min(random.lognormvariate(math.log(30), math.log(1.5)), random.randint(200, 220))) groceries_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 5, 20), GROCERIES_NAMES, \"Buying groceries\", account_credit, 'Expenses:Food:Groceries', lambda: min(random.lognormvariate(math.log(80), math.log(1.3)), random.randint(250, 300))) subway_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 27, 33), \"Metro Transport Authority\", \"Tram tickets\", account_credit, 'Expenses:Transport:Tram', lambda: D('120.00')) credit_expenses = data.sorted(restaurant_expenses + groceries_expenses + subway_expenses) # Entries to open accounts. credit_preamble = generate_open_entries(date_birth, [account_credit], 'CCY') return data.sorted(credit_preamble + credit_expenses) beancount.scripts.example.generate_retirement_employer_match(entries, account_invest, account_income) \uf0c1 Generate employer matching contributions into a retirement account. Parameters: entries \u2013 A list of directives that cover the retirement account. account_invest \u2013 The name of the retirement cash account. account_income \u2013 The name of the income account. Returns: A list of new entries generated for employer contributions. Source code in beancount/scripts/example.py def generate_retirement_employer_match(entries, account_invest, account_income): \"\"\"Generate employer matching contributions into a retirement account. Args: entries: A list of directives that cover the retirement account. account_invest: The name of the retirement cash account. account_income: The name of the income account. Returns: A list of new entries generated for employer contributions. \"\"\" match_frac = D('0.50') new_entries = parse(\"\"\" {date} open {account_income} CCY \"\"\", date=entries[0].date, account_income=account_income) for txn_posting, balances in postings_for(entries, [account_invest]): amount = txn_posting.posting.units.number * match_frac amount_neg = -amount date = txn_posting.txn.date + ONE_DAY new_entries.extend(parse(\"\"\" {date} * \"Employer match for contribution\" {account_invest} {amount:.2f} CCY {account_income} {amount_neg:.2f} CCY \"\"\", **locals())) return new_entries beancount.scripts.example.generate_retirement_investments(entries, account, commodities_items, price_map) \uf0c1 Invest money deposited to the given retirement account. Parameters: entries \u2013 A list of directives account \u2013 The root account for all retirement investment sub-accounts. commodities_items \u2013 A list of (commodity, fraction to be invested in) items. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. Source code in beancount/scripts/example.py def generate_retirement_investments(entries, account, commodities_items, price_map): \"\"\"Invest money deposited to the given retirement account. Args: entries: A list of directives account: The root account for all retirement investment sub-accounts. commodities_items: A list of (commodity, fraction to be invested in) items. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. \"\"\" open_entries = [] account_cash = join(account, 'Cash') date_origin = entries[0].date open_entries.extend(parse(\"\"\" {date_origin} open {account} CCY institution: \"Retirement_Institution\" address: \"Retirement_Address\" phone: \"Retirement_Phone\" {date_origin} open {account_cash} CCY number: \"882882\" \"\"\", **locals())) for currency, _ in commodities_items: open_entries.extend(parse(\"\"\" {date_origin} open {account}:{currency} {currency} number: \"882882\" \"\"\", **locals())) new_entries = [] for txn_posting, balances in postings_for(entries, [account_cash]): balance = balances[account_cash] amount_to_invest = balance.get_currency_units('CCY').number # Find the date the following Monday, the date to invest. txn_date = txn_posting.txn.date while txn_date.weekday() != calendar.MONDAY: txn_date += ONE_DAY amount_invested = ZERO for commodity, fraction in commodities_items: amount_fraction = amount_to_invest * D(fraction) # Find the price at that date. _, price = prices.get_price(price_map, (commodity, 'CCY'), txn_date) units = (amount_fraction / price).quantize(D('0.001')) amount_cash = (units * price).quantize(D('0.01')) amount_cash_neg = -amount_cash new_entries.extend(parse(\"\"\" {txn_date} * \"Investing {fraction:.0%} of cash in {commodity}\" {account}:{commodity} {units:.3f} {commodity} {{{price:.2f} CCY}} {account}:Cash {amount_cash_neg:.2f} CCY \"\"\", **locals())) balance.add_amount(amount.Amount(-amount_cash, 'CCY')) return data.sorted(open_entries + new_entries) beancount.scripts.example.generate_tax_accounts(year, date_max) \uf0c1 Generate accounts and contribution directives for a particular tax year. Parameters: year \u2013 An integer, the year we're to generate this for. date_max \u2013 The maximum date to produce an entry for. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_accounts(year, date_max): \"\"\"Generate accounts and contribution directives for a particular tax year. Args: year: An integer, the year we're to generate this for. date_max: The maximum date to produce an entry for. Returns: A list of directives. \"\"\" date_year = datetime.date(year, 1, 1) date_filing = (datetime.date(year + 1, 3, 20) + datetime.timedelta(days=random.randint(0, 5))) date_federal = (date_filing + datetime.timedelta(days=random.randint(0, 4))) date_state = (date_filing + datetime.timedelta(days=random.randint(0, 4))) quantum = D('0.01') amount_federal = D(max(random.normalvariate(500, 120), 12)).quantize(quantum) amount_federal_neg = -amount_federal amount_state = D(max(random.normalvariate(300, 100), 10)).quantize(quantum) amount_state_neg = -amount_state amount_payable = -(amount_federal + amount_state) amount_limit = RETIREMENT_LIMITS.get(year, RETIREMENT_LIMITS[None]) amount_limit_neg = -amount_limit entries = parse(\"\"\" ;; Open tax accounts for that year. {date_year} open Expenses:Taxes:Y{year}:CC:Federal:PreTax401k DEFCCY {date_year} open Expenses:Taxes:Y{year}:CC:Medicare CCY {date_year} open Expenses:Taxes:Y{year}:CC:Federal CCY {date_year} open Expenses:Taxes:Y{year}:CC:CityNYC CCY {date_year} open Expenses:Taxes:Y{year}:CC:SDI CCY {date_year} open Expenses:Taxes:Y{year}:CC:State CCY {date_year} open Expenses:Taxes:Y{year}:CC:SocSec CCY ;; Check that the tax amounts have been fully used. {date_year} balance Assets:CC:Federal:PreTax401k 0 DEFCCY {date_year} * \"Allowed contributions for one year\" Income:CC:Federal:PreTax401k {amount_limit_neg} DEFCCY Assets:CC:Federal:PreTax401k {amount_limit} DEFCCY {date_filing} * \"Filing taxes for {year}\" Expenses:Taxes:Y{year}:CC:Federal {amount_federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {amount_state:.2f} CCY Liabilities:AccountsPayable {amount_payable:.2f} CCY {date_federal} * \"FEDERAL TAXPYMT\" Assets:CC:Bank1:Checking {amount_federal_neg:.2f} CCY Liabilities:AccountsPayable {amount_federal:.2f} CCY {date_state} * \"STATE TAX & FINANC PYMT\" Assets:CC:Bank1:Checking {amount_state_neg:.2f} CCY Liabilities:AccountsPayable {amount_state:.2f} CCY \"\"\", **locals()) return [entry for entry in entries if entry.date < date_max] beancount.scripts.example.generate_tax_preamble(date_birth) \uf0c1 Generate tax declarations not specific to any particular year. Parameters: date_birth \u2013 A date instance, the birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_preamble(date_birth): \"\"\"Generate tax declarations not specific to any particular year. Args: date_birth: A date instance, the birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" ;; Tax accounts not specific to a year. {date_birth} open Income:CC:Federal:PreTax401k DEFCCY {date_birth} open Assets:CC:Federal:PreTax401k DEFCCY \"\"\", **locals()) beancount.scripts.example.generate_taxable_investment(date_begin, date_end, entries, price_map, stocks) \uf0c1 Generate opening directives and transactions for an investment account. Parameters: date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. entries \u2013 A list of entries that contains at least the transfers to the investment account's cash account. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). stocks \u2013 A list of strings, the list of commodities to invest in. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_taxable_investment(date_begin, date_end, entries, price_map, stocks): \"\"\"Generate opening directives and transactions for an investment account. Args: date_begin: A date instance, the beginning date. date_end: A date instance, the end date. entries: A list of entries that contains at least the transfers to the investment account's cash account. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). stocks: A list of strings, the list of commodities to invest in. Returns: A list of directives. \"\"\" account = 'Assets:CC:Investment' account_cash = join(account, 'Cash') account_gains = 'Income:CC:Investment:Gains' account_dividends = 'Income:CC:Investment:Dividends' accounts_stocks = ['Assets:CC:Investment:{}'.format(commodity) for commodity in stocks] open_entries = parse(\"\"\" {date_begin} open {account}:Cash CCY {date_begin} open {account_gains} CCY {date_begin} open {account_dividends} CCY \"\"\", **locals()) for stock in stocks: open_entries.extend(parse(\"\"\" {date_begin} open {account}:{stock} {stock} \"\"\", **locals())) # Figure out dates at which dividends should be distributed, near the end of # each quarter. days_to = datetime.timedelta(days=3*90-10) dividend_dates = [] for quarter_begin in iter_quarters(date_begin, date_end): end_of_quarter = quarter_begin + days_to if not (date_begin < end_of_quarter < date_end): continue dividend_dates.append(end_of_quarter) # Iterate over all the dates, but merging in the postings for the cash # account. min_amount = D('1000.00') round_amount = D('100.00') commission = D('8.95') round_units = D('1') frac_invest = D('1.00') frac_dividend = D('0.004') p_daily_buy = 1./15 # days p_daily_sell = 1./90 # days stocks_inventory = inventory.Inventory() new_entries = [] dividend_date_iter = iter(dividend_dates) next_dividend_date = next(dividend_date_iter) for date, balances in iter_dates_with_balance(date_begin, date_end, entries, [account_cash]): # Check if we should insert a dividend. Note that we could not factor # this out because we want to explicitly reinvest the cash dividends and # we also want the dividends to be proportional to the amount of # invested stock, so one feeds on the other and vice-versa. if next_dividend_date and date > next_dividend_date: # Compute the total balances for the stock accounts in order to # create a realistic dividend. total = inventory.Inventory() for account_stock in accounts_stocks: total.add_inventory(balances[account_stock]) # Create an entry offering dividends of 1% of the portfolio. portfolio_cost = total.reduce(convert.get_cost).get_currency_units('CCY').number amount_cash = (frac_dividend * portfolio_cost).quantize(D('0.01')) amount_cash_neg = -amount_cash dividend = parse(\"\"\" {next_dividend_date} * \"Dividends on portfolio\" {account}:Cash {amount_cash:.2f} CCY {account_dividends} {amount_cash_neg:.2f} CCY \"\"\", **locals())[0] new_entries.append(dividend) # Advance the next dividend date. try: next_dividend_date = next(dividend_date_iter) except StopIteration: next_dividend_date = None # If the balance is high, buy with high probability. balance = balances[account_cash] total_cash = balance.get_currency_units('CCY').number assert total_cash >= ZERO, ('Cash balance is negative: {}'.format(total_cash)) invest_cash = total_cash * frac_invest - commission if invest_cash > min_amount: if random.random() < p_daily_buy: commodities = random.sample(stocks, random.randint(1, len(stocks))) lot_amount = round_to(invest_cash / len(commodities), round_amount) invested_amount = ZERO for stock in commodities: # Find the price at that date. _, price = prices.get_price(price_map, (stock, 'CCY'), date) units = round_to((lot_amount / price), round_units) if units <= ZERO: continue amount_cash = -(units * price + commission) # logging.info('Buying %s %s @ %s CCY = %s CCY', # units, stock, price, units * price) buy = parse(\"\"\" {date} * \"Buy shares of {stock}\" {account}:Cash {amount_cash:.2f} CCY {account}:{stock} {units:.0f} {stock} {{{price:.2f} CCY}} Expenses:Financial:Commissions {commission:.2f} CCY \"\"\", **locals())[0] new_entries.append(buy) account_stock = ':'.join([account, stock]) balances[account_cash].add_position(buy.postings[0]) balances[account_stock].add_position(buy.postings[1]) stocks_inventory.add_position(buy.postings[1]) # Don't sell on days you buy. continue # Otherwise, sell with low probability. if not stocks_inventory.is_empty() and random.random() < p_daily_sell: # Choose the lot with the highest gain or highest loss. gains = [] for position in stocks_inventory.get_positions(): base_quote = (position.units.currency, position.cost.currency) _, price = prices.get_price(price_map, base_quote, date) if price == position.cost.number: continue # Skip lots without movement. market_value = position.units.number * price book_value = convert.get_cost(position).number gain = market_value - book_value gains.append((gain, market_value, price, position)) if not gains: continue # Sell either biggest winner or biggest loser. biggest = bool(random.random() < 0.5) lot_tuple = sorted(gains)[0 if biggest else -1] gain, market_value, price, sell_position = lot_tuple #logging.info('Selling {} for {}'.format(sell_position, market_value)) sell_position = -sell_position stock = sell_position.units.currency amount_cash = market_value - commission amount_gain = -gain sell = parse(\"\"\" {date} * \"Sell shares of {stock}\" {account}:{stock} {sell_position} @ {price:.2f} CCY {account}:Cash {amount_cash:.2f} CCY Expenses:Financial:Commissions {commission:.2f} CCY {account_gains} {amount_gain:.2f} CCY \"\"\", **locals())[0] new_entries.append(sell) balances[account_cash].add_position(sell.postings[1]) stocks_inventory.add_position(sell.postings[0]) continue return open_entries + new_entries beancount.scripts.example.generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit) \uf0c1 Generate more dense expenses for a trip. Parameters: date_begin \u2013 A datetime.date instance, the beginning of the trip. date_end \u2013 A datetime.date instance, the end of the trip. tag \u2013 A string, the name of the tag. config \u2013 A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city \u2013 A string, the capitalized name of the destination city. home_city \u2013 A string, the name of the home city. account_credit \u2013 A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. Source code in beancount/scripts/example.py def generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit): \"\"\"Generate more dense expenses for a trip. Args: date_begin: A datetime.date instance, the beginning of the trip. date_end: A datetime.date instance, the end of the trip. tag: A string, the name of the tag. config: A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city: A string, the capitalized name of the destination city. home_city: A string, the name of the home city. account_credit: A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. \"\"\" p_day_generate = 0.3 new_entries = [] for date in date_iter(date_begin, date_end): for payee, account_expense, (mu, sigma3) in config: if random.random() < p_day_generate: amount = random.normalvariate(mu, sigma3 / 3.) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{payee}\" \"\" #{tag} {account_credit} {amount_neg:.2f} CCY {account_expense} {amount:.2f} CCY \"\"\", **locals())) # Consume the vacation days. vacation_hrs = (date_end - date_begin).days * 8 # hrs/day new_entries.extend(parse(\"\"\" {date_end} * \"Consume vacation days\" Assets:CC:Employer1:Vacation -{vacation_hrs:.2f} VACHR Expenses:Vacation {vacation_hrs:.2f} VACHR \"\"\", **locals())) # Generate events for the trip. new_entries.extend(parse(\"\"\" {date_begin} event \"location\" \"{trip_city}\" {date_end} event \"location\" \"{home_city}\" \"\"\", **locals())) return new_entries beancount.scripts.example.get_minimum_balance(entries, account, currency) \uf0c1 Compute the minimum balance of the given account according to the entries history. Parameters: entries \u2013 A list of directives. account \u2013 An account string. currency \u2013 A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. Source code in beancount/scripts/example.py def get_minimum_balance(entries, account, currency): \"\"\"Compute the minimum balance of the given account according to the entries history. Args: entries: A list of directives. account: An account string. currency: A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. \"\"\" min_amount = ZERO for _, balances in postings_for(entries, [account]): balance = balances[account] current = balance.get_currency_units(currency).number if current < min_amount: min_amount = current return min_amount beancount.scripts.example.iter_dates_with_balance(date_begin, date_end, entries, accounts) \uf0c1 Iterate over dates, including the balances of the postings iterator. Parameters: postings_iter \u2013 An iterator of postings as per postings_for(). date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. Source code in beancount/scripts/example.py def iter_dates_with_balance(date_begin, date_end, entries, accounts): \"\"\"Iterate over dates, including the balances of the postings iterator. Args: postings_iter: An iterator of postings as per postings_for(). date_begin: The start date. date_end: The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. \"\"\" balances = collections.defaultdict(inventory.Inventory) merged_txn_postings = iter(merge_postings(entries, accounts)) txn_posting = next(merged_txn_postings, None) for date in date_iter(date_begin, date_end): while txn_posting and txn_posting.txn.date == date: posting = txn_posting.posting balances[posting.account].add_position(posting) txn_posting = next(merged_txn_postings, None) yield date, balances beancount.scripts.example.iter_quarters(date_begin, date_end) \uf0c1 Iterate over all quarters between begin and end dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. Source code in beancount/scripts/example.py def iter_quarters(date_begin, date_end): \"\"\"Iterate over all quarters between begin and end dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. \"\"\" quarter = (date_begin.year, (date_begin.month-1)//3) quarter_last = (date_end.year, (date_end.month-1)//3) assert quarter <= quarter_last while True: year, trimester = quarter yield datetime.date(year, trimester*3 + 1, 1) if quarter == quarter_last: break trimester = (trimester + 1) % 4 if trimester == 0: year += 1 quarter = (year, trimester) beancount.scripts.example.merge_postings(entries, accounts) \uf0c1 Merge all the postings from the given account names. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. Source code in beancount/scripts/example.py def merge_postings(entries, accounts): \"\"\"Merge all the postings from the given account names. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. \"\"\" real_root = realization.realize(entries) merged_postings = [] for account in accounts: real_account = realization.get(real_root, account) if real_account is None: continue merged_postings.extend(txn_posting for txn_posting in real_account.txn_postings if isinstance(txn_posting, data.TxnPosting)) merged_postings.sort(key=lambda txn_posting: txn_posting.txn.date) return merged_postings beancount.scripts.example.parse(input_string, **replacements) \uf0c1 Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Parameters: input_string \u2013 Beancount input text. **replacements \u2013 A dict of keywords to replace to their values. Returns: A list of directive objects. Source code in beancount/scripts/example.py def parse(input_string, **replacements): \"\"\"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Args: input_string: Beancount input text. **replacements: A dict of keywords to replace to their values. Returns: A list of directive objects. \"\"\" if replacements: class IgnoreFormatter(string.Formatter): def check_unused_args(self, used_args, args, kwargs): pass formatter = IgnoreFormatter() formatted_string = formatter.format(input_string, **replacements) else: formatted_string = input_string entries, errors, options_map = parser.parse_string(textwrap.dedent(formatted_string)) if errors: printer.print_errors(errors, file=sys.stderr) raise ValueError(\"Parsed text has errors\") # Interpolation. entries, unused_balance_errors = booking.book(entries, options_map) return data.sorted(entries) beancount.scripts.example.postings_for(entries, accounts, before=False) \uf0c1 Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. before \u2013 A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts after applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. Source code in beancount/scripts/example.py def postings_for(entries, accounts, before=False): \"\"\"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. before: A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts _after_ applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. \"\"\" assert isinstance(accounts, list) merged_txn_postings = merge_postings(entries, accounts) balances = collections.defaultdict(inventory.Inventory) for txn_posting in merged_txn_postings: if before: yield txn_posting, balances posting = txn_posting.posting balances[posting.account].add_position(posting) if not before: yield txn_posting, balances beancount.scripts.example.price_series(start, mu, sigma) \uf0c1 Generate a price series based on a simple stochastic model. Parameters: start \u2013 The beginning value. mu \u2013 The per-step drift, in units of value. sigma \u2013 Volatility of the changes. Yields: Floats, at each step. Source code in beancount/scripts/example.py def price_series(start, mu, sigma): \"\"\"Generate a price series based on a simple stochastic model. Args: start: The beginning value. mu: The per-step drift, in units of value. sigma: Volatility of the changes. Yields: Floats, at each step. \"\"\" value = start while 1: yield value value += random.normalvariate(mu, sigma) * value beancount.scripts.example.replace(string, replacements, strip=False) \uf0c1 Apply word-boundaried regular expression replacements to an indented string. Parameters: string \u2013 Some input template string. replacements \u2013 A dict of regexp to replacement value. strip \u2013 A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. Source code in beancount/scripts/example.py def replace(string, replacements, strip=False): \"\"\"Apply word-boundaried regular expression replacements to an indented string. Args: string: Some input template string. replacements: A dict of regexp to replacement value. strip: A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. \"\"\" output = textwrap.dedent(string) if strip: output = output.strip() for from_, to_ in replacements.items(): if not isinstance(to_, str) and not callable(to_): to_ = str(to_) output = re.sub(r'\\b{}\\b'.format(from_), to_, output) return output beancount.scripts.example.validate_output(contents, positive_accounts, currency) \uf0c1 Check that the output file validates. Parameters: contents \u2013 A string, the output file. positive_accounts \u2013 A list of strings, account names to check for non-negative balances. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 If the output does not validate. Source code in beancount/scripts/example.py def validate_output(contents, positive_accounts, currency): \"\"\"Check that the output file validates. Args: contents: A string, the output file. positive_accounts: A list of strings, account names to check for non-negative balances. currency: A string, the currency to check minimums for. Raises: AssertionError: If the output does not validate. \"\"\" loaded_entries, _, _ = loader.load_string( contents, log_errors=sys.stderr, extra_validations=validation.HARDCORE_VALIDATIONS) # Sanity checks: Check that the checking balance never goes below zero. for account in positive_accounts: check_non_negative(loaded_entries, account, currency) beancount.scripts.example.write_example_file(date_birth, date_begin, date_end, reformat, file) \uf0c1 Generate the example file. Parameters: date_birth \u2013 A datetime.date instance, the birth date of our character. date_begin \u2013 A datetime.date instance, the beginning date at which to generate transactions. date_end \u2013 A datetime.date instance, the end date at which to generate transactions. reformat \u2013 A boolean, true if we should apply global reformatting to this file. file \u2013 A file object, where to write out the output. Source code in beancount/scripts/example.py def write_example_file(date_birth, date_begin, date_end, reformat, file): \"\"\"Generate the example file. Args: date_birth: A datetime.date instance, the birth date of our character. date_begin: A datetime.date instance, the beginning date at which to generate transactions. date_end: A datetime.date instance, the end date at which to generate transactions. reformat: A boolean, true if we should apply global reformatting to this file. file: A file object, where to write out the output. \"\"\" # The following code entirely writes out the output to generic names, such # as \"Employer1\", \"Bank1\", and \"CCY\" (for principal currency). Those names # are purposely chosen to be unique, and only near the very end do we make # renamings to more specific and realistic names. # Name of the checking account. account_opening = 'Equity:Opening-Balances' account_payable = 'Liabilities:AccountsPayable' account_checking = 'Assets:CC:Bank1:Checking' account_credit = 'Liabilities:CC:CreditCard1' account_retirement = 'Assets:CC:Retirement' account_investing = 'Assets:CC:Investment:Cash' # Commodities. commodity_entries = generate_commodity_entries(date_birth) # Estimate the rent. rent_amount = round_to(ANNUAL_SALARY / RENT_DIVISOR, RENT_INCREMENT) # Get a random employer. employer_name, employer_address = random.choice(EMPLOYERS) logging.info(\"Generating Salary Employment Income\") income_entries = generate_employment_income(employer_name, employer_address, ANNUAL_SALARY, account_checking, join(account_retirement, 'Cash'), date_begin, date_end) logging.info(\"Generating Expenses from Banking Accounts\") banking_expenses = generate_banking_expenses(date_begin, date_end, account_checking, rent_amount) logging.info(\"Generating Regular Expenses via Credit Card\") credit_regular_entries = generate_regular_credit_expenses( date_birth, date_begin, date_end, account_credit, account_checking) logging.info(\"Generating Credit Card Expenses for Trips\") trip_entries = [] destinations = sorted(TRIP_DESTINATIONS.items()) destinations.extend(destinations) random.shuffle(destinations) for (date_trip_begin, date_trip_end), (destination_name, config) in zip( compute_trip_dates(date_begin, date_end), destinations): # Compute a suitable tag. tag = 'trip-{}-{}'.format(destination_name.lower().replace(' ', '-'), date_trip_begin.year) #logging.info(\"%s -- %s %s\", tag, date_trip_begin, date_trip_end) # Remove regular entries during this trip. credit_regular_entries = [entry for entry in credit_regular_entries if not(date_trip_begin <= entry.date < date_trip_end)] # Generate entries for the trip. this_trip_entries = generate_trip_entries( date_trip_begin, date_trip_end, tag, config, destination_name.replace('-', ' ').title(), HOME_NAME, account_credit) trip_entries.extend(this_trip_entries) logging.info(\"Generating Credit Card Payment Entries\") credit_payments = generate_clearing_entries( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end, bymonthday=7), 0, 4), \"CreditCard1\", \"Paying off credit card\", credit_regular_entries, account_credit, account_checking) credit_entries = credit_regular_entries + trip_entries + credit_payments logging.info(\"Generating Tax Filings and Payments\") tax_preamble = generate_tax_preamble(date_birth) # Figure out all the years we need tax accounts for. years = set() for account_name in getters.get_accounts(income_entries): match = re.match(r'Expenses:Taxes:Y(\\d\\d\\d\\d)', account_name) if match: years.add(int(match.group(1))) taxes = [(year, generate_tax_accounts(year, date_end)) for year in sorted(years)] tax_entries = tax_preamble + functools.reduce(operator.add, (entries for _, entries in taxes)) logging.info(\"Generating Opening of Banking Accounts\") # Open banking accounts and gift the checking account with a balance that # will offset all the amounts to ensure a positive balance throughout its # lifetime. entries_for_banking = data.sorted(income_entries + banking_expenses + credit_entries + tax_entries) minimum = get_minimum_balance(entries_for_banking, account_checking, 'CCY') banking_entries = generate_banking(entries_for_banking, date_begin, date_end, max(-minimum, ZERO)) logging.info(\"Generating Transfers to Investment Account\") banking_transfers = generate_outgoing_transfers( data.sorted(income_entries + banking_entries + banking_expenses + credit_entries + tax_entries), account_checking, account_investing, transfer_minimum=D('200'), transfer_threshold=D('3000'), transfer_increment=D('500')) logging.info(\"Generating Prices\") # Generate price entries for investment currencies and create a price map to # use for later for generating investment transactions. funds_allocation = {'MFUND1': 0.40, 'MFUND2': 0.60} stocks = ['STK1', 'STK2', 'STK3', 'STK4'] price_entries = generate_prices(date_begin, date_end, sorted(funds_allocation.keys()) + stocks, 'CCY') price_map = prices.build_price_map(price_entries) logging.info(\"Generating Employer Match Contribution\") account_match = 'Income:US:Employer1:Match401k' retirement_match = generate_retirement_employer_match(income_entries, join(account_retirement, 'Cash'), account_match) logging.info(\"Generating Retirement Investments\") retirement_entries = generate_retirement_investments( income_entries + retirement_match, account_retirement, sorted(funds_allocation.items()), price_map) logging.info(\"Generating Taxes Investments\") investment_entries = generate_taxable_investment(date_begin, date_end, banking_transfers, price_map, stocks) logging.info(\"Generating Expense Accounts\") expense_accounts_entries = generate_expense_accounts(date_birth) logging.info(\"Generating Equity Accounts\") equity_entries = generate_open_entries(date_birth, [account_opening, account_payable]) logging.info(\"Generating Balance Checks\") credit_checks = generate_balance_checks(credit_entries, account_credit, date_random_seq(date_begin, date_end, 20, 30)) banking_checks = generate_balance_checks(data.sorted(income_entries + banking_entries + banking_expenses + banking_transfers + credit_entries + tax_entries), account_checking, date_random_seq(date_begin, date_end, 20, 30)) logging.info(\"Outputting and Formatting Entries\") dcontext = display_context.DisplayContext() default_int_digits = 8 for currency, precision in {'USD': 2, 'CAD': 2, 'VACHR':0, 'IRAUSD': 2, 'VBMPX': 3, 'RGAGX': 3, 'ITOT': 0, 'VEA': 0, 'VHT': 0, 'GLD': 0}.items(): int_digits = default_int_digits if precision > 0: int_digits += 1 + precision dcontext.update(D('{{:0{}.{}f}}'.format(int_digits, precision).format(0)), currency) output = io.StringIO() def output_section(title, entries): output.write('\\n\\n\\n{}\\n\\n'.format(title)) printer.print_entries(data.sorted(entries), dcontext, file=output) output.write(FILE_PREAMBLE.format(**locals())) output_section('* Commodities', commodity_entries) output_section('* Equity Accounts', equity_entries) output_section('* Banking', data.sorted(banking_entries + banking_expenses + banking_transfers + banking_checks)) output_section('* Credit-Cards', data.sorted(credit_entries + credit_checks)) output_section('* Taxable Investments', investment_entries) output_section('* Retirement Investments', data.sorted(retirement_entries + retirement_match)) output_section('* Sources of Income', income_entries) output_section('* Taxes', tax_preamble) for year, entries in taxes: output_section('** Tax Year {}'.format(year), entries) output_section('* Expenses', expense_accounts_entries) output_section('* Prices', price_entries) output_section('* Cash', []) logging.info(\"Contextualizing to Realistic Names\") contents, replacements = contextualize_file(output.getvalue(), employer_name) if reformat: contents = format.align_beancount(contents) logging.info(\"Writing contents\") file.write(contents) logging.info(\"Validating Results\") validate_output(contents, [replace(account, replacements) for account in [account_checking]], replace('CCY', replacements)) beancount.scripts.format \uf0c1 Align a beancount/ledger input file's numbers. This reformats at beancount or ledger input file so that the amounts in the postings are all aligned to the same column. The currency should match. Note: this does not parse the Beancount ledger. It simply uses regular expressions and text manipulations to do its work. beancount.scripts.format.align_beancount(contents, prefix_width=None, num_width=None, currency_column=None) \uf0c1 Reformat Beancount input to align all the numbers at the same column. Parameters: contents \u2013 A string, Beancount input syntax to reformat. prefix_width \u2013 An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width \u2013 An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column \u2013 An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. Source code in beancount/scripts/format.py def align_beancount(contents, prefix_width=None, num_width=None, currency_column=None): \"\"\"Reformat Beancount input to align all the numbers at the same column. Args: contents: A string, Beancount input syntax to reformat. prefix_width: An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width: An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column: An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. \"\"\" # Find all lines that have a number in them and calculate the maximum length # of the stripped prefix and the number. match_pairs = [] for line in contents.splitlines(): match = re.match( r'([^\";]*?)\\s+([-+]?\\s*[\\d,]+(?:\\.\\d*)?)\\s+({}\\b.*)'.format(amount.CURRENCY_RE), line) if match: prefix, number, rest = match.groups() match_pairs.append((prefix, number, rest)) else: match_pairs.append((line, None, None)) # Normalize whitespace before lines that has some indent and an account # name. norm_match_pairs = normalize_indent_whitespace(match_pairs) if currency_column: output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: num_of_spaces = currency_column - len(prefix) - len(number) - 4 spaces = ' ' * num_of_spaces output.write(prefix + spaces + ' ' + number + ' ' + rest) output.write('\\n') return output.getvalue() # Compute the maximum widths. filtered_pairs = [(prefix, number) for prefix, number, _ in match_pairs if number is not None] if filtered_pairs: max_prefix_width = max(len(prefix) for prefix, _ in filtered_pairs) max_num_width = max(len(number) for _, number in filtered_pairs) else: max_prefix_width = 0 max_num_width = 0 # Use user-supplied overrides, if available if prefix_width: max_prefix_width = prefix_width if num_width: max_num_width = num_width # Create a format that will admit the maximum width of all prefixes equally. line_format = '{{:<{prefix_width}}} {{:>{num_width}}} {{}}'.format( prefix_width=max_prefix_width, num_width=max_num_width) # Process each line to an output buffer. output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: output.write(line_format.format(prefix.rstrip(), number, rest)) output.write('\\n') formatted_contents = output.getvalue() # Ensure that the file before and after have only whitespace differences. # This is a sanity check, to make really sure we never change anything but whitespace, # so it's safe. # open('/tmp/before', 'w').write(re.sub(r'[ \\t]+', ' ', contents)) # open('/tmp/after', 'w').write(re.sub(r'[ \\t]+', ' ', formatted_contents)) old_stripped = re.sub(r'[ \\t\\n]+', ' ', contents.rstrip()) new_stripped = re.sub(r'[ \\t\\n]+', ' ', formatted_contents.rstrip()) assert (old_stripped == new_stripped), (old_stripped, new_stripped) return formatted_contents beancount.scripts.format.compute_most_frequent(iterable) \uf0c1 Compute the frequencies of the given elements and return the most frequent. Parameters: iterable \u2013 A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. Source code in beancount/scripts/format.py def compute_most_frequent(iterable): \"\"\"Compute the frequencies of the given elements and return the most frequent. Args: iterable: A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. \"\"\" frequencies = collections.Counter(iterable) if not frequencies: return None counts = sorted((count, element) for element, count in frequencies.items()) # Note: In case of a tie, this chooses the longest width. # We could eventually make this an option. return counts[-1][1] beancount.scripts.format.normalize_indent_whitespace(match_pairs) \uf0c1 Normalize whitespace before lines that has some indent and an account name. Parameters: match_pairs \u2013 A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. Source code in beancount/scripts/format.py def normalize_indent_whitespace(match_pairs): \"\"\"Normalize whitespace before lines that has some indent and an account name. Args: match_pairs: A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. \"\"\" # Compute most frequent account name prefix. match_posting = re.compile(r'([ \\t]+)({}.*)'.format(account.ACCOUNT_RE)).match width = compute_most_frequent( len(match.group(1)) for match in (match_posting(prefix) for prefix, _, _ in match_pairs) if match is not None) norm_format = ' ' * (width or 0) + '{}' # Make the necessary adjustments. adjusted_pairs = [] for tup in match_pairs: prefix, number, rest = tup match = match_posting(prefix) if match is not None: tup = (norm_format.format(match.group(2)), number, rest) adjusted_pairs.append(tup) return adjusted_pairs beancount.scripts.sql \uf0c1 Convert a Beancount ledger into an SQL database. beancount.scripts.sql.BalanceWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.BalanceWriter.type ( tuple ) \uf0c1 Balance(meta, date, account, amount, tolerance, diff_amount) beancount.scripts.sql.BalanceWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.BalanceWriter.type.__new__(_cls, meta, date, account, amount, tolerance, diff_amount) special staticmethod \uf0c1 Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount) beancount.scripts.sql.BalanceWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.BalanceWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.amount.number, entry.amount.currency, entry.diff_amount.currency if entry.diff_amount else None, entry.diff_amount.currency if entry.diff_amount else None) beancount.scripts.sql.CloseWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.CloseWriter.type ( tuple ) \uf0c1 Close(meta, date, account) beancount.scripts.sql.CloseWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.CloseWriter.type.__new__(_cls, meta, date, account) special staticmethod \uf0c1 Create new instance of Close(meta, date, account) beancount.scripts.sql.CloseWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.CloseWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account,) beancount.scripts.sql.DirectiveWriter \uf0c1 A base class for writers of directives. This is used to factor out code for all the simple directives types (all types except Transaction). beancount.scripts.sql.DirectiveWriter.__call__(self, connection, entries) special \uf0c1 Create a table for a directives. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def __call__(self, connection, entries): \"\"\"Create a table for a directives. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: columns_text = ','.join(self.columns.strip().splitlines()) connection.execute(\"\"\" CREATE TABLE {name}_detail ( id INTEGER PRIMARY KEY, {columns} ); \"\"\".format(name=self.name, columns=columns_text)) connection.execute(\"\"\" CREATE VIEW {name} AS SELECT * FROM entry JOIN {name}_detail USING (id); \"\"\".format(name=self.name)) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, self.type): continue # Store common data. connection.execute(\"\"\" INSERT INTO entry VALUES (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, self.name, entry.meta[\"filename\"], entry.meta[\"lineno\"])) # Store detail data. detail_data = self.get_detail(entry) row_data = (eid,) + detail_data query = \"\"\" INSERT INTO {name}_detail VALUES ({placeholder}); \"\"\".format(name=self.name, placeholder=','.join(['?'] * (1 + len(detail_data)))) connection.execute(query, row_data) beancount.scripts.sql.DirectiveWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): \"\"\"Provide data to store for details table. Args: entry: An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. \"\"\" raise NotImplementedError beancount.scripts.sql.DocumentWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.DocumentWriter.type ( tuple ) \uf0c1 Document(meta, date, account, filename, tags, links) beancount.scripts.sql.DocumentWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.DocumentWriter.type.__new__(_cls, meta, date, account, filename, tags, links) special staticmethod \uf0c1 Create new instance of Document(meta, date, account, filename, tags, links) beancount.scripts.sql.DocumentWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.DocumentWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.filename) beancount.scripts.sql.EventWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.EventWriter.type ( tuple ) \uf0c1 Event(meta, date, type, description) beancount.scripts.sql.EventWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.EventWriter.type.__new__(_cls, meta, date, type, description) special staticmethod \uf0c1 Create new instance of Event(meta, date, type, description) beancount.scripts.sql.EventWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.EventWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.type, entry.description) beancount.scripts.sql.NoteWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.NoteWriter.type ( tuple ) \uf0c1 Note(meta, date, account, comment) beancount.scripts.sql.NoteWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.NoteWriter.type.__new__(_cls, meta, date, account, comment) special staticmethod \uf0c1 Create new instance of Note(meta, date, account, comment) beancount.scripts.sql.NoteWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.NoteWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.comment) beancount.scripts.sql.OpenWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.OpenWriter.type ( tuple ) \uf0c1 Open(meta, date, account, currencies, booking) beancount.scripts.sql.OpenWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.OpenWriter.type.__new__(_cls, meta, date, account, currencies, booking) special staticmethod \uf0c1 Create new instance of Open(meta, date, account, currencies, booking) beancount.scripts.sql.OpenWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.OpenWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, ','.join(entry.currencies or [])) beancount.scripts.sql.PadWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.PadWriter.type ( tuple ) \uf0c1 Pad(meta, date, account, source_account) beancount.scripts.sql.PadWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.PadWriter.type.__new__(_cls, meta, date, account, source_account) special staticmethod \uf0c1 Create new instance of Pad(meta, date, account, source_account) beancount.scripts.sql.PadWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.PadWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.source_account) beancount.scripts.sql.PriceWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.PriceWriter.type ( tuple ) \uf0c1 Price(meta, date, currency, amount) beancount.scripts.sql.PriceWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.PriceWriter.type.__new__(_cls, meta, date, currency, amount) special staticmethod \uf0c1 Create new instance of Price(meta, date, currency, amount) beancount.scripts.sql.PriceWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.PriceWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.currency, entry.amount.number, entry.amount.currency) beancount.scripts.sql.QueryWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.QueryWriter.type ( tuple ) \uf0c1 Query(meta, date, name, query_string) beancount.scripts.sql.QueryWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.QueryWriter.type.__new__(_cls, meta, date, name, query_string) special staticmethod \uf0c1 Create new instance of Query(meta, date, name, query_string) beancount.scripts.sql.QueryWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.QueryWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.name, entry.query_string) beancount.scripts.sql.adapt_decimal(number) \uf0c1 Adapt a Decimal instance to a string for creating queries. Parameters: number \u2013 An instance of Decimal. Returns: A string. Source code in beancount/scripts/sql.py def adapt_decimal(number): \"\"\"Adapt a Decimal instance to a string for creating queries. Args: number: An instance of Decimal. Returns: A string. \"\"\" return str(number) beancount.scripts.sql.convert_decimal(string) \uf0c1 Convert a Decimal string to a Decimal instance. Parameters: string \u2013 A decimal number in a string. Returns: An instance of Decimal. Source code in beancount/scripts/sql.py def convert_decimal(string): \"\"\"Convert a Decimal string to a Decimal instance. Args: string: A decimal number in a string. Returns: An instance of Decimal. \"\"\" return Decimal(string) beancount.scripts.sql.output_common(connection, unused_entries) \uf0c1 Create a table of common data for all entries. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_common(connection, unused_entries): \"\"\"Create a table of common data for all entries. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE entry ( id INTEGER PRIMARY KEY, date DATE, type CHARACTER(8), source_filename STRING, source_lineno INTEGER ); \"\"\") beancount.scripts.sql.output_transactions(connection, entries) \uf0c1 Create a table for transactions and fill in the data. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_transactions(connection, entries): \"\"\"Create a table for transactions and fill in the data. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE transactions_detail ( id INTEGER PRIMARY KEY, flag CHARACTER(1), payee VARCHAR, narration VARCHAR, tags VARCHAR, -- Comma-separated links VARCHAR -- Comma-separated ); \"\"\") connection.execute(\"\"\" CREATE VIEW transactions AS SELECT * FROM entry JOIN transactions_detail USING (id); \"\"\") connection.execute(\"\"\" CREATE TABLE postings ( posting_id INTEGER PRIMARY KEY, id INTEGER, flag CHARACTER(1), account VARCHAR, number DECIMAL(16, 6), currency CHARACTER(10), cost_number DECIMAL(16, 6), cost_currency CHARACTER(10), cost_date DATE, cost_label VARCHAR, price_number DECIMAL(16, 6), price_currency CHARACTER(10), FOREIGN KEY(id) REFERENCES entries(id) ); \"\"\") postings_count = iter(itertools.count()) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue connection.execute(\"\"\" insert into entry values (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, 'txn', entry.meta[\"filename\"], entry.meta[\"lineno\"])) connection.execute(\"\"\" insert into transactions_detail values (?, ?, ?, ?, ?, ?); \"\"\", (eid, entry.flag, entry.payee, entry.narration, ','.join(entry.tags or ()), ','.join(entry.links or ()))) for posting in entry.postings: pid = next(postings_count) units = posting.units cost = posting.cost price = posting.price connection.execute(\"\"\" INSERT INTO postings VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \"\"\", (pid, eid, posting.flag, posting.account, units.number, units.currency, cost.number if cost else None, cost.currency if cost else None, cost.date if cost else None, cost.label if cost else None, price.number if price else None, price.currency if price else None)) beancount.scripts.sql.setup_decimal_support() \uf0c1 Setup sqlite3 to support conversions to/from Decimal numbers. Source code in beancount/scripts/sql.py def setup_decimal_support(): \"\"\"Setup sqlite3 to support conversions to/from Decimal numbers. \"\"\" dbapi.register_adapter(Decimal, adapt_decimal) dbapi.register_converter(\"decimal\", convert_decimal) beancount.scripts.tutorial \uf0c1 Write output files for the tutorial commands.","title":"beancount.scripts"},{"location":"api_reference/beancount.scripts.html#beancountscripts","text":"Implementation of the various scripts available from bin. This is structured this way because we want all the significant codes under a single directory, for analysis, grepping and unit testing.","title":"beancount.scripts"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake","text":"Bake a Beancount input file's web files to a directory hierarchy. You provide a Beancount filename, an output directory, and this script runs a server and a scraper that puts all the files in the directory, and if your output name has an archive suffix, we automatically the fetched directory contents to the archive and delete them.","title":"bake"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.archive","text":"Archive the directory to the given tar/gz archive filename. Parameters: command_template \u2013 A string, the command template to format with in order to compute the command to run. directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. quiet \u2013 A boolean, True to suppress output. Exceptions: IOError \u2013 if the directory does not exist or if the archive name already Source code in beancount/scripts/bake.py def archive(command_template, directory, archive, quiet=False): \"\"\"Archive the directory to the given tar/gz archive filename. Args: command_template: A string, the command template to format with in order to compute the command to run. directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. quiet: A boolean, True to suppress output. Raises: IOError: if the directory does not exist or if the archive name already exists. \"\"\" directory = path.abspath(directory) archive = path.abspath(archive) if not path.exists(directory): raise IOError(\"Directory to archive '{}' does not exist\".format( directory)) if path.exists(archive): raise IOError(\"Output archive name '{}' already exists\".format( archive)) command = command_template.format(directory=directory, dirname=path.dirname(directory), basename=path.basename(directory), archive=archive) pipe = subprocess.Popen(shlex.split(command), shell=False, cwd=path.dirname(directory), stdout=subprocess.PIPE if quiet else None, stderr=subprocess.PIPE if quiet else None) _, _ = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Archive failure\")","title":"archive()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.archive_zip","text":"Archive the directory to the given tar/gz archive filename. Parameters: directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. Source code in beancount/scripts/bake.py def archive_zip(directory, archive): \"\"\"Archive the directory to the given tar/gz archive filename. Args: directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. \"\"\" # Figure out optimal level of compression among the supported ones in this # installation. for spec, compression in [ ('lzma', zipfile.ZIP_LZMA), ('bz2', zipfile.ZIP_BZIP2), ('zlib', zipfile.ZIP_DEFLATED)]: if importlib.util.find_spec(spec): zip_compression = compression break else: # Default is no compression. zip_compression = zipfile.ZIP_STORED with file_utils.chdir(directory), zipfile.ZipFile( archive, 'w', compression=zip_compression) as archfile: for root, dirs, files in os.walk(directory): for filename in files: relpath = path.relpath(path.join(root, filename), directory) archfile.write(relpath)","title":"archive_zip()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.bake_to_directory","text":"Serve and bake a Beancount's web to a directory. Parameters: webargs \u2013 An argparse parsed options object with the web app arguments. output_dir \u2013 A directory name. We don't check here whether it exists or not. quiet \u2013 A boolean, True to suppress web server fetch log. render_all_pages \u2013 If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. Source code in beancount/scripts/bake.py def bake_to_directory(webargs, output_dir, render_all_pages=True): \"\"\"Serve and bake a Beancount's web to a directory. Args: webargs: An argparse parsed options object with the web app arguments. output_dir: A directory name. We don't check here whether it exists or not. quiet: A boolean, True to suppress web server fetch log. render_all_pages: If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. \"\"\" callback = functools.partial(save_scraped_document, output_dir) if render_all_pages: ignore_regexps = None else: regexps = [ # Skip the context pages, too slow. r'/context/', # Skip the link pages, too slow. r'/link/', # Skip the component pages... too many. r'/view/component/', # Skip served documents. r'/.*/doc/', # Skip monthly pages. r'/view/year/\\d\\d\\d\\d/month/', ] ignore_regexps = '({})'.format('|'.join(regexps)) processed_urls, skipped_urls = web.scrape_webapp(webargs, callback, ignore_regexps)","title":"bake_to_directory()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.normalize_filename","text":"Convert URL paths to filenames. Add .html extension if needed. Parameters: url \u2013 A string, the url to convert. Returns: A string, possibly with an extension appended. Source code in beancount/scripts/bake.py def normalize_filename(url): \"\"\"Convert URL paths to filenames. Add .html extension if needed. Args: url: A string, the url to convert. Returns: A string, possibly with an extension appended. \"\"\" if url.endswith('/'): return path.join(url, 'index.html') elif BINARY_MATCH(url): return url else: return url if url.endswith('.html') else (url + '.html')","title":"normalize_filename()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.relativize_links","text":"Make all the links in the contents string relative to an URL. Parameters: html \u2013 An lxml document node. current_url \u2013 A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. Source code in beancount/scripts/bake.py def relativize_links(html, current_url): \"\"\"Make all the links in the contents string relative to an URL. Args: html: An lxml document node. current_url: A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. \"\"\" current_dir = path.dirname(current_url) for element, attribute, link, pos in lxml.html.iterlinks(html): if path.isabs(link): relative_link = path.relpath(normalize_filename(link), current_dir) element.set(attribute, relative_link)","title":"relativize_links()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.remove_links","text":"Convert a list of anchors () from an HTML tree to spans (). Parameters: html \u2013 An lxml document node. targets \u2013 A set of string, targets to be removed. Source code in beancount/scripts/bake.py def remove_links(html, targets): \"\"\"Convert a list of anchors () from an HTML tree to spans (). Args: html: An lxml document node. targets: A set of string, targets to be removed. \"\"\" for element, attribute, link, pos in lxml.html.iterlinks(html): if link in targets: del element.attrib[attribute] element.tag = 'span' element.set('class', 'removed-link')","title":"remove_links()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.save_scraped_document","text":"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Parameters: output_dir \u2013 A string, the output directory to write. url \u2013 A string, the originally requested URL. response \u2013 An http response as per urlopen. contents \u2013 Bytes, the content of a response. html_root \u2013 An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls \u2013 A set of the links from the file that were skipped. Source code in beancount/scripts/bake.py def save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls): \"\"\"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Args: output_dir: A string, the output directory to write. url: A string, the originally requested URL. response: An http response as per urlopen. contents: Bytes, the content of a response. html_root: An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls: A set of the links from the file that were skipped. \"\"\" if response.status != 200: logging.error(\"Invalid status: %s\", response.status) # Ignore directories. if url.endswith('/'): return # Note that we're saving the file under the non-redirected URL, because this # will have to be opened using files and there are no redirects that way. if response.info().get_content_type() == 'text/html': if html_root is None: html_root = lxml.html.document_fromstring(contents) remove_links(html_root, skipped_urls) relativize_links(html_root, url) contents = lxml.html.tostring(html_root, method=\"html\") # Compute output filename and write out the relativized contents. output_filename = path.join(output_dir, normalize_filename(url).lstrip('/')) os.makedirs(path.dirname(output_filename), exist_ok=True) with open(output_filename, 'wb') as outfile: outfile.write(contents)","title":"save_scraped_document()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.check","text":"Parse, check and realize a beancount input file. This also measures the time it takes to run all these steps.","title":"check"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps","text":"Check the installation dependencies and report the version numbers of each. This is meant to be used as an error diagnostic tool.","title":"deps"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_cdecimal","text":"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_cdecimal(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" # Note: this code mirrors and should be kept in-sync with that at the top of # beancount.core.number. # Try the built-in installation. import decimal if is_fast_decimal(decimal): return ('cdecimal', '{} (built-in)'.format(decimal.__version__), True) # Try an explicitly installed version. try: import cdecimal if is_fast_decimal(cdecimal): return ('cdecimal', getattr(cdecimal, '__version__', 'OKAY'), True) except ImportError: pass # Not found. return ('cdecimal', None, False)","title":"check_cdecimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_dependencies","text":"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. Source code in beancount/scripts/deps.py def check_dependencies(): \"\"\"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. \"\"\" return [ # Check for a complete installation of Python itself. check_python(), check_cdecimal(), # Modules we really do need installed. check_import('dateutil'), check_import('bottle'), check_import('ply', module_name='ply.yacc', min_version='3.4'), check_import('lxml', module_name='lxml.etree', min_version='3'), # Optionally required to upload data to Google Drive. check_import('googleapiclient'), check_import('oauth2client'), check_import('httplib2'), # Optionally required to support various price source fetchers. check_import('requests', min_version='2.0'), # Optionally required to support imports (identify, extract, file) code. check_python_magic(), check_import('beautifulsoup4', module_name='bs4', min_version='4'), ]","title":"check_dependencies()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_import","text":"Check that a particular module name is installed. Parameters: package_name \u2013 A string, the name of the package and module to be imported to verify this works. This should have a version attribute on it. min_version \u2013 If not None, a string, the minimum version number we require. module_name \u2013 The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_import(package_name, min_version=None, module_name=None): \"\"\"Check that a particular module name is installed. Args: package_name: A string, the name of the package and module to be imported to verify this works. This should have a __version__ attribute on it. min_version: If not None, a string, the minimum version number we require. module_name: The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" if module_name is None: module_name = package_name try: __import__(module_name) module = sys.modules[module_name] if min_version is not None: version = module.__version__ assert isinstance(version, str) is_sufficient = (parse_version(version) >= parse_version(min_version) if min_version else True) else: version, is_sufficient = None, True except ImportError: version, is_sufficient = None, False return (package_name, version, is_sufficient)","title":"check_import()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_python","text":"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" return ('python3', '.'.join(map(str, sys.version_info[:3])), sys.version_info[:2] >= (3, 3))","title":"check_python()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_python_magic","text":"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python_magic(): \"\"\"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" try: import magic # Check that python-magic and not filemagic is installed. if not hasattr(magic, 'from_file'): # 'filemagic' is installed; install python-magic. raise ImportError return ('python-magic', 'OK', True) except ImportError: return ('python-magic', None, False)","title":"check_python_magic()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.is_fast_decimal","text":"Return true if a fast C decimal implementation is installed. Source code in beancount/scripts/deps.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType)","title":"is_fast_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.list_dependencies","text":"Check the dependencies and produce a listing on the given file. Parameters: file \u2013 A file object to write the output to. Source code in beancount/scripts/deps.py def list_dependencies(file=sys.stderr): \"\"\"Check the dependencies and produce a listing on the given file. Args: file: A file object to write the output to. \"\"\" print(\"Dependencies:\") for package, version, sufficient in check_dependencies(): print(\" {:16}: {} {}\".format( package, version or 'NOT INSTALLED', \"(INSUFFICIENT)\" if version and not sufficient else \"\"), file=file)","title":"list_dependencies()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.parse_version","text":"Parse the version string into a comparable tuple. Source code in beancount/scripts/deps.py def parse_version(version_str: str) -> str: \"\"\"Parse the version string into a comparable tuple.\"\"\" return [int(v) for v in version_str.split('.')]","title":"parse_version()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories","text":"Check that document directories mirror a list of accounts correctly.","title":"directories"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.ValidateDirectoryError","text":"A directory validation error.","title":"ValidateDirectoryError"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.validate_directories","text":"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: entries \u2013 A list of directives. document_dirs \u2013 A list of string, the directory roots to walk and validate. Source code in beancount/scripts/directories.py def validate_directories(entries, document_dirs): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: entries: A list of directives. document_dirs: A list of string, the directory roots to walk and validate. \"\"\" # Get the list of accounts declared in the ledge. accounts = getters.get_accounts(entries) # For each of the roots, validate the hierarchy of directories. for document_dir in document_dirs: errors = validate_directory(accounts, document_dir) for error in errors: print(\"ERROR: {}\".format(error))","title":"validate_directories()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.validate_directory","text":"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Parameters: account \u2013 A set or dict of account names. document_dir \u2013 A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. Source code in beancount/scripts/directories.py def validate_directory(accounts, document_dir): \"\"\"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Args: account: A set or dict of account names. document_dir: A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. \"\"\" # Generate all parent accounts in the account_set we're checking against, so # that parent directories with no corresponding account don't warn. accounts_with_parents = set(accounts) for account_ in accounts: while True: parent = account.parent(account_) if not parent: break if parent in accounts_with_parents: break accounts_with_parents.add(parent) account_ = parent errors = [] for directory, account_name, _, _ in account.walk(document_dir): if account_name not in accounts_with_parents: errors.append(ValidateDirectoryError( \"Invalid directory '{}': no corresponding account '{}'\".format( directory, account_name))) return errors","title":"validate_directory()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor","text":"Debugging tool for those finding bugs in Beancount. This tool is able to dump lexer/parser state, and will provide other services in the name of debugging.","title":"doctor"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError","text":"RenderError(source, message, entry)","title":"RenderError"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/doctor.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__new__","text":"Create new instance of RenderError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/doctor.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_checkdeps","text":"Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.')","title":"do_checkdeps()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_context","text":"Describe the context that a particular transaction is applied to. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). Source code in beancount/scripts/doctor.py def do_context(filename, args): \"\"\"Describe the context that a particular transaction is applied to. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). \"\"\" from beancount.reports import context from beancount import loader # Check we have the required number of arguments. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") # Load the input files. entries, errors, options_map = loader.load_file(filename) # Parse the arguments, get the line number. match = re.match(r\"(.+):(\\d+)$\", args[0]) if match: search_filename = path.abspath(match.group(1)) lineno = int(match.group(2)) elif re.match(r\"(\\d+)$\", args[0]): # Note: Make sure to use the absolute filename used by the parser to # resolve the file. search_filename = options_map['filename'] lineno = int(args[0]) else: raise SystemExit(\"Invalid format for location.\") str_context = context.render_file_context(entries, options_map, search_filename, lineno) sys.stdout.write(str_context)","title":"do_context()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_deps","text":"Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.')","title":"do_deps()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_directories","text":"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: filename \u2013 A string, the Beancount input filename. args \u2013 The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. Source code in beancount/scripts/doctor.py def do_directories(filename, args): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: filename: A string, the Beancount input filename. args: The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. \"\"\" from beancount import loader from beancount.scripts import directories entries, _, __ = loader.load_file(filename) directories.validate_directories(entries, args)","title":"do_directories()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_display_context","text":"Print out the precision inferred from the parsed numbers in the input file. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_display_context(filename, args): \"\"\"Print out the precision inferred from the parsed numbers in the input file. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount import loader entries, errors, options_map = loader.load_file(filename) dcontext = options_map['dcontext'] sys.stdout.write(str(dcontext))","title":"do_display_context()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_dump_lexer","text":"Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text)))","title":"do_dump_lexer()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_lex","text":"Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text)))","title":"do_lex()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_linked","text":"Print out a list of transactions linked to the one at the given line. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_linked(filename, args): \"\"\"Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. if closest_entry is None: raise SystemExit(\"No entry could be found before {}:{}\".format(filename, lineno)) links = (closest_entry.links if isinstance(closest_entry, data.Transaction) else data.EMPTY_SET) if not links: linked_entries = [closest_entry] else: # Find all linked entries. # # Note that there is an option here: You can either just look at the links # on the closest entry, or you can include the links of the linked # transactions as well. Whichever one you want depends on how you use your # links. Best would be to query the user (in Emacs) when there are many # links present. follow_links = True if not follow_links: linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] else: links = set(links) linked_entries = [] while True: num_linked = len(linked_entries) linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] if len(linked_entries) == num_linked: break for entry in linked_entries: if entry.links: links.update(entry.links) # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) dformat = options_map['dcontext'].build(alignment=display_context.Align.DOT, reserved=2) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))","title":"do_linked()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_list_options","text":"Print out a list of the available options. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_list_options(*unused_args): \"\"\"Print out a list of the available options. Args: unused_args: Ignored. \"\"\" from beancount.parser import options print(options.list_options())","title":"do_list_options()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_missing_open","text":"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_missing_open(filename, args): \"\"\"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import printer from beancount.core import data from beancount.core import getters from beancount import loader entries, errors, options_map = loader.load_file(filename) # Get accounts usage and open directives. first_use_map, _ = getters.get_accounts_use_map(entries) open_close_map = getters.get_account_open_close(entries) new_entries = [] for account, first_use_date in first_use_map.items(): if account not in open_close_map: new_entries.append( data.Open(data.new_metadata(filename, 0), first_use_date, account, None, None)) dcontext = options_map['dcontext'] printer.print_entries(data.sorted(new_entries), dcontext)","title":"do_missing_open()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_parse","text":"Run the parser in debug mode. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_parse(filename, unused_args): \"\"\"Run the parser in debug mode. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import parser entries, errors, _ = parser.parse_file(filename, yydebug=1)","title":"do_parse()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_print_options","text":"Print out the actual options parsed from a file. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_print_options(filename, *args): \"\"\"Print out the actual options parsed from a file. Args: unused_args: Ignored. \"\"\" from beancount import loader _, __, options_map = loader.load_file(filename) for key, value in sorted(options_map.items()): print('{}: {}'.format(key, value))","title":"do_print_options()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_roundtrip","text":"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_roundtrip(filename, unused_args): \"\"\"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import printer from beancount.core import compare from beancount import loader round1_filename = round2_filename = None try: logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') logging.info(\"Read the entries\") entries, errors, options_map = loader.load_file(filename) printer.print_errors(errors, file=sys.stderr) logging.info(\"Print them out to a file\") basename, extension = path.splitext(filename) round1_filename = ''.join([basename, '.roundtrip1', extension]) with open(round1_filename, 'w') as outfile: printer.print_entries(entries, file=outfile) logging.info(\"Read the entries from that file\") # Note that we don't want to run any of the auto-generation here, but # parsing now returns incomplete objects and we assume idempotence on a # file that was output from the printer after having been processed, so # it shouldn't add anything new. That is, a processed file printed and # resolve when parsed again should contain the same entries, i.e. # nothing new should be generated. entries_roundtrip, errors, options_map = loader.load_file(round1_filename) # Print out the list of errors from parsing the results. if errors: print(',----------------------------------------------------------------------') printer.print_errors(errors, file=sys.stdout) print('`----------------------------------------------------------------------') logging.info(\"Print what you read to yet another file\") round2_filename = ''.join([basename, '.roundtrip2', extension]) with open(round2_filename, 'w') as outfile: printer.print_entries(entries_roundtrip, file=outfile) logging.info(\"Compare the original entries with the re-read ones\") same, missing1, missing2 = compare.compare_entries(entries, entries_roundtrip) if same: logging.info('Entries are the same. Congratulations.') else: logging.error('Entries differ!') print() print('\\n\\nMissing from original:') for entry in entries: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() print('\\n\\nMissing from round-trip:') for entry in missing2: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() finally: for rfilename in (round1_filename, round2_filename): if path.exists(rfilename): os.remove(rfilename)","title":"do_roundtrip()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_validate_html","text":"Validate all the HTML files under a directory hierarchy. Parameters: directory \u2013 A string, the root directory whose contents to validate. args \u2013 A tuple of the rest of arguments. Source code in beancount/scripts/doctor.py def do_validate_html(directory, args): \"\"\"Validate all the HTML files under a directory hierarchy. Args: directory: A string, the root directory whose contents to validate. args: A tuple of the rest of arguments. \"\"\" from beancount.web import scrape files, missing, empty = scrape.validate_local_links_in_dir(directory) logging.info('%d files processed', len(files)) for target in missing: logging.error('Missing %s', target) for target in empty: logging.error('Empty %s', target)","title":"do_validate_html()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.get_commands","text":"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). Source code in beancount/scripts/doctor.py def get_commands(): \"\"\"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). \"\"\" commands = [] for attr_name, attr_value in globals().items(): match = re.match('do_(.*)', attr_name) if match: commands.append((match.group(1), misc_utils.first_paragraph(attr_value.__doc__))) return commands","title":"get_commands()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example","text":"Generate a decently-sized example history, based on some rules. This script is used to generate some meaningful input to Beancount, input that looks as realistic as possible for a moderately complex mock individual. This can also be used as an input generator for a stress test for performance evaluation.","title":"example"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.check_non_negative","text":"Check that the balance of the given account never goes negative. Parameters: entries \u2013 A list of directives. account \u2013 An account string, the account to check the balance for. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 if the balance goes negative. Source code in beancount/scripts/example.py def check_non_negative(entries, account, currency): \"\"\"Check that the balance of the given account never goes negative. Args: entries: A list of directives. account: An account string, the account to check the balance for. currency: A string, the currency to check minimums for. Raises: AssertionError: if the balance goes negative. \"\"\" previous_date = None for txn_posting, balances in postings_for(data.sorted(entries), [account], before=True): balance = balances[account] date = txn_posting.txn.date if date != previous_date: assert all(pos.units.number >= ZERO for pos in balance.get_positions()), ( \"Negative balance: {} at: {}\".format(balance, txn_posting.txn.date)) previous_date = date","title":"check_non_negative()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.compute_trip_dates","text":"Generate dates at reasonable intervals for trips during the given time period. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of dates for the trips within the period. Source code in beancount/scripts/example.py def compute_trip_dates(date_begin, date_end): \"\"\"Generate dates at reasonable intervals for trips during the given time period. Args: date_begin: The start date. date_end: The end date. Yields: Pairs of dates for the trips within the period. \"\"\" # Min and max number of days remaining at home. days_at_home = (4*30, 13*30) # Length of trip. days_trip = (8, 22) # Number of days to ensure no trip at the beginning and the end. days_buffer = 21 date_begin += datetime.timedelta(days=days_buffer) date_end -= datetime.timedelta(days=days_buffer) date = date_begin while 1: duration_at_home = datetime.timedelta(days=random.randint(*days_at_home)) duration_trip = datetime.timedelta(days=random.randint(*days_trip)) date_trip_begin = date + duration_at_home date_trip_end = date_trip_begin + duration_trip if date_trip_end >= date_end: break yield (date_trip_begin, date_trip_end) date = date_trip_end","title":"compute_trip_dates()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.contextualize_file","text":"Replace generic strings in the generated file with realistic strings. Parameters: contents \u2013 A string, the generic file contents. Returns: A string, the contextualized version. Source code in beancount/scripts/example.py def contextualize_file(contents, employer): \"\"\"Replace generic strings in the generated file with realistic strings. Args: contents: A string, the generic file contents. Returns: A string, the contextualized version. \"\"\" replacements = { 'CC': 'US', 'Bank1': 'BofA', 'Bank1_Institution': 'Bank of America', 'Bank1_Address': '123 America Street, LargeTown, USA', 'Bank1_Phone': '+1.012.345.6789', 'CreditCard1': 'Chase:Slate', 'CreditCard2': 'Amex:BlueCash', 'Employer1': employer, 'Retirement': 'Vanguard', 'Retirement_Institution': 'Vanguard Group', 'Retirement_Address': \"P.O. Box 1110, Valley Forge, PA 19482-1110\", 'Retirement_Phone': \"+1.800.523.1188\", 'Investment': 'ETrade', # Commodities 'CCY': 'USD', 'VACHR': 'VACHR', 'DEFCCY': 'IRAUSD', 'MFUND1': 'VBMPX', 'MFUND2': 'RGAGX', 'STK1': 'ITOT', 'STK2': 'VEA', 'STK3': 'VHT', 'STK4': 'GLD', } new_contents = replace(contents, replacements) return new_contents, replacements","title":"contextualize_file()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.date_iter","text":"Generate a sequence of dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_iter(date_begin, date_end): \"\"\"Generate a sequence of dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date. \"\"\" assert date_begin <= date_end date = date_begin one_day = datetime.timedelta(days=1) while date < date_end: date += one_day yield date","title":"date_iter()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.date_random_seq","text":"Generate a sequence of dates with some random increase in days. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. days_min \u2013 The minimum number of days to advance on each iteration. days_max \u2013 The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_random_seq(date_begin, date_end, days_min, days_max): \"\"\"Generate a sequence of dates with some random increase in days. Args: date_begin: The start date. date_end: The end date. days_min: The minimum number of days to advance on each iteration. days_max: The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. \"\"\" assert days_min > 0 assert days_min <= days_max date = date_begin while date < date_end: nb_days_forward = random.randint(days_min, days_max) date += datetime.timedelta(days=nb_days_forward) if date >= date_end: break yield date","title":"date_random_seq()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.delay_dates","text":"Delay the dates from the given iterator by some uniformly drawn number of days. Parameters: date_iter \u2013 An iterator of datetime.date instances. delay_days_min \u2013 The minimum amount of advance days for the transaction. delay_days_max \u2013 The maximum amount of advance days for the transaction. Yields: datetime.date instances. Source code in beancount/scripts/example.py def delay_dates(date_iter, delay_days_min, delay_days_max): \"\"\"Delay the dates from the given iterator by some uniformly drawn number of days. Args: date_iter: An iterator of datetime.date instances. delay_days_min: The minimum amount of advance days for the transaction. delay_days_max: The maximum amount of advance days for the transaction. Yields: datetime.date instances. \"\"\" dates = list(date_iter) last_date = dates[-1] last_date = last_date.date() if isinstance(last_date, datetime.datetime) else last_date for dtime in dates: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime date += datetime.timedelta(days=random.randint(delay_days_min, delay_days_max)) if date >= last_date: break yield date","title":"delay_dates()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_balance_checks","text":"Generate balance check entries to the given frequency. Parameters: entries \u2013 A list of directives that contain all the transactions for the accounts. account \u2013 The name of the account for which to generate. date_iter \u2013 Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. Source code in beancount/scripts/example.py def generate_balance_checks(entries, account, date_iter): \"\"\"Generate balance check entries to the given frequency. Args: entries: A list of directives that contain all the transactions for the accounts. account: The name of the account for which to generate. date_iter: Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. \"\"\" balance_checks = [] date_iter = iter(date_iter) next_date = next(date_iter) with misc_utils.swallow(StopIteration): for txn_posting, balance in postings_for(entries, [account], before=True): while txn_posting.txn.date >= next_date: amount = balance[account].get_currency_units('CCY').number balance_checks.extend(parse(\"\"\" {next_date} balance {account} {amount} CCY \"\"\", **locals())) next_date = next(date_iter) return balance_checks","title":"generate_balance_checks()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_banking","text":"Generate a checking account opening. Parameters: entries \u2013 A list of entries which affect this account. date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. amount_initial \u2013 A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking(entries, date_begin, date_end, amount_initial): \"\"\"Generate a checking account opening. Args: entries: A list of entries which affect this account. date_begin: A date instance, the beginning date. date_end: A date instance, the end date. amount_initial: A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. \"\"\" amount_initial_neg = -amount_initial new_entries = parse(\"\"\" {date_begin} open Assets:CC:Bank1 institution: \"Bank1_Institution\" address: \"Bank1_Address\" phone: \"Bank1_Phone\" {date_begin} open Assets:CC:Bank1:Checking CCY account: \"00234-48574897\" ;; {date_begin} open Assets:CC:Bank1:Savings CCY {date_begin} * \"Opening Balance for checking account\" Assets:CC:Bank1:Checking {amount_initial} CCY Equity:Opening-Balances {amount_initial_neg} CCY \"\"\", **locals()) date_balance = date_begin + datetime.timedelta(days=1) account = 'Assets:CC:Bank1:Checking' for txn_posting, balances in postings_for(data.sorted(entries + new_entries), [account], before=True): if txn_posting.txn.date >= date_balance: break amount_balance = balances[account].get_currency_units('CCY').number bal_entries = parse(\"\"\" {date_balance} balance Assets:CC:Bank1:Checking {amount_balance} CCY \"\"\", **locals()) return new_entries + bal_entries","title":"generate_banking()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_banking_expenses","text":"Generate expenses paid out of a checking account, typically living expenses. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. account \u2013 The checking account to generate expenses to. rent_amount \u2013 The amount of rent. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking_expenses(date_begin, date_end, account, rent_amount): \"\"\"Generate expenses paid out of a checking account, typically living expenses. Args: date_begin: The start date. date_end: The end date. account: The checking account to generate expenses to. rent_amount: The amount of rent. Returns: A list of directives. \"\"\" fee_expenses = generate_periodic_expenses( rrule.rrule(rrule.MONTHLY, bymonthday=4, dtstart=date_begin, until=date_end), \"BANK FEES\", \"Monthly bank fee\", account, 'Expenses:Financial:Fees', lambda: D('4.00')) rent_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 2, 5), \"RiverBank Properties\", \"Paying the rent\", account, 'Expenses:Home:Rent', lambda: random.normalvariate(float(rent_amount), 0)) electricity_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 7, 8), \"EDISON POWER\", \"\", account, 'Expenses:Home:Electricity', lambda: random.normalvariate(65, 0)) internet_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 20, 22), \"Wine-Tarner Cable\", \"\", account, 'Expenses:Home:Internet', lambda: random.normalvariate(80, 0.10)) phone_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 17, 19), \"Verizon Wireless\", \"\", account, 'Expenses:Home:Phone', lambda: random.normalvariate(60, 10)) return data.sorted(fee_expenses + rent_expenses + electricity_expenses + internet_expenses + phone_expenses)","title":"generate_banking_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_clearing_entries","text":"Generate entries to clear the value of an account. Parameters: date_iter \u2013 An iterator of datetime.date instances. payee \u2013 A string, the payee name to use on the transactions. narration \u2013 A string, the narration to use on the transactions. entries \u2013 A list of entries. account_clear \u2013 The account to clear. account_from \u2013 The source account to clear 'account_clear' from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from): \"\"\"Generate entries to clear the value of an account. Args: date_iter: An iterator of datetime.date instances. payee: A string, the payee name to use on the transactions. narration: A string, the narration to use on the transactions. entries: A list of entries. account_clear: The account to clear. account_from: The source account to clear 'account_clear' from. Returns: A list of directives. \"\"\" # The next date we're looking for. next_date = next(iter(date_iter)) # Iterate over all the postings of the account to clear. new_entries = [] for txn_posting, balances in postings_for(entries, [account_clear]): balance_clear = balances[account_clear] # Check if we need to clear. if next_date <= txn_posting.txn.date: pos_amount = balance_clear.get_currency_units('CCY') neg_amount = -pos_amount new_entries.extend(parse(\"\"\" {next_date} * \"{payee}\" \"{narration}\" {account_clear} {neg_amount.number:.2f} CCY {account_from} {pos_amount.number:.2f} CCY \"\"\", **locals())) balance_clear.add_amount(neg_amount) # Advance to the next date we're looking for. try: next_date = next(iter(date_iter)) except StopIteration: break return new_entries","title":"generate_clearing_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_commodity_entries","text":"Create a list of Commodity entries for all the currencies we're using. Parameters: date_birth \u2013 A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. Source code in beancount/scripts/example.py def generate_commodity_entries(date_birth): \"\"\"Create a list of Commodity entries for all the currencies we're using. Args: date_birth: A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. \"\"\" return parse(\"\"\" 1792-01-01 commodity USD name: \"US Dollar\" export: \"CASH\" {date_birth} commodity VACHR name: \"Employer Vacation Hours\" export: \"IGNORE\" {date_birth} commodity IRAUSD name: \"US 401k and IRA Contributions\" export: \"IGNORE\" 2009-05-01 commodity RGAGX name: \"American Funds The Growth Fund of America Class R-6\" export: \"MUTF:RGAGX\" price: \"USD:google/MUTF:RGAGX\" 1995-09-18 commodity VBMPX name: \"Vanguard Total Bond Market Index Fund Institutional Plus Shares\" export: \"MUTF:VBMPX\" price: \"USD:google/MUTF:VBMPX\" 2004-01-20 commodity ITOT name: \"iShares Core S&P Total U.S. Stock Market ETF\" export: \"NYSEARCA:ITOT\" price: \"USD:google/NYSEARCA:ITOT\" 2007-07-20 commodity VEA name: \"Vanguard FTSE Developed Markets ETF\" export: \"NYSEARCA:VEA\" price: \"USD:google/NYSEARCA:VEA\" 2004-01-26 commodity VHT name: \"Vanguard Health Care ETF\" export: \"NYSEARCA:VHT\" price: \"USD:google/NYSEARCA:VHT\" 2004-11-01 commodity GLD name: \"SPDR Gold Trust (ETF)\" export: \"NYSEARCA:GLD\" price: \"USD:google/NYSEARCA:GLD\" 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" \"\"\", **locals())","title":"generate_commodity_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_employment_income","text":"Generate bi-weekly entries for payroll salary income. Parameters: employer_name \u2013 A string, the human-readable name of the employer. employer_address \u2013 A string, the address of the employer. annual_salary \u2013 A Decimal, the annual salary of the employee. account_deposit \u2013 An account string, the account to deposit the salary to. account_retirement \u2013 An account string, the account to deposit retirement contributions to. date_begin \u2013 The start date. date_end \u2013 The end date. Returns: A list of directives, including open directives for the account. Source code in beancount/scripts/example.py def generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end): \"\"\"Generate bi-weekly entries for payroll salary income. Args: employer_name: A string, the human-readable name of the employer. employer_address: A string, the address of the employer. annual_salary: A Decimal, the annual salary of the employee. account_deposit: An account string, the account to deposit the salary to. account_retirement: An account string, the account to deposit retirement contributions to. date_begin: The start date. date_end: The end date. Returns: A list of directives, including open directives for the account. \"\"\" preamble = parse(\"\"\" {date_begin} event \"employer\" \"{employer_name}, {employer_address}\" {date_begin} open Income:CC:Employer1:Salary CCY ;{date_begin} open Income:CC:Employer1:AnnualBonus CCY {date_begin} open Income:CC:Employer1:GroupTermLife CCY {date_begin} open Income:CC:Employer1:Vacation VACHR {date_begin} open Assets:CC:Employer1:Vacation VACHR {date_begin} open Expenses:Vacation VACHR {date_begin} open Expenses:Health:Life:GroupTermLife {date_begin} open Expenses:Health:Medical:Insurance {date_begin} open Expenses:Health:Dental:Insurance {date_begin} open Expenses:Health:Vision:Insurance ;{date_begin} open Expenses:Vacation:Employer \"\"\", **locals()) date_prev = None contrib_retirement = ZERO contrib_socsec = ZERO biweekly_pay = annual_salary / 26 gross = biweekly_pay medicare = gross * D('0.0231') federal = gross * D('0.2303') state = gross * D('0.0791') city = gross * D('0.0379') sdi = D('1.12') lifeinsurance = D('24.32') dental = D('2.90') medical = D('27.38') vision = D('42.30') fixed = (medicare + federal + state + city + sdi + dental + medical + vision) # Calculate vacation hours per-pay. with decimal.localcontext() as ctx: ctx.prec = 4 vacation_hrs = (ANNUAL_VACATION_DAYS * D('8')) / D('26') transactions = [] for dtime in misc_utils.skipiter( rrule.rrule(rrule.WEEKLY, byweekday=rrule.TH, dtstart=date_begin, until=date_end), 2): date = dtime.date() year = date.year if not date_prev or date_prev.year != date.year: contrib_retirement = RETIREMENT_LIMITS.get(date.year, RETIREMENT_LIMITS[None]) contrib_socsec = D('7000') date_prev = date retirement_uncapped = math.ceil((gross * D('0.25')) / 100) * 100 retirement = min(contrib_retirement, retirement_uncapped) contrib_retirement -= retirement socsec_uncapped = gross * D('0.0610') socsec = min(contrib_socsec, socsec_uncapped) contrib_socsec -= socsec with decimal.localcontext() as ctx: ctx.prec = 6 deposit = (gross - retirement - fixed - socsec) retirement_neg = -retirement gross_neg = -gross lifeinsurance_neg = -lifeinsurance vacation_hrs_neg = -vacation_hrs template = \"\"\" {date} * \"{employer_name}\" \"Payroll\" {account_deposit} {deposit:.2f} CCY {account_retirement} {retirement:.2f} CCY Assets:CC:Federal:PreTax401k {retirement_neg:.2f} DEFCCY Expenses:Taxes:Y{year}:CC:Federal:PreTax401k {retirement:.2f} DEFCCY Income:CC:Employer1:Salary {gross_neg:.2f} CCY Income:CC:Employer1:GroupTermLife {lifeinsurance_neg:.2f} CCY Expenses:Health:Life:GroupTermLife {lifeinsurance:.2f} CCY Expenses:Health:Dental:Insurance {dental} CCY Expenses:Health:Medical:Insurance {medical} CCY Expenses:Health:Vision:Insurance {vision} CCY Expenses:Taxes:Y{year}:CC:Medicare {medicare:.2f} CCY Expenses:Taxes:Y{year}:CC:Federal {federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {state:.2f} CCY Expenses:Taxes:Y{year}:CC:CityNYC {city:.2f} CCY Expenses:Taxes:Y{year}:CC:SDI {sdi:.2f} CCY Expenses:Taxes:Y{year}:CC:SocSec {socsec:.2f} CCY Assets:CC:Employer1:Vacation {vacation_hrs:.2f} VACHR Income:CC:Employer1:Vacation {vacation_hrs_neg:.2f} VACHR \"\"\" if retirement == ZERO: # Remove retirement lines. template = '\\n'.join(line for line in template.splitlines() if not re.search(r'\\bretirement\\b', line)) transactions.extend(parse(template, **locals())) return preamble + transactions","title":"generate_employment_income()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_expense_accounts","text":"Generate directives for expense accounts. Parameters: date_birth \u2013 Birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_expense_accounts(date_birth): \"\"\"Generate directives for expense accounts. Args: date_birth: Birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" {date_birth} open Expenses:Food:Groceries {date_birth} open Expenses:Food:Restaurant {date_birth} open Expenses:Food:Coffee {date_birth} open Expenses:Food:Alcohol {date_birth} open Expenses:Transport:Tram {date_birth} open Expenses:Home:Rent {date_birth} open Expenses:Home:Electricity {date_birth} open Expenses:Home:Internet {date_birth} open Expenses:Home:Phone {date_birth} open Expenses:Financial:Fees {date_birth} open Expenses:Financial:Commissions \"\"\", **locals())","title":"generate_expense_accounts()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_open_entries","text":"Generate a list of Open entries for the given accounts: Parameters: date \u2013 A datetime.date instance for the open entries. accounts \u2013 A list of account strings. currency \u2013 An optional currency constraint. Returns: A list of Open directives. Source code in beancount/scripts/example.py def generate_open_entries(date, accounts, currency=None): \"\"\"Generate a list of Open entries for the given accounts: Args: date: A datetime.date instance for the open entries. accounts: A list of account strings. currency: An optional currency constraint. Returns: A list of Open directives. \"\"\" assert isinstance(accounts, (list, tuple)) return parse(''.join( '{date} open {account} {currency}\\n'.format(date=date, account=account, currency=currency or '') for account in accounts))","title":"generate_open_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_outgoing_transfers","text":"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Parameters: entries \u2013 A list of existing entries that affect this account so far. The generated entries will also affect this account. account \u2013 An account string, the account to monitor. account_out \u2013 An account string, the savings account to make transfers to. transfer_minimum \u2013 The minimum amount of funds to always leave in this account after a transfer. transfer_threshold \u2013 The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment \u2013 A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. Source code in beancount/scripts/example.py def generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment): \"\"\"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Args: entries: A list of existing entries that affect this account so far. The generated entries will also affect this account. account: An account string, the account to monitor. account_out: An account string, the savings account to make transfers to. transfer_minimum: The minimum amount of funds to always leave in this account after a transfer. transfer_threshold: The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment: A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. \"\"\" last_date = entries[-1].date # Reverse the balance amounts taking into account the minimum balance for # all time in the future. amounts = [(balances[account].get_currency_units('CCY').number, txn_posting) for txn_posting, balances in postings_for(entries, [account])] reversed_amounts = [] last_amount, _ = amounts[-1] for current_amount, _ in reversed(amounts): if current_amount < last_amount: reversed_amounts.append(current_amount) last_amount = current_amount else: reversed_amounts.append(last_amount) capped_amounts = reversed(reversed_amounts) # Create transfers outward where the future allows it. new_entries = [] offset_amount = ZERO for current_amount, (_, txn_posting) in zip(capped_amounts, amounts): if txn_posting.txn.date >= last_date: break adjusted_amount = current_amount - offset_amount if adjusted_amount > (transfer_minimum + transfer_threshold): amount_transfer = round_to(adjusted_amount - transfer_minimum, transfer_increment) date = txn_posting.txn.date + datetime.timedelta(days=1) amount_transfer_neg = -amount_transfer new_entries.extend(parse(\"\"\" {date} * \"Transfering accumulated savings to other account\" {account} {amount_transfer_neg:2f} CCY {account_out} {amount_transfer:2f} CCY \"\"\", **locals())) offset_amount += amount_transfer return new_entries","title":"generate_outgoing_transfers()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_periodic_expenses","text":"Generate periodic expense transactions. Parameters: date_iter \u2013 An iterator for dates or datetimes. payee \u2013 A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration \u2013 A string, the narration to use on the transactions. account_from \u2013 An account string the debited account. account_to \u2013 An account string the credited account. amount_generator \u2013 A callable object to generate variates. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator): \"\"\"Generate periodic expense transactions. Args: date_iter: An iterator for dates or datetimes. payee: A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration: A string, the narration to use on the transactions. account_from: An account string the debited account. account_to: An account string the credited account. amount_generator: A callable object to generate variates. Returns: A list of directives. \"\"\" new_entries = [] for dtime in date_iter: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime amount = D(amount_generator()) txn_payee = (payee if isinstance(payee, str) else random.choice(payee)) txn_narration = (narration if isinstance(narration, str) else random.choice(narration)) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{txn_payee}\" \"{txn_narration}\" {account_from} {amount_neg:.2f} CCY {account_to} {amount:.2f} CCY \"\"\", **locals())) return new_entries","title":"generate_periodic_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_prices","text":"Generate weekly or monthly price entries for the given currencies. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. currencies \u2013 A list of currency strings to generate prices for. cost_currency \u2013 A string, the cost currency. Returns: A list of Price directives. Source code in beancount/scripts/example.py def generate_prices(date_begin, date_end, currencies, cost_currency): \"\"\"Generate weekly or monthly price entries for the given currencies. Args: date_begin: The start date. date_end: The end date. currencies: A list of currency strings to generate prices for. cost_currency: A string, the cost currency. Returns: A list of Price directives. \"\"\" digits = D('0.01') entries = [] counter = itertools.count() for currency in currencies: start_price = random.uniform(30, 200) growth = random.uniform(0.02, 0.13) # %/year mu = growth * (7 / 365) sigma = random.uniform(0.005, 0.02) # Vol for dtime, price_float in zip(rrule.rrule(rrule.WEEKLY, byweekday=rrule.FR, dtstart=date_begin, until=date_end), price_series(start_price, mu, sigma)): price = D(price_float).quantize(digits) meta = data.new_metadata(generate_prices.__name__, next(counter)) entry = data.Price(meta, dtime.date(), currency, amount.Amount(price, cost_currency)) entries.append(entry) return entries","title":"generate_prices()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_regular_credit_expenses","text":"Generate expenses paid out of a credit card account, including payments to the credit card. Parameters: date_birth \u2013 The user's birth date. date_begin \u2013 The start date. date_end \u2013 The end date. account_credit \u2013 The credit card account to generate expenses against. account_checking \u2013 The checking account to generate payments from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking): \"\"\"Generate expenses paid out of a credit card account, including payments to the credit card. Args: date_birth: The user's birth date. date_begin: The start date. date_end: The end date. account_credit: The credit card account to generate expenses against. account_checking: The checking account to generate payments from. Returns: A list of directives. \"\"\" restaurant_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 1, 5), RESTAURANT_NAMES, RESTAURANT_NARRATIONS, account_credit, 'Expenses:Food:Restaurant', lambda: min(random.lognormvariate(math.log(30), math.log(1.5)), random.randint(200, 220))) groceries_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 5, 20), GROCERIES_NAMES, \"Buying groceries\", account_credit, 'Expenses:Food:Groceries', lambda: min(random.lognormvariate(math.log(80), math.log(1.3)), random.randint(250, 300))) subway_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 27, 33), \"Metro Transport Authority\", \"Tram tickets\", account_credit, 'Expenses:Transport:Tram', lambda: D('120.00')) credit_expenses = data.sorted(restaurant_expenses + groceries_expenses + subway_expenses) # Entries to open accounts. credit_preamble = generate_open_entries(date_birth, [account_credit], 'CCY') return data.sorted(credit_preamble + credit_expenses)","title":"generate_regular_credit_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_retirement_employer_match","text":"Generate employer matching contributions into a retirement account. Parameters: entries \u2013 A list of directives that cover the retirement account. account_invest \u2013 The name of the retirement cash account. account_income \u2013 The name of the income account. Returns: A list of new entries generated for employer contributions. Source code in beancount/scripts/example.py def generate_retirement_employer_match(entries, account_invest, account_income): \"\"\"Generate employer matching contributions into a retirement account. Args: entries: A list of directives that cover the retirement account. account_invest: The name of the retirement cash account. account_income: The name of the income account. Returns: A list of new entries generated for employer contributions. \"\"\" match_frac = D('0.50') new_entries = parse(\"\"\" {date} open {account_income} CCY \"\"\", date=entries[0].date, account_income=account_income) for txn_posting, balances in postings_for(entries, [account_invest]): amount = txn_posting.posting.units.number * match_frac amount_neg = -amount date = txn_posting.txn.date + ONE_DAY new_entries.extend(parse(\"\"\" {date} * \"Employer match for contribution\" {account_invest} {amount:.2f} CCY {account_income} {amount_neg:.2f} CCY \"\"\", **locals())) return new_entries","title":"generate_retirement_employer_match()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_retirement_investments","text":"Invest money deposited to the given retirement account. Parameters: entries \u2013 A list of directives account \u2013 The root account for all retirement investment sub-accounts. commodities_items \u2013 A list of (commodity, fraction to be invested in) items. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. Source code in beancount/scripts/example.py def generate_retirement_investments(entries, account, commodities_items, price_map): \"\"\"Invest money deposited to the given retirement account. Args: entries: A list of directives account: The root account for all retirement investment sub-accounts. commodities_items: A list of (commodity, fraction to be invested in) items. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. \"\"\" open_entries = [] account_cash = join(account, 'Cash') date_origin = entries[0].date open_entries.extend(parse(\"\"\" {date_origin} open {account} CCY institution: \"Retirement_Institution\" address: \"Retirement_Address\" phone: \"Retirement_Phone\" {date_origin} open {account_cash} CCY number: \"882882\" \"\"\", **locals())) for currency, _ in commodities_items: open_entries.extend(parse(\"\"\" {date_origin} open {account}:{currency} {currency} number: \"882882\" \"\"\", **locals())) new_entries = [] for txn_posting, balances in postings_for(entries, [account_cash]): balance = balances[account_cash] amount_to_invest = balance.get_currency_units('CCY').number # Find the date the following Monday, the date to invest. txn_date = txn_posting.txn.date while txn_date.weekday() != calendar.MONDAY: txn_date += ONE_DAY amount_invested = ZERO for commodity, fraction in commodities_items: amount_fraction = amount_to_invest * D(fraction) # Find the price at that date. _, price = prices.get_price(price_map, (commodity, 'CCY'), txn_date) units = (amount_fraction / price).quantize(D('0.001')) amount_cash = (units * price).quantize(D('0.01')) amount_cash_neg = -amount_cash new_entries.extend(parse(\"\"\" {txn_date} * \"Investing {fraction:.0%} of cash in {commodity}\" {account}:{commodity} {units:.3f} {commodity} {{{price:.2f} CCY}} {account}:Cash {amount_cash_neg:.2f} CCY \"\"\", **locals())) balance.add_amount(amount.Amount(-amount_cash, 'CCY')) return data.sorted(open_entries + new_entries)","title":"generate_retirement_investments()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_tax_accounts","text":"Generate accounts and contribution directives for a particular tax year. Parameters: year \u2013 An integer, the year we're to generate this for. date_max \u2013 The maximum date to produce an entry for. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_accounts(year, date_max): \"\"\"Generate accounts and contribution directives for a particular tax year. Args: year: An integer, the year we're to generate this for. date_max: The maximum date to produce an entry for. Returns: A list of directives. \"\"\" date_year = datetime.date(year, 1, 1) date_filing = (datetime.date(year + 1, 3, 20) + datetime.timedelta(days=random.randint(0, 5))) date_federal = (date_filing + datetime.timedelta(days=random.randint(0, 4))) date_state = (date_filing + datetime.timedelta(days=random.randint(0, 4))) quantum = D('0.01') amount_federal = D(max(random.normalvariate(500, 120), 12)).quantize(quantum) amount_federal_neg = -amount_federal amount_state = D(max(random.normalvariate(300, 100), 10)).quantize(quantum) amount_state_neg = -amount_state amount_payable = -(amount_federal + amount_state) amount_limit = RETIREMENT_LIMITS.get(year, RETIREMENT_LIMITS[None]) amount_limit_neg = -amount_limit entries = parse(\"\"\" ;; Open tax accounts for that year. {date_year} open Expenses:Taxes:Y{year}:CC:Federal:PreTax401k DEFCCY {date_year} open Expenses:Taxes:Y{year}:CC:Medicare CCY {date_year} open Expenses:Taxes:Y{year}:CC:Federal CCY {date_year} open Expenses:Taxes:Y{year}:CC:CityNYC CCY {date_year} open Expenses:Taxes:Y{year}:CC:SDI CCY {date_year} open Expenses:Taxes:Y{year}:CC:State CCY {date_year} open Expenses:Taxes:Y{year}:CC:SocSec CCY ;; Check that the tax amounts have been fully used. {date_year} balance Assets:CC:Federal:PreTax401k 0 DEFCCY {date_year} * \"Allowed contributions for one year\" Income:CC:Federal:PreTax401k {amount_limit_neg} DEFCCY Assets:CC:Federal:PreTax401k {amount_limit} DEFCCY {date_filing} * \"Filing taxes for {year}\" Expenses:Taxes:Y{year}:CC:Federal {amount_federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {amount_state:.2f} CCY Liabilities:AccountsPayable {amount_payable:.2f} CCY {date_federal} * \"FEDERAL TAXPYMT\" Assets:CC:Bank1:Checking {amount_federal_neg:.2f} CCY Liabilities:AccountsPayable {amount_federal:.2f} CCY {date_state} * \"STATE TAX & FINANC PYMT\" Assets:CC:Bank1:Checking {amount_state_neg:.2f} CCY Liabilities:AccountsPayable {amount_state:.2f} CCY \"\"\", **locals()) return [entry for entry in entries if entry.date < date_max]","title":"generate_tax_accounts()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_tax_preamble","text":"Generate tax declarations not specific to any particular year. Parameters: date_birth \u2013 A date instance, the birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_preamble(date_birth): \"\"\"Generate tax declarations not specific to any particular year. Args: date_birth: A date instance, the birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" ;; Tax accounts not specific to a year. {date_birth} open Income:CC:Federal:PreTax401k DEFCCY {date_birth} open Assets:CC:Federal:PreTax401k DEFCCY \"\"\", **locals())","title":"generate_tax_preamble()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_taxable_investment","text":"Generate opening directives and transactions for an investment account. Parameters: date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. entries \u2013 A list of entries that contains at least the transfers to the investment account's cash account. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). stocks \u2013 A list of strings, the list of commodities to invest in. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_taxable_investment(date_begin, date_end, entries, price_map, stocks): \"\"\"Generate opening directives and transactions for an investment account. Args: date_begin: A date instance, the beginning date. date_end: A date instance, the end date. entries: A list of entries that contains at least the transfers to the investment account's cash account. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). stocks: A list of strings, the list of commodities to invest in. Returns: A list of directives. \"\"\" account = 'Assets:CC:Investment' account_cash = join(account, 'Cash') account_gains = 'Income:CC:Investment:Gains' account_dividends = 'Income:CC:Investment:Dividends' accounts_stocks = ['Assets:CC:Investment:{}'.format(commodity) for commodity in stocks] open_entries = parse(\"\"\" {date_begin} open {account}:Cash CCY {date_begin} open {account_gains} CCY {date_begin} open {account_dividends} CCY \"\"\", **locals()) for stock in stocks: open_entries.extend(parse(\"\"\" {date_begin} open {account}:{stock} {stock} \"\"\", **locals())) # Figure out dates at which dividends should be distributed, near the end of # each quarter. days_to = datetime.timedelta(days=3*90-10) dividend_dates = [] for quarter_begin in iter_quarters(date_begin, date_end): end_of_quarter = quarter_begin + days_to if not (date_begin < end_of_quarter < date_end): continue dividend_dates.append(end_of_quarter) # Iterate over all the dates, but merging in the postings for the cash # account. min_amount = D('1000.00') round_amount = D('100.00') commission = D('8.95') round_units = D('1') frac_invest = D('1.00') frac_dividend = D('0.004') p_daily_buy = 1./15 # days p_daily_sell = 1./90 # days stocks_inventory = inventory.Inventory() new_entries = [] dividend_date_iter = iter(dividend_dates) next_dividend_date = next(dividend_date_iter) for date, balances in iter_dates_with_balance(date_begin, date_end, entries, [account_cash]): # Check if we should insert a dividend. Note that we could not factor # this out because we want to explicitly reinvest the cash dividends and # we also want the dividends to be proportional to the amount of # invested stock, so one feeds on the other and vice-versa. if next_dividend_date and date > next_dividend_date: # Compute the total balances for the stock accounts in order to # create a realistic dividend. total = inventory.Inventory() for account_stock in accounts_stocks: total.add_inventory(balances[account_stock]) # Create an entry offering dividends of 1% of the portfolio. portfolio_cost = total.reduce(convert.get_cost).get_currency_units('CCY').number amount_cash = (frac_dividend * portfolio_cost).quantize(D('0.01')) amount_cash_neg = -amount_cash dividend = parse(\"\"\" {next_dividend_date} * \"Dividends on portfolio\" {account}:Cash {amount_cash:.2f} CCY {account_dividends} {amount_cash_neg:.2f} CCY \"\"\", **locals())[0] new_entries.append(dividend) # Advance the next dividend date. try: next_dividend_date = next(dividend_date_iter) except StopIteration: next_dividend_date = None # If the balance is high, buy with high probability. balance = balances[account_cash] total_cash = balance.get_currency_units('CCY').number assert total_cash >= ZERO, ('Cash balance is negative: {}'.format(total_cash)) invest_cash = total_cash * frac_invest - commission if invest_cash > min_amount: if random.random() < p_daily_buy: commodities = random.sample(stocks, random.randint(1, len(stocks))) lot_amount = round_to(invest_cash / len(commodities), round_amount) invested_amount = ZERO for stock in commodities: # Find the price at that date. _, price = prices.get_price(price_map, (stock, 'CCY'), date) units = round_to((lot_amount / price), round_units) if units <= ZERO: continue amount_cash = -(units * price + commission) # logging.info('Buying %s %s @ %s CCY = %s CCY', # units, stock, price, units * price) buy = parse(\"\"\" {date} * \"Buy shares of {stock}\" {account}:Cash {amount_cash:.2f} CCY {account}:{stock} {units:.0f} {stock} {{{price:.2f} CCY}} Expenses:Financial:Commissions {commission:.2f} CCY \"\"\", **locals())[0] new_entries.append(buy) account_stock = ':'.join([account, stock]) balances[account_cash].add_position(buy.postings[0]) balances[account_stock].add_position(buy.postings[1]) stocks_inventory.add_position(buy.postings[1]) # Don't sell on days you buy. continue # Otherwise, sell with low probability. if not stocks_inventory.is_empty() and random.random() < p_daily_sell: # Choose the lot with the highest gain or highest loss. gains = [] for position in stocks_inventory.get_positions(): base_quote = (position.units.currency, position.cost.currency) _, price = prices.get_price(price_map, base_quote, date) if price == position.cost.number: continue # Skip lots without movement. market_value = position.units.number * price book_value = convert.get_cost(position).number gain = market_value - book_value gains.append((gain, market_value, price, position)) if not gains: continue # Sell either biggest winner or biggest loser. biggest = bool(random.random() < 0.5) lot_tuple = sorted(gains)[0 if biggest else -1] gain, market_value, price, sell_position = lot_tuple #logging.info('Selling {} for {}'.format(sell_position, market_value)) sell_position = -sell_position stock = sell_position.units.currency amount_cash = market_value - commission amount_gain = -gain sell = parse(\"\"\" {date} * \"Sell shares of {stock}\" {account}:{stock} {sell_position} @ {price:.2f} CCY {account}:Cash {amount_cash:.2f} CCY Expenses:Financial:Commissions {commission:.2f} CCY {account_gains} {amount_gain:.2f} CCY \"\"\", **locals())[0] new_entries.append(sell) balances[account_cash].add_position(sell.postings[1]) stocks_inventory.add_position(sell.postings[0]) continue return open_entries + new_entries","title":"generate_taxable_investment()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_trip_entries","text":"Generate more dense expenses for a trip. Parameters: date_begin \u2013 A datetime.date instance, the beginning of the trip. date_end \u2013 A datetime.date instance, the end of the trip. tag \u2013 A string, the name of the tag. config \u2013 A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city \u2013 A string, the capitalized name of the destination city. home_city \u2013 A string, the name of the home city. account_credit \u2013 A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. Source code in beancount/scripts/example.py def generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit): \"\"\"Generate more dense expenses for a trip. Args: date_begin: A datetime.date instance, the beginning of the trip. date_end: A datetime.date instance, the end of the trip. tag: A string, the name of the tag. config: A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city: A string, the capitalized name of the destination city. home_city: A string, the name of the home city. account_credit: A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. \"\"\" p_day_generate = 0.3 new_entries = [] for date in date_iter(date_begin, date_end): for payee, account_expense, (mu, sigma3) in config: if random.random() < p_day_generate: amount = random.normalvariate(mu, sigma3 / 3.) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{payee}\" \"\" #{tag} {account_credit} {amount_neg:.2f} CCY {account_expense} {amount:.2f} CCY \"\"\", **locals())) # Consume the vacation days. vacation_hrs = (date_end - date_begin).days * 8 # hrs/day new_entries.extend(parse(\"\"\" {date_end} * \"Consume vacation days\" Assets:CC:Employer1:Vacation -{vacation_hrs:.2f} VACHR Expenses:Vacation {vacation_hrs:.2f} VACHR \"\"\", **locals())) # Generate events for the trip. new_entries.extend(parse(\"\"\" {date_begin} event \"location\" \"{trip_city}\" {date_end} event \"location\" \"{home_city}\" \"\"\", **locals())) return new_entries","title":"generate_trip_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.get_minimum_balance","text":"Compute the minimum balance of the given account according to the entries history. Parameters: entries \u2013 A list of directives. account \u2013 An account string. currency \u2013 A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. Source code in beancount/scripts/example.py def get_minimum_balance(entries, account, currency): \"\"\"Compute the minimum balance of the given account according to the entries history. Args: entries: A list of directives. account: An account string. currency: A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. \"\"\" min_amount = ZERO for _, balances in postings_for(entries, [account]): balance = balances[account] current = balance.get_currency_units(currency).number if current < min_amount: min_amount = current return min_amount","title":"get_minimum_balance()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.iter_dates_with_balance","text":"Iterate over dates, including the balances of the postings iterator. Parameters: postings_iter \u2013 An iterator of postings as per postings_for(). date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. Source code in beancount/scripts/example.py def iter_dates_with_balance(date_begin, date_end, entries, accounts): \"\"\"Iterate over dates, including the balances of the postings iterator. Args: postings_iter: An iterator of postings as per postings_for(). date_begin: The start date. date_end: The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. \"\"\" balances = collections.defaultdict(inventory.Inventory) merged_txn_postings = iter(merge_postings(entries, accounts)) txn_posting = next(merged_txn_postings, None) for date in date_iter(date_begin, date_end): while txn_posting and txn_posting.txn.date == date: posting = txn_posting.posting balances[posting.account].add_position(posting) txn_posting = next(merged_txn_postings, None) yield date, balances","title":"iter_dates_with_balance()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.iter_quarters","text":"Iterate over all quarters between begin and end dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. Source code in beancount/scripts/example.py def iter_quarters(date_begin, date_end): \"\"\"Iterate over all quarters between begin and end dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. \"\"\" quarter = (date_begin.year, (date_begin.month-1)//3) quarter_last = (date_end.year, (date_end.month-1)//3) assert quarter <= quarter_last while True: year, trimester = quarter yield datetime.date(year, trimester*3 + 1, 1) if quarter == quarter_last: break trimester = (trimester + 1) % 4 if trimester == 0: year += 1 quarter = (year, trimester)","title":"iter_quarters()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.merge_postings","text":"Merge all the postings from the given account names. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. Source code in beancount/scripts/example.py def merge_postings(entries, accounts): \"\"\"Merge all the postings from the given account names. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. \"\"\" real_root = realization.realize(entries) merged_postings = [] for account in accounts: real_account = realization.get(real_root, account) if real_account is None: continue merged_postings.extend(txn_posting for txn_posting in real_account.txn_postings if isinstance(txn_posting, data.TxnPosting)) merged_postings.sort(key=lambda txn_posting: txn_posting.txn.date) return merged_postings","title":"merge_postings()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.parse","text":"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Parameters: input_string \u2013 Beancount input text. **replacements \u2013 A dict of keywords to replace to their values. Returns: A list of directive objects. Source code in beancount/scripts/example.py def parse(input_string, **replacements): \"\"\"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Args: input_string: Beancount input text. **replacements: A dict of keywords to replace to their values. Returns: A list of directive objects. \"\"\" if replacements: class IgnoreFormatter(string.Formatter): def check_unused_args(self, used_args, args, kwargs): pass formatter = IgnoreFormatter() formatted_string = formatter.format(input_string, **replacements) else: formatted_string = input_string entries, errors, options_map = parser.parse_string(textwrap.dedent(formatted_string)) if errors: printer.print_errors(errors, file=sys.stderr) raise ValueError(\"Parsed text has errors\") # Interpolation. entries, unused_balance_errors = booking.book(entries, options_map) return data.sorted(entries)","title":"parse()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.postings_for","text":"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. before \u2013 A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts after applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. Source code in beancount/scripts/example.py def postings_for(entries, accounts, before=False): \"\"\"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. before: A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts _after_ applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. \"\"\" assert isinstance(accounts, list) merged_txn_postings = merge_postings(entries, accounts) balances = collections.defaultdict(inventory.Inventory) for txn_posting in merged_txn_postings: if before: yield txn_posting, balances posting = txn_posting.posting balances[posting.account].add_position(posting) if not before: yield txn_posting, balances","title":"postings_for()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.price_series","text":"Generate a price series based on a simple stochastic model. Parameters: start \u2013 The beginning value. mu \u2013 The per-step drift, in units of value. sigma \u2013 Volatility of the changes. Yields: Floats, at each step. Source code in beancount/scripts/example.py def price_series(start, mu, sigma): \"\"\"Generate a price series based on a simple stochastic model. Args: start: The beginning value. mu: The per-step drift, in units of value. sigma: Volatility of the changes. Yields: Floats, at each step. \"\"\" value = start while 1: yield value value += random.normalvariate(mu, sigma) * value","title":"price_series()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.replace","text":"Apply word-boundaried regular expression replacements to an indented string. Parameters: string \u2013 Some input template string. replacements \u2013 A dict of regexp to replacement value. strip \u2013 A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. Source code in beancount/scripts/example.py def replace(string, replacements, strip=False): \"\"\"Apply word-boundaried regular expression replacements to an indented string. Args: string: Some input template string. replacements: A dict of regexp to replacement value. strip: A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. \"\"\" output = textwrap.dedent(string) if strip: output = output.strip() for from_, to_ in replacements.items(): if not isinstance(to_, str) and not callable(to_): to_ = str(to_) output = re.sub(r'\\b{}\\b'.format(from_), to_, output) return output","title":"replace()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.validate_output","text":"Check that the output file validates. Parameters: contents \u2013 A string, the output file. positive_accounts \u2013 A list of strings, account names to check for non-negative balances. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 If the output does not validate. Source code in beancount/scripts/example.py def validate_output(contents, positive_accounts, currency): \"\"\"Check that the output file validates. Args: contents: A string, the output file. positive_accounts: A list of strings, account names to check for non-negative balances. currency: A string, the currency to check minimums for. Raises: AssertionError: If the output does not validate. \"\"\" loaded_entries, _, _ = loader.load_string( contents, log_errors=sys.stderr, extra_validations=validation.HARDCORE_VALIDATIONS) # Sanity checks: Check that the checking balance never goes below zero. for account in positive_accounts: check_non_negative(loaded_entries, account, currency)","title":"validate_output()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.write_example_file","text":"Generate the example file. Parameters: date_birth \u2013 A datetime.date instance, the birth date of our character. date_begin \u2013 A datetime.date instance, the beginning date at which to generate transactions. date_end \u2013 A datetime.date instance, the end date at which to generate transactions. reformat \u2013 A boolean, true if we should apply global reformatting to this file. file \u2013 A file object, where to write out the output. Source code in beancount/scripts/example.py def write_example_file(date_birth, date_begin, date_end, reformat, file): \"\"\"Generate the example file. Args: date_birth: A datetime.date instance, the birth date of our character. date_begin: A datetime.date instance, the beginning date at which to generate transactions. date_end: A datetime.date instance, the end date at which to generate transactions. reformat: A boolean, true if we should apply global reformatting to this file. file: A file object, where to write out the output. \"\"\" # The following code entirely writes out the output to generic names, such # as \"Employer1\", \"Bank1\", and \"CCY\" (for principal currency). Those names # are purposely chosen to be unique, and only near the very end do we make # renamings to more specific and realistic names. # Name of the checking account. account_opening = 'Equity:Opening-Balances' account_payable = 'Liabilities:AccountsPayable' account_checking = 'Assets:CC:Bank1:Checking' account_credit = 'Liabilities:CC:CreditCard1' account_retirement = 'Assets:CC:Retirement' account_investing = 'Assets:CC:Investment:Cash' # Commodities. commodity_entries = generate_commodity_entries(date_birth) # Estimate the rent. rent_amount = round_to(ANNUAL_SALARY / RENT_DIVISOR, RENT_INCREMENT) # Get a random employer. employer_name, employer_address = random.choice(EMPLOYERS) logging.info(\"Generating Salary Employment Income\") income_entries = generate_employment_income(employer_name, employer_address, ANNUAL_SALARY, account_checking, join(account_retirement, 'Cash'), date_begin, date_end) logging.info(\"Generating Expenses from Banking Accounts\") banking_expenses = generate_banking_expenses(date_begin, date_end, account_checking, rent_amount) logging.info(\"Generating Regular Expenses via Credit Card\") credit_regular_entries = generate_regular_credit_expenses( date_birth, date_begin, date_end, account_credit, account_checking) logging.info(\"Generating Credit Card Expenses for Trips\") trip_entries = [] destinations = sorted(TRIP_DESTINATIONS.items()) destinations.extend(destinations) random.shuffle(destinations) for (date_trip_begin, date_trip_end), (destination_name, config) in zip( compute_trip_dates(date_begin, date_end), destinations): # Compute a suitable tag. tag = 'trip-{}-{}'.format(destination_name.lower().replace(' ', '-'), date_trip_begin.year) #logging.info(\"%s -- %s %s\", tag, date_trip_begin, date_trip_end) # Remove regular entries during this trip. credit_regular_entries = [entry for entry in credit_regular_entries if not(date_trip_begin <= entry.date < date_trip_end)] # Generate entries for the trip. this_trip_entries = generate_trip_entries( date_trip_begin, date_trip_end, tag, config, destination_name.replace('-', ' ').title(), HOME_NAME, account_credit) trip_entries.extend(this_trip_entries) logging.info(\"Generating Credit Card Payment Entries\") credit_payments = generate_clearing_entries( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end, bymonthday=7), 0, 4), \"CreditCard1\", \"Paying off credit card\", credit_regular_entries, account_credit, account_checking) credit_entries = credit_regular_entries + trip_entries + credit_payments logging.info(\"Generating Tax Filings and Payments\") tax_preamble = generate_tax_preamble(date_birth) # Figure out all the years we need tax accounts for. years = set() for account_name in getters.get_accounts(income_entries): match = re.match(r'Expenses:Taxes:Y(\\d\\d\\d\\d)', account_name) if match: years.add(int(match.group(1))) taxes = [(year, generate_tax_accounts(year, date_end)) for year in sorted(years)] tax_entries = tax_preamble + functools.reduce(operator.add, (entries for _, entries in taxes)) logging.info(\"Generating Opening of Banking Accounts\") # Open banking accounts and gift the checking account with a balance that # will offset all the amounts to ensure a positive balance throughout its # lifetime. entries_for_banking = data.sorted(income_entries + banking_expenses + credit_entries + tax_entries) minimum = get_minimum_balance(entries_for_banking, account_checking, 'CCY') banking_entries = generate_banking(entries_for_banking, date_begin, date_end, max(-minimum, ZERO)) logging.info(\"Generating Transfers to Investment Account\") banking_transfers = generate_outgoing_transfers( data.sorted(income_entries + banking_entries + banking_expenses + credit_entries + tax_entries), account_checking, account_investing, transfer_minimum=D('200'), transfer_threshold=D('3000'), transfer_increment=D('500')) logging.info(\"Generating Prices\") # Generate price entries for investment currencies and create a price map to # use for later for generating investment transactions. funds_allocation = {'MFUND1': 0.40, 'MFUND2': 0.60} stocks = ['STK1', 'STK2', 'STK3', 'STK4'] price_entries = generate_prices(date_begin, date_end, sorted(funds_allocation.keys()) + stocks, 'CCY') price_map = prices.build_price_map(price_entries) logging.info(\"Generating Employer Match Contribution\") account_match = 'Income:US:Employer1:Match401k' retirement_match = generate_retirement_employer_match(income_entries, join(account_retirement, 'Cash'), account_match) logging.info(\"Generating Retirement Investments\") retirement_entries = generate_retirement_investments( income_entries + retirement_match, account_retirement, sorted(funds_allocation.items()), price_map) logging.info(\"Generating Taxes Investments\") investment_entries = generate_taxable_investment(date_begin, date_end, banking_transfers, price_map, stocks) logging.info(\"Generating Expense Accounts\") expense_accounts_entries = generate_expense_accounts(date_birth) logging.info(\"Generating Equity Accounts\") equity_entries = generate_open_entries(date_birth, [account_opening, account_payable]) logging.info(\"Generating Balance Checks\") credit_checks = generate_balance_checks(credit_entries, account_credit, date_random_seq(date_begin, date_end, 20, 30)) banking_checks = generate_balance_checks(data.sorted(income_entries + banking_entries + banking_expenses + banking_transfers + credit_entries + tax_entries), account_checking, date_random_seq(date_begin, date_end, 20, 30)) logging.info(\"Outputting and Formatting Entries\") dcontext = display_context.DisplayContext() default_int_digits = 8 for currency, precision in {'USD': 2, 'CAD': 2, 'VACHR':0, 'IRAUSD': 2, 'VBMPX': 3, 'RGAGX': 3, 'ITOT': 0, 'VEA': 0, 'VHT': 0, 'GLD': 0}.items(): int_digits = default_int_digits if precision > 0: int_digits += 1 + precision dcontext.update(D('{{:0{}.{}f}}'.format(int_digits, precision).format(0)), currency) output = io.StringIO() def output_section(title, entries): output.write('\\n\\n\\n{}\\n\\n'.format(title)) printer.print_entries(data.sorted(entries), dcontext, file=output) output.write(FILE_PREAMBLE.format(**locals())) output_section('* Commodities', commodity_entries) output_section('* Equity Accounts', equity_entries) output_section('* Banking', data.sorted(banking_entries + banking_expenses + banking_transfers + banking_checks)) output_section('* Credit-Cards', data.sorted(credit_entries + credit_checks)) output_section('* Taxable Investments', investment_entries) output_section('* Retirement Investments', data.sorted(retirement_entries + retirement_match)) output_section('* Sources of Income', income_entries) output_section('* Taxes', tax_preamble) for year, entries in taxes: output_section('** Tax Year {}'.format(year), entries) output_section('* Expenses', expense_accounts_entries) output_section('* Prices', price_entries) output_section('* Cash', []) logging.info(\"Contextualizing to Realistic Names\") contents, replacements = contextualize_file(output.getvalue(), employer_name) if reformat: contents = format.align_beancount(contents) logging.info(\"Writing contents\") file.write(contents) logging.info(\"Validating Results\") validate_output(contents, [replace(account, replacements) for account in [account_checking]], replace('CCY', replacements))","title":"write_example_file()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format","text":"Align a beancount/ledger input file's numbers. This reformats at beancount or ledger input file so that the amounts in the postings are all aligned to the same column. The currency should match. Note: this does not parse the Beancount ledger. It simply uses regular expressions and text manipulations to do its work.","title":"format"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.align_beancount","text":"Reformat Beancount input to align all the numbers at the same column. Parameters: contents \u2013 A string, Beancount input syntax to reformat. prefix_width \u2013 An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width \u2013 An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column \u2013 An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. Source code in beancount/scripts/format.py def align_beancount(contents, prefix_width=None, num_width=None, currency_column=None): \"\"\"Reformat Beancount input to align all the numbers at the same column. Args: contents: A string, Beancount input syntax to reformat. prefix_width: An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width: An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column: An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. \"\"\" # Find all lines that have a number in them and calculate the maximum length # of the stripped prefix and the number. match_pairs = [] for line in contents.splitlines(): match = re.match( r'([^\";]*?)\\s+([-+]?\\s*[\\d,]+(?:\\.\\d*)?)\\s+({}\\b.*)'.format(amount.CURRENCY_RE), line) if match: prefix, number, rest = match.groups() match_pairs.append((prefix, number, rest)) else: match_pairs.append((line, None, None)) # Normalize whitespace before lines that has some indent and an account # name. norm_match_pairs = normalize_indent_whitespace(match_pairs) if currency_column: output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: num_of_spaces = currency_column - len(prefix) - len(number) - 4 spaces = ' ' * num_of_spaces output.write(prefix + spaces + ' ' + number + ' ' + rest) output.write('\\n') return output.getvalue() # Compute the maximum widths. filtered_pairs = [(prefix, number) for prefix, number, _ in match_pairs if number is not None] if filtered_pairs: max_prefix_width = max(len(prefix) for prefix, _ in filtered_pairs) max_num_width = max(len(number) for _, number in filtered_pairs) else: max_prefix_width = 0 max_num_width = 0 # Use user-supplied overrides, if available if prefix_width: max_prefix_width = prefix_width if num_width: max_num_width = num_width # Create a format that will admit the maximum width of all prefixes equally. line_format = '{{:<{prefix_width}}} {{:>{num_width}}} {{}}'.format( prefix_width=max_prefix_width, num_width=max_num_width) # Process each line to an output buffer. output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: output.write(line_format.format(prefix.rstrip(), number, rest)) output.write('\\n') formatted_contents = output.getvalue() # Ensure that the file before and after have only whitespace differences. # This is a sanity check, to make really sure we never change anything but whitespace, # so it's safe. # open('/tmp/before', 'w').write(re.sub(r'[ \\t]+', ' ', contents)) # open('/tmp/after', 'w').write(re.sub(r'[ \\t]+', ' ', formatted_contents)) old_stripped = re.sub(r'[ \\t\\n]+', ' ', contents.rstrip()) new_stripped = re.sub(r'[ \\t\\n]+', ' ', formatted_contents.rstrip()) assert (old_stripped == new_stripped), (old_stripped, new_stripped) return formatted_contents","title":"align_beancount()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.compute_most_frequent","text":"Compute the frequencies of the given elements and return the most frequent. Parameters: iterable \u2013 A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. Source code in beancount/scripts/format.py def compute_most_frequent(iterable): \"\"\"Compute the frequencies of the given elements and return the most frequent. Args: iterable: A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. \"\"\" frequencies = collections.Counter(iterable) if not frequencies: return None counts = sorted((count, element) for element, count in frequencies.items()) # Note: In case of a tie, this chooses the longest width. # We could eventually make this an option. return counts[-1][1]","title":"compute_most_frequent()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.normalize_indent_whitespace","text":"Normalize whitespace before lines that has some indent and an account name. Parameters: match_pairs \u2013 A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. Source code in beancount/scripts/format.py def normalize_indent_whitespace(match_pairs): \"\"\"Normalize whitespace before lines that has some indent and an account name. Args: match_pairs: A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. \"\"\" # Compute most frequent account name prefix. match_posting = re.compile(r'([ \\t]+)({}.*)'.format(account.ACCOUNT_RE)).match width = compute_most_frequent( len(match.group(1)) for match in (match_posting(prefix) for prefix, _, _ in match_pairs) if match is not None) norm_format = ' ' * (width or 0) + '{}' # Make the necessary adjustments. adjusted_pairs = [] for tup in match_pairs: prefix, number, rest = tup match = match_posting(prefix) if match is not None: tup = (norm_format.format(match.group(2)), number, rest) adjusted_pairs.append(tup) return adjusted_pairs","title":"normalize_indent_whitespace()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql","text":"Convert a Beancount ledger into an SQL database.","title":"sql"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter","text":"","title":"BalanceWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type","text":"Balance(meta, date, account, amount, tolerance, diff_amount)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__new__","text":"Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.amount.number, entry.amount.currency, entry.diff_amount.currency if entry.diff_amount else None, entry.diff_amount.currency if entry.diff_amount else None)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter","text":"","title":"CloseWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type","text":"Close(meta, date, account)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__new__","text":"Create new instance of Close(meta, date, account)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account,)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter","text":"A base class for writers of directives. This is used to factor out code for all the simple directives types (all types except Transaction).","title":"DirectiveWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter.__call__","text":"Create a table for a directives. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def __call__(self, connection, entries): \"\"\"Create a table for a directives. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: columns_text = ','.join(self.columns.strip().splitlines()) connection.execute(\"\"\" CREATE TABLE {name}_detail ( id INTEGER PRIMARY KEY, {columns} ); \"\"\".format(name=self.name, columns=columns_text)) connection.execute(\"\"\" CREATE VIEW {name} AS SELECT * FROM entry JOIN {name}_detail USING (id); \"\"\".format(name=self.name)) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, self.type): continue # Store common data. connection.execute(\"\"\" INSERT INTO entry VALUES (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, self.name, entry.meta[\"filename\"], entry.meta[\"lineno\"])) # Store detail data. detail_data = self.get_detail(entry) row_data = (eid,) + detail_data query = \"\"\" INSERT INTO {name}_detail VALUES ({placeholder}); \"\"\".format(name=self.name, placeholder=','.join(['?'] * (1 + len(detail_data)))) connection.execute(query, row_data)","title":"__call__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): \"\"\"Provide data to store for details table. Args: entry: An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. \"\"\" raise NotImplementedError","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter","text":"","title":"DocumentWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type","text":"Document(meta, date, account, filename, tags, links)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__new__","text":"Create new instance of Document(meta, date, account, filename, tags, links)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.filename)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter","text":"","title":"EventWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type","text":"Event(meta, date, type, description)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__new__","text":"Create new instance of Event(meta, date, type, description)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.type, entry.description)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter","text":"","title":"NoteWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type","text":"Note(meta, date, account, comment)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__new__","text":"Create new instance of Note(meta, date, account, comment)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.comment)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter","text":"","title":"OpenWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type","text":"Open(meta, date, account, currencies, booking)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__new__","text":"Create new instance of Open(meta, date, account, currencies, booking)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, ','.join(entry.currencies or []))","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter","text":"","title":"PadWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type","text":"Pad(meta, date, account, source_account)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__new__","text":"Create new instance of Pad(meta, date, account, source_account)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.source_account)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter","text":"","title":"PriceWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type","text":"Price(meta, date, currency, amount)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__new__","text":"Create new instance of Price(meta, date, currency, amount)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.currency, entry.amount.number, entry.amount.currency)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter","text":"","title":"QueryWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type","text":"Query(meta, date, name, query_string)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__new__","text":"Create new instance of Query(meta, date, name, query_string)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.name, entry.query_string)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.adapt_decimal","text":"Adapt a Decimal instance to a string for creating queries. Parameters: number \u2013 An instance of Decimal. Returns: A string. Source code in beancount/scripts/sql.py def adapt_decimal(number): \"\"\"Adapt a Decimal instance to a string for creating queries. Args: number: An instance of Decimal. Returns: A string. \"\"\" return str(number)","title":"adapt_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.convert_decimal","text":"Convert a Decimal string to a Decimal instance. Parameters: string \u2013 A decimal number in a string. Returns: An instance of Decimal. Source code in beancount/scripts/sql.py def convert_decimal(string): \"\"\"Convert a Decimal string to a Decimal instance. Args: string: A decimal number in a string. Returns: An instance of Decimal. \"\"\" return Decimal(string)","title":"convert_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.output_common","text":"Create a table of common data for all entries. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_common(connection, unused_entries): \"\"\"Create a table of common data for all entries. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE entry ( id INTEGER PRIMARY KEY, date DATE, type CHARACTER(8), source_filename STRING, source_lineno INTEGER ); \"\"\")","title":"output_common()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.output_transactions","text":"Create a table for transactions and fill in the data. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_transactions(connection, entries): \"\"\"Create a table for transactions and fill in the data. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE transactions_detail ( id INTEGER PRIMARY KEY, flag CHARACTER(1), payee VARCHAR, narration VARCHAR, tags VARCHAR, -- Comma-separated links VARCHAR -- Comma-separated ); \"\"\") connection.execute(\"\"\" CREATE VIEW transactions AS SELECT * FROM entry JOIN transactions_detail USING (id); \"\"\") connection.execute(\"\"\" CREATE TABLE postings ( posting_id INTEGER PRIMARY KEY, id INTEGER, flag CHARACTER(1), account VARCHAR, number DECIMAL(16, 6), currency CHARACTER(10), cost_number DECIMAL(16, 6), cost_currency CHARACTER(10), cost_date DATE, cost_label VARCHAR, price_number DECIMAL(16, 6), price_currency CHARACTER(10), FOREIGN KEY(id) REFERENCES entries(id) ); \"\"\") postings_count = iter(itertools.count()) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue connection.execute(\"\"\" insert into entry values (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, 'txn', entry.meta[\"filename\"], entry.meta[\"lineno\"])) connection.execute(\"\"\" insert into transactions_detail values (?, ?, ?, ?, ?, ?); \"\"\", (eid, entry.flag, entry.payee, entry.narration, ','.join(entry.tags or ()), ','.join(entry.links or ()))) for posting in entry.postings: pid = next(postings_count) units = posting.units cost = posting.cost price = posting.price connection.execute(\"\"\" INSERT INTO postings VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \"\"\", (pid, eid, posting.flag, posting.account, units.number, units.currency, cost.number if cost else None, cost.currency if cost else None, cost.date if cost else None, cost.label if cost else None, price.number if price else None, price.currency if price else None))","title":"output_transactions()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.setup_decimal_support","text":"Setup sqlite3 to support conversions to/from Decimal numbers. Source code in beancount/scripts/sql.py def setup_decimal_support(): \"\"\"Setup sqlite3 to support conversions to/from Decimal numbers. \"\"\" dbapi.register_adapter(Decimal, adapt_decimal) dbapi.register_converter(\"decimal\", convert_decimal)","title":"setup_decimal_support()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.tutorial","text":"Write output files for the tutorial commands.","title":"tutorial"},{"location":"api_reference/beancount.tools.html","text":"beancount.tools \uf0c1 Standalone tools that aren't linked to Beancount but that are useful with it. The beancount.scripts package contains the implementation of scripts which invoke the Beancount library code. This beancount.tools package implements other tools which aren't directly invoking Beancount library code and which could be theoretically copied and used independently. However, these are to be distributed with Beancount and in order to maintain all the source code together they are put in this package and invokes from stubs under beancount/bin/, just like the other scripts. beancount.tools.treeify \uf0c1 Identify a column of text that contains hierarchical id and treeify that column. This script will inspect a text file and attempt to find a vertically left-aligned column of text that contains identifiers with multiple components, such as \"Assets:US:Bank:Checking\", and replace those by a tree-like structure rendered in ASCII, inserting new empty lines where necessary to create the tree. Note: If your paths have spaces in them, this will not work. Space is used as a delimiter to detect the end of a column. You can customize the delimiter with an option. beancount.tools.treeify.Node ( list ) \uf0c1 A node with a name attribute, a list of line numbers and a list of children (from its parent class). beancount.tools.treeify.Node.__repr__(self) special \uf0c1 Return str(self). Source code in beancount/tools/treeify.py def __str__(self): return ''.format(self.name, [node.name for node in self]) beancount.tools.treeify.create_tree(column_matches, regexp_split) \uf0c1 Build up a tree from a list of matches. Parameters: column_matches \u2013 A list of (line-number, name) pairs. regexp_split \u2013 A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. Source code in beancount/tools/treeify.py def create_tree(column_matches, regexp_split): \"\"\"Build up a tree from a list of matches. Args: column_matches: A list of (line-number, name) pairs. regexp_split: A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. \"\"\" root = Node('') for no, name in column_matches: parts = re.split(regexp_split, name) node = root for part in parts: last_node = node[-1] if node else None if last_node is None or last_node.name != part: last_node = Node(part) node.append(last_node) node = last_node node.nos.append(no) return root beancount.tools.treeify.dump_tree(node, file=<_io.StringIO object at 0x7a3206d45540>, prefix='') \uf0c1 Render a tree as a tree. Parameters: node \u2013 An instance of Node. file \u2013 A file object to write to. prefix \u2013 A prefix string for each of the lines of the children. Source code in beancount/tools/treeify.py def dump_tree(node, file=sys.stdout, prefix=''): \"\"\"Render a tree as a tree. Args: node: An instance of Node. file: A file object to write to. prefix: A prefix string for each of the lines of the children. \"\"\" file.write(prefix) file.write(node.name) file.write('\\n') for child in node: dump_tree(child, file, prefix + '... ') beancount.tools.treeify.enum_tree_by_input_line_num(tree_lines) \uf0c1 Accumulate the lines of a tree until a line number is found. Parameters: tree_lines \u2013 A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). Source code in beancount/tools/treeify.py def enum_tree_by_input_line_num(tree_lines): \"\"\"Accumulate the lines of a tree until a line number is found. Args: tree_lines: A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). \"\"\" pending = [] for first_line, cont_line, node in tree_lines: if not node.nos: pending.append((first_line, node)) else: line = first_line for no in node.nos: pending.append((line, node)) line = cont_line yield (no, pending) pending = [] if pending: yield (None, pending) beancount.tools.treeify.find_column(lines, pattern, delimiter) \uf0c1 Find a valid column with hierarchical data in the text lines. Parameters: lines \u2013 A list of strings, the contents of the input. pattern \u2013 A regular expression for the hierarchical entries. delimiter \u2013 A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches \u2013 A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. Source code in beancount/tools/treeify.py def find_column(lines, pattern, delimiter): \"\"\"Find a valid column with hierarchical data in the text lines. Args: lines: A list of strings, the contents of the input. pattern: A regular expression for the hierarchical entries. delimiter: A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches: A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. \"\"\" # A mapping of the line beginning position to its match object. beginnings = collections.defaultdict(list) pattern_and_whitespace = \"({})(?P{}.|$)\".format(pattern, delimiter) for no, line in enumerate(lines): for match in re.finditer(pattern_and_whitespace, line): beginnings[match.start()].append((no, line, match)) # For each potential column found, verify that it is valid. A valid column # will have the maximum of its content text not overlap with any of the # following text. We assume that a column will have been formatted to full # width and that no text following the line overlap with the column, even in # its trailing whitespace. # # In other words, the following example is a violation because \"10,990.74\" # overlaps with the end of \"Insurance\" and so this would not be recognized # as a valid column: # # Expenses:Food:Restaurant 10,990.74 USD # Expenses:Health:Dental:Insurance 208.80 USD # for leftmost_column, column_matches in sorted(beginnings.items()): # Compute the location of the rightmost column of text. rightmost_column = max(match.end(1) for _, _, match in column_matches) # Compute the leftmost location of the content following the column text # and past its whitespace. following_column = min(match.end() if match.group('ws') else 10000 for _, _, match in column_matches) if rightmost_column < following_column: # We process only the very first match. return_matches = [(no, match.group(1).rstrip()) for no, _, match in column_matches] return return_matches, leftmost_column, rightmost_column beancount.tools.treeify.render_tree(root) \uf0c1 Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. Source code in beancount/tools/treeify.py def render_tree(root): \"\"\"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root.name, root, True)] while stack: prefix, name, node, is_last = stack.pop(-1) if node is root: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(node) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (node is root and not name): lines.append((first + name, cont + cont_name, node)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. if node: child_items = reversed(node) child_iter = iter(child_items) child_node = next(child_iter) stack.append((cont, child_node.name, child_node, True)) for child_node in child_iter: stack.append((cont, child_node.name, child_node, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), node) for (first_line, cont_line, node) in lines], max_width beancount.tools.treeify_test \uf0c1 Unit tests for treeify tool. beancount.tools.treeify_test.TestTreeifyBase ( TestCase ) \uf0c1 beancount.tools.treeify_test.TestTreeifyBase.treeify(self, string, expect_errors=False, options=None) \uf0c1 Run treeify on the given string and assert no errors. Parameters: string \u2013 A string, the input contents to run on. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. Source code in beancount/tools/treeify_test.py def treeify(self, string, expect_errors=False, options=None): \"\"\"Run treeify on the given string and assert no errors. Args: string: A string, the input contents to run on. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. \"\"\" returncode, output, errors = treeify(string, options) actual_errors = returncode != 0 or bool(errors) if actual_errors != expect_errors: if expect_errors: self.fail(\"Missing expected errors\") else: self.fail(\"Unexpected errors: {}\".format(errors)) return output beancount.tools.treeify_test.TestTreeifyBase.treeify_equal(self, string, expected, expect_errors=False, options=None) \uf0c1 Assert an expected treeification result. Parameters: string \u2013 An expected input contents string to treeify. expected \u2013 A string, the expected treeified output. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. Source code in beancount/tools/treeify_test.py def treeify_equal(self, string, expected, expect_errors=False, options=None): \"\"\"Assert an expected treeification result. Args: string: An expected input contents string to treeify. expected: A string, the expected treeified output. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. \"\"\" input_ = textwrap.dedent(string) output = self.treeify(input_, expect_errors, options) expected = textwrap.dedent(expected) if DEBUG: print('-(input)----------------------------------') print(input_) print('-(output)---------------------------------') print(output) print('-(expected)-------------------------------') print(expected) print('------------------------------------------') self.assertEqual(expected, output) return output beancount.tools.treeify_test.treeify(string, options=None) \uf0c1 Run treeify on the string. Parameters: string \u2013 The input string to feed treeify. options \u2013 An optional list of options for the subprogram. Returns: The treeified string. Source code in beancount/tools/treeify_test.py def treeify(string, options=None): \"\"\"Run treeify on the string. Args: string: The input string to feed treeify. options: An optional list of options for the subprogram. Returns: The treeified string. \"\"\" pipe = subprocess.Popen([PROGRAM] + (options or []), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, errors = pipe.communicate(string.encode('utf-8')) return (pipe.returncode, output.decode('utf-8') if output else '', errors.decode('utf-8') if errors else '')","title":"beancount.tools"},{"location":"api_reference/beancount.tools.html#beancounttools","text":"Standalone tools that aren't linked to Beancount but that are useful with it. The beancount.scripts package contains the implementation of scripts which invoke the Beancount library code. This beancount.tools package implements other tools which aren't directly invoking Beancount library code and which could be theoretically copied and used independently. However, these are to be distributed with Beancount and in order to maintain all the source code together they are put in this package and invokes from stubs under beancount/bin/, just like the other scripts.","title":"beancount.tools"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify","text":"Identify a column of text that contains hierarchical id and treeify that column. This script will inspect a text file and attempt to find a vertically left-aligned column of text that contains identifiers with multiple components, such as \"Assets:US:Bank:Checking\", and replace those by a tree-like structure rendered in ASCII, inserting new empty lines where necessary to create the tree. Note: If your paths have spaces in them, this will not work. Space is used as a delimiter to detect the end of a column. You can customize the delimiter with an option.","title":"treeify"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.Node","text":"A node with a name attribute, a list of line numbers and a list of children (from its parent class).","title":"Node"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.Node.__repr__","text":"Return str(self). Source code in beancount/tools/treeify.py def __str__(self): return ''.format(self.name, [node.name for node in self])","title":"__repr__()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.create_tree","text":"Build up a tree from a list of matches. Parameters: column_matches \u2013 A list of (line-number, name) pairs. regexp_split \u2013 A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. Source code in beancount/tools/treeify.py def create_tree(column_matches, regexp_split): \"\"\"Build up a tree from a list of matches. Args: column_matches: A list of (line-number, name) pairs. regexp_split: A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. \"\"\" root = Node('') for no, name in column_matches: parts = re.split(regexp_split, name) node = root for part in parts: last_node = node[-1] if node else None if last_node is None or last_node.name != part: last_node = Node(part) node.append(last_node) node = last_node node.nos.append(no) return root","title":"create_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.dump_tree","text":"Render a tree as a tree. Parameters: node \u2013 An instance of Node. file \u2013 A file object to write to. prefix \u2013 A prefix string for each of the lines of the children. Source code in beancount/tools/treeify.py def dump_tree(node, file=sys.stdout, prefix=''): \"\"\"Render a tree as a tree. Args: node: An instance of Node. file: A file object to write to. prefix: A prefix string for each of the lines of the children. \"\"\" file.write(prefix) file.write(node.name) file.write('\\n') for child in node: dump_tree(child, file, prefix + '... ')","title":"dump_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.enum_tree_by_input_line_num","text":"Accumulate the lines of a tree until a line number is found. Parameters: tree_lines \u2013 A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). Source code in beancount/tools/treeify.py def enum_tree_by_input_line_num(tree_lines): \"\"\"Accumulate the lines of a tree until a line number is found. Args: tree_lines: A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). \"\"\" pending = [] for first_line, cont_line, node in tree_lines: if not node.nos: pending.append((first_line, node)) else: line = first_line for no in node.nos: pending.append((line, node)) line = cont_line yield (no, pending) pending = [] if pending: yield (None, pending)","title":"enum_tree_by_input_line_num()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.find_column","text":"Find a valid column with hierarchical data in the text lines. Parameters: lines \u2013 A list of strings, the contents of the input. pattern \u2013 A regular expression for the hierarchical entries. delimiter \u2013 A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches \u2013 A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. Source code in beancount/tools/treeify.py def find_column(lines, pattern, delimiter): \"\"\"Find a valid column with hierarchical data in the text lines. Args: lines: A list of strings, the contents of the input. pattern: A regular expression for the hierarchical entries. delimiter: A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches: A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. \"\"\" # A mapping of the line beginning position to its match object. beginnings = collections.defaultdict(list) pattern_and_whitespace = \"({})(?P{}.|$)\".format(pattern, delimiter) for no, line in enumerate(lines): for match in re.finditer(pattern_and_whitespace, line): beginnings[match.start()].append((no, line, match)) # For each potential column found, verify that it is valid. A valid column # will have the maximum of its content text not overlap with any of the # following text. We assume that a column will have been formatted to full # width and that no text following the line overlap with the column, even in # its trailing whitespace. # # In other words, the following example is a violation because \"10,990.74\" # overlaps with the end of \"Insurance\" and so this would not be recognized # as a valid column: # # Expenses:Food:Restaurant 10,990.74 USD # Expenses:Health:Dental:Insurance 208.80 USD # for leftmost_column, column_matches in sorted(beginnings.items()): # Compute the location of the rightmost column of text. rightmost_column = max(match.end(1) for _, _, match in column_matches) # Compute the leftmost location of the content following the column text # and past its whitespace. following_column = min(match.end() if match.group('ws') else 10000 for _, _, match in column_matches) if rightmost_column < following_column: # We process only the very first match. return_matches = [(no, match.group(1).rstrip()) for no, _, match in column_matches] return return_matches, leftmost_column, rightmost_column","title":"find_column()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.render_tree","text":"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. Source code in beancount/tools/treeify.py def render_tree(root): \"\"\"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root.name, root, True)] while stack: prefix, name, node, is_last = stack.pop(-1) if node is root: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(node) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (node is root and not name): lines.append((first + name, cont + cont_name, node)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. if node: child_items = reversed(node) child_iter = iter(child_items) child_node = next(child_iter) stack.append((cont, child_node.name, child_node, True)) for child_node in child_iter: stack.append((cont, child_node.name, child_node, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), node) for (first_line, cont_line, node) in lines], max_width","title":"render_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test","text":"Unit tests for treeify tool.","title":"treeify_test"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase","text":"","title":"TestTreeifyBase"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase.treeify","text":"Run treeify on the given string and assert no errors. Parameters: string \u2013 A string, the input contents to run on. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. Source code in beancount/tools/treeify_test.py def treeify(self, string, expect_errors=False, options=None): \"\"\"Run treeify on the given string and assert no errors. Args: string: A string, the input contents to run on. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. \"\"\" returncode, output, errors = treeify(string, options) actual_errors = returncode != 0 or bool(errors) if actual_errors != expect_errors: if expect_errors: self.fail(\"Missing expected errors\") else: self.fail(\"Unexpected errors: {}\".format(errors)) return output","title":"treeify()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase.treeify_equal","text":"Assert an expected treeification result. Parameters: string \u2013 An expected input contents string to treeify. expected \u2013 A string, the expected treeified output. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. Source code in beancount/tools/treeify_test.py def treeify_equal(self, string, expected, expect_errors=False, options=None): \"\"\"Assert an expected treeification result. Args: string: An expected input contents string to treeify. expected: A string, the expected treeified output. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. \"\"\" input_ = textwrap.dedent(string) output = self.treeify(input_, expect_errors, options) expected = textwrap.dedent(expected) if DEBUG: print('-(input)----------------------------------') print(input_) print('-(output)---------------------------------') print(output) print('-(expected)-------------------------------') print(expected) print('------------------------------------------') self.assertEqual(expected, output) return output","title":"treeify_equal()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.treeify","text":"Run treeify on the string. Parameters: string \u2013 The input string to feed treeify. options \u2013 An optional list of options for the subprogram. Returns: The treeified string. Source code in beancount/tools/treeify_test.py def treeify(string, options=None): \"\"\"Run treeify on the string. Args: string: The input string to feed treeify. options: An optional list of options for the subprogram. Returns: The treeified string. \"\"\" pipe = subprocess.Popen([PROGRAM] + (options or []), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, errors = pipe.communicate(string.encode('utf-8')) return (pipe.returncode, output.decode('utf-8') if output else '', errors.decode('utf-8') if errors else '')","title":"treeify()"},{"location":"api_reference/beancount.utils.html","text":"beancount.utils \uf0c1 Generic utility packages and functions. beancount.utils.bisect_key \uf0c1 A version of bisect that accepts a custom key function, like the sorting ones do. beancount.utils.bisect_key.bisect_left_with_key(sequence, value, key=None) \uf0c1 Find the last element before the given value in a sorted list. Parameters: sequence \u2013 A sorted sequence of elements. value \u2013 The value to search for. key \u2013 An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. Source code in beancount/utils/bisect_key.py def bisect_left_with_key(sequence, value, key=None): \"\"\"Find the last element before the given value in a sorted list. Args: sequence: A sorted sequence of elements. value: The value to search for. key: An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. \"\"\" # pylint: disable=invalid-name if key is None: key = lambda x: x # Identity. lo = 0 hi = len(sequence) while lo < hi: mid = (lo + hi) // 2 if key(sequence[mid]) < value: lo = mid + 1 else: hi = mid return lo beancount.utils.bisect_key.bisect_right_with_key(a, x, key, lo=0, hi=None) \uf0c1 Like bisect.bisect_right, but with a key lookup parameter. Parameters: a \u2013 The list to search in. x \u2013 The element to search for. key \u2013 A function, to extract the value from the list. lo \u2013 The smallest index to search. hi \u2013 The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. Source code in beancount/utils/bisect_key.py def bisect_right_with_key(a, x, key, lo=0, hi=None): \"\"\"Like bisect.bisect_right, but with a key lookup parameter. Args: a: The list to search in. x: The element to search for. key: A function, to extract the value from the list. lo: The smallest index to search. hi: The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. \"\"\" # pylint: disable=invalid-name if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 if x < key(a[mid]): hi = mid else: lo = mid+1 return lo beancount.utils.csv_utils \uf0c1 Utilities for reading and writing CSV files. beancount.utils.csv_utils.as_rows(string) \uf0c1 Split a string as rows of a CSV file. Parameters: string \u2013 A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. Source code in beancount/utils/csv_utils.py def as_rows(string): \"\"\"Split a string as rows of a CSV file. Args: string: A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. \"\"\" return list(csv.reader(io.StringIO(textwrap.dedent(string)))) beancount.utils.csv_utils.csv_clean_header(header_row) \uf0c1 Create a new class for the following rows from the header line. This both normalizes the header line and assign Parameters: header_row \u2013 A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. Source code in beancount/utils/csv_utils.py def csv_clean_header(header_row): \"\"\"Create a new class for the following rows from the header line. This both normalizes the header line and assign Args: header_row: A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. \"\"\" fieldnames = [] for index, column in enumerate(header_row): field = column.lower() field = re.sub(r'\\bp/l\\b', 'pnl', field) field = re.sub('[^a-z0-9]', '_', field) field = field.strip(' _') field = re.sub('__+', '_', field) if not field: field = 'col{}'.format(index) assert field not in fieldnames, field fieldnames.append(field) return fieldnames beancount.utils.csv_utils.csv_dict_reader(fileobj, **kw) \uf0c1 Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. Source code in beancount/utils/csv_utils.py def csv_dict_reader(fileobj, **kw): \"\"\"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. \"\"\" reader = csv.DictReader(fileobj, **kw) reader.fieldnames = csv_clean_header(reader.fieldnames) return reader beancount.utils.csv_utils.csv_split_sections(rows) \uf0c1 Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Parameters: rows \u2013 A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. Source code in beancount/utils/csv_utils.py def csv_split_sections(rows): \"\"\"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Args: rows: A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. \"\"\" sections = [] current_section = [] for row in rows: if row: current_section.append(row) else: sections.append(current_section) current_section = [] if current_section: sections.append(current_section) return sections beancount.utils.csv_utils.csv_split_sections_with_titles(rows) \uf0c1 Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Parameters: rows \u2013 A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). Source code in beancount/utils/csv_utils.py def csv_split_sections_with_titles(rows): \"\"\"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Args: rows: A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). \"\"\" sections_map = {} for index, section in enumerate(csv_split_sections(rows)): # Skip too short sections, cannot possibly be a title. if len(section) < 2: continue if len(section[0]) == 1 and len(section[1]) != 1: name = section[0][0] section = section[1:] else: name = 'Section {}'.format(index) sections_map[name] = section return sections_map beancount.utils.csv_utils.csv_tuple_reader(fileobj, **kw) \uf0c1 Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. Source code in beancount/utils/csv_utils.py def csv_tuple_reader(fileobj, **kw): \"\"\"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. \"\"\" reader = csv.reader(fileobj, **kw) ireader = iter(reader) fieldnames = csv_clean_header(next(ireader)) Tuple = collections.namedtuple('Row', fieldnames) for row in ireader: try: yield Tuple(*row) except TypeError: # If there's an error, it's usually from a line that has a 'END OF # LINE' marker at the end, or some comment line. assert len(row) in (0, 1) beancount.utils.csv_utils.iter_sections(fileobj, separating_predicate=None) \uf0c1 For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Parameters: fileobj \u2013 A file object to read from. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. Source code in beancount/utils/csv_utils.py def iter_sections(fileobj, separating_predicate=None): \"\"\"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Args: fileobj: A file object to read from. separating_predicate: A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) lineiter = iter(fileobj) for line in lineiter: if separating_predicate(line): iterator = itertools.chain((line,), iter_until_empty(lineiter, separating_predicate)) yield iterator for _ in iterator: pass beancount.utils.csv_utils.iter_until_empty(iterator, separating_predicate=None) \uf0c1 An iterator of lines that will stop at the first empty line. Parameters: iterator \u2013 An iterator of lines. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. Source code in beancount/utils/csv_utils.py def iter_until_empty(iterator, separating_predicate=None): \"\"\"An iterator of lines that will stop at the first empty line. Args: iterator: An iterator of lines. separating_predicate: A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) for line in iterator: if not separating_predicate(line): break yield line beancount.utils.date_utils \uf0c1 Parse the date from various formats. beancount.utils.date_utils.intimezone(tz_value) \uf0c1 Temporarily reset the value of TZ. This is used for testing. Parameters: tz_value ( str ) \u2013 The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. Source code in beancount/utils/date_utils.py @contextlib.contextmanager def intimezone(tz_value: str): \"\"\"Temporarily reset the value of TZ. This is used for testing. Args: tz_value: The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. \"\"\" tz_old = os.environ.get('TZ', None) os.environ['TZ'] = tz_value time.tzset() try: yield finally: if tz_old is None: del os.environ['TZ'] else: os.environ['TZ'] = tz_old time.tzset() beancount.utils.date_utils.iter_dates(start_date, end_date) \uf0c1 Yield all the dates between 'start_date' and 'end_date'. Parameters: start_date \u2013 An instance of datetime.date. end_date \u2013 An instance of datetime.date. Yields: Instances of datetime.date. Source code in beancount/utils/date_utils.py def iter_dates(start_date, end_date): \"\"\"Yield all the dates between 'start_date' and 'end_date'. Args: start_date: An instance of datetime.date. end_date: An instance of datetime.date. Yields: Instances of datetime.date. \"\"\" oneday = datetime.timedelta(days=1) date = start_date while date < end_date: yield date date += oneday beancount.utils.date_utils.next_month(date) \uf0c1 Compute the date at the beginning of the following month from the given date. Parameters: date \u2013 A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. Source code in beancount/utils/date_utils.py def next_month(date): \"\"\"Compute the date at the beginning of the following month from the given date. Args: date: A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. \"\"\" # Compute the date at the beginning of the following month. year = date.year month = date.month + 1 if date.month == 12: year += 1 month = 1 return datetime.date(year, month, 1) beancount.utils.date_utils.parse_date_liberally(string, parse_kwargs_dict=None) \uf0c1 Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Parameters: string \u2013 A string to parse. parse_kwargs_dict \u2013 Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. Source code in beancount/utils/date_utils.py def parse_date_liberally(string, parse_kwargs_dict=None): \"\"\"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Args: string: A string to parse. parse_kwargs_dict: Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. \"\"\" # At the moment, rely on the most excellent dateutil. if parse_kwargs_dict is None: parse_kwargs_dict = {} return dateutil.parser.parse(string, **parse_kwargs_dict).date() beancount.utils.date_utils.render_ofx_date(dtime) \uf0c1 Render a datetime to the OFX format. Parameters: dtime \u2013 A datetime.datetime instance. Returns: A string, rendered to milliseconds. Source code in beancount/utils/date_utils.py def render_ofx_date(dtime): \"\"\"Render a datetime to the OFX format. Args: dtime: A datetime.datetime instance. Returns: A string, rendered to milliseconds. \"\"\" return '{}.{:03d}'.format(dtime.strftime('%Y%m%d%H%M%S'), int(dtime.microsecond / 1000)) beancount.utils.defdict \uf0c1 An instance of collections.defaultdict whose factory accepts a key. Note: This really ought to be an enhancement to Python itself. I should bother adding this in eventually. beancount.utils.defdict.DefaultDictWithKey ( defaultdict ) \uf0c1 A version of defaultdict whose factory accepts the key as an argument. Note: collections.defaultdict would be improved by supporting this directly, this is a common occurrence. beancount.utils.defdict.ImmutableDictWithDefault ( dict ) \uf0c1 An immutable dict which returns a default value for missing keys. This differs from a defaultdict in that it does not insert a missing default value when one is materialized (from a missing fetch), and furthermore, the set method is make unavailable to prevent mutation beyond construction. beancount.utils.defdict.ImmutableDictWithDefault.__setitem__(self, key, value) special \uf0c1 Disallow mutating the dict in the usual way. Source code in beancount/utils/defdict.py def __setitem__(self, key, value): \"\"\"Disallow mutating the dict in the usual way.\"\"\" raise NotImplementedError beancount.utils.defdict.ImmutableDictWithDefault.get(self, key, _=None) \uf0c1 Return the value for key if key is in the dictionary, else default. Source code in beancount/utils/defdict.py def get(self, key, _=None): return self.__getitem__(key) beancount.utils.encryption \uf0c1 Support for encrypted tests. beancount.utils.encryption.is_encrypted_file(filename) \uf0c1 Return true if the given filename contains an encrypted file. Parameters: filename \u2013 A path string. Returns: A boolean, true if the file contains an encrypted file. Source code in beancount/utils/encryption.py def is_encrypted_file(filename): \"\"\"Return true if the given filename contains an encrypted file. Args: filename: A path string. Returns: A boolean, true if the file contains an encrypted file. \"\"\" _, ext = path.splitext(filename) if ext == '.gpg': return True if ext == '.asc': with open(filename) as encfile: head = encfile.read(1024) if re.search('--BEGIN PGP MESSAGE--', head): return True return False beancount.utils.encryption.is_gpg_installed() \uf0c1 Return true if GPG 1.4.x or 2.x are installed, which is what we use and support. Source code in beancount/utils/encryption.py def is_gpg_installed(): \"\"\"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support.\"\"\" try: pipe = subprocess.Popen(['gpg', '--version'], shell=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = pipe.communicate() version_text = out.decode('utf8') return pipe.returncode == 0 and re.match(r'gpg \\(GnuPG\\) (1\\.4|2)\\.', version_text) except OSError: return False beancount.utils.encryption.read_encrypted_file(filename) \uf0c1 Decrypt and read an encrypted file without temporary storage. Parameters: filename \u2013 A string, the path to the encrypted file. Returns: A string, the contents of the file. Exceptions: OSError \u2013 If we could not properly decrypt the file. Source code in beancount/utils/encryption.py def read_encrypted_file(filename): \"\"\"Decrypt and read an encrypted file without temporary storage. Args: filename: A string, the path to the encrypted file. Returns: A string, the contents of the file. Raises: OSError: If we could not properly decrypt the file. \"\"\" command = ['gpg', '--batch', '--decrypt', path.realpath(filename)] pipe = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) contents, errors = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Could not decrypt file ({}): {}\".format(pipe.returncode, errors.decode('utf8'))) return contents.decode('utf-8') beancount.utils.file_type \uf0c1 Code that can guess a MIME type for a filename. This attempts to identify the mime-type of a file suing The built-in mimetypes library, then python-magic (if available), and finally some custom mappers for datatypes used in financial downloads, such as Quicken files. beancount.utils.file_type.guess_file_type(filename) \uf0c1 Attempt to guess the type of the input file. Parameters: filename \u2013 A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. Source code in beancount/utils/file_type.py def guess_file_type(filename): \"\"\"Attempt to guess the type of the input file. Args: filename: A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. \"\"\" # Try the standard mimetypes association. filetype, _ = mimetypes.guess_type(filename, False) if filetype: return filetype # Try out some extra types that only we know about. for regexp, mimetype in EXTRA_FILE_TYPES: if regexp.match(filename): return mimetype # Try out libmagic, if it is installed. if magic: filetype = magic.from_file(filename, mime=True) if isinstance(filetype, bytes): filetype = filetype.decode('utf8') return filetype else: raise ValueError((\"Could not identify the type of file '{}'; \" \"try installing python-magic\").format(filename)) beancount.utils.file_utils \uf0c1 File utilities. beancount.utils.file_utils.chdir(directory) \uf0c1 Temporarily chdir to the given directory. Parameters: directory \u2013 The directory to switch do. Returns: A context manager which restores the cwd after running. Source code in beancount/utils/file_utils.py @contextlib.contextmanager def chdir(directory): \"\"\"Temporarily chdir to the given directory. Args: directory: The directory to switch do. Returns: A context manager which restores the cwd after running. \"\"\" cwd = os.getcwd() os.chdir(directory) try: yield cwd finally: os.chdir(cwd) beancount.utils.file_utils.find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)) \uf0c1 Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Parameters: fords \u2013 A list of strings, file or directory names. ignore_dirs \u2013 A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. Source code in beancount/utils/file_utils.py def find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)): \"\"\"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Args: fords: A list of strings, file or directory names. ignore_dirs: A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. \"\"\" if isinstance(fords, str): fords = [fords] assert isinstance(fords, (list, tuple)) for ford in fords: if path.isdir(ford): for root, dirs, filenames in os.walk(ford): dirs[:] = sorted(dirname for dirname in dirs if dirname not in ignore_dirs) for filename in sorted(filenames): if filename in ignore_files: continue yield path.join(root, filename) elif path.isfile(ford) or path.islink(ford): yield ford elif not path.exists(ford): logging.error(\"File or directory '{}' does not exist.\".format(ford)) beancount.utils.file_utils.guess_file_format(filename, default=None) \uf0c1 Guess the file format from the filename. Parameters: filename \u2013 A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. Source code in beancount/utils/file_utils.py def guess_file_format(filename, default=None): \"\"\"Guess the file format from the filename. Args: filename: A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. \"\"\" if filename: if filename.endswith('.txt') or filename.endswith('.text'): format = 'text' elif filename.endswith('.csv'): format = 'csv' elif filename.endswith('.html') or filename.endswith('.xhtml'): format = 'html' else: format = default else: format = default return format beancount.utils.file_utils.path_greedy_split(filename) \uf0c1 Split a path, returning the longest possible extension. Parameters: filename \u2013 A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). Source code in beancount/utils/file_utils.py def path_greedy_split(filename): \"\"\"Split a path, returning the longest possible extension. Args: filename: A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). \"\"\" basename = path.basename(filename) index = basename.find('.') if index == -1: extension = None else: extension = basename[index:] basename = basename[:index] return (path.join(path.dirname(filename), basename), extension) beancount.utils.file_utils.touch_file(filename, *otherfiles) \uf0c1 Touch a file and wait until its timestamp has been changed. Parameters: filename \u2013 A string path, the name of the file to touch. otherfiles \u2013 A list of other files to ensure the timestamp is beyond of. Source code in beancount/utils/file_utils.py def touch_file(filename, *otherfiles): \"\"\"Touch a file and wait until its timestamp has been changed. Args: filename: A string path, the name of the file to touch. otherfiles: A list of other files to ensure the timestamp is beyond of. \"\"\" # Note: You could set os.stat_float_times() but then the main function would # have to set that up as well. It doesn't help so much, however, since # filesystems tend to have low resolutions, e.g. one second. orig_mtime_ns = max(os.stat(minfile).st_mtime_ns for minfile in (filename,) + otherfiles) delay_secs = 0.05 while True: with open(filename, 'a'): os.utime(filename) time.sleep(delay_secs) new_stat = os.stat(filename) if new_stat.st_mtime_ns > orig_mtime_ns: break beancount.utils.import_utils \uf0c1 Utilities for importing symbols programmatically. beancount.utils.import_utils.import_symbol(dotted_name) \uf0c1 Import a symbol in an arbitrary module. Parameters: dotted_name \u2013 A dotted path to a symbol. Returns: The object referenced by the given name. Exceptions: ImportError \u2013 If the module not not be imported. AttributeError \u2013 If the symbol could not be found in the module. Source code in beancount/utils/import_utils.py def import_symbol(dotted_name): \"\"\"Import a symbol in an arbitrary module. Args: dotted_name: A dotted path to a symbol. Returns: The object referenced by the given name. Raises: ImportError: If the module not not be imported. AttributeError: If the symbol could not be found in the module. \"\"\" comps = dotted_name.split('.') module_name = '.'.join(comps[:-1]) symbol_name = comps[-1] module = importlib.import_module(module_name) return getattr(module, symbol_name) beancount.utils.invariants \uf0c1 Functions to register auxiliary functions on a class' methods to check for invariants. This is intended to be used in a test, whereby your test will setup a class to automatically run invariant verification functions before and after each function call, to ensure some extra sanity checks that wouldn't be used in non-tests. Example: Instrument the Inventory class with the check_inventory_invariants() function. def setUp(module): instrument_invariants(Inventory, check_inventory_invariants, check_inventory_invariants) def tearDown(module): uninstrument_invariants(Inventory) beancount.utils.invariants.instrument_invariants(klass, prefun, postfun) \uf0c1 Instrument the class 'klass' with pre/post invariant checker functions. Parameters: klass \u2013 A class object, whose methods to be instrumented. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants pre-call. Source code in beancount/utils/invariants.py def instrument_invariants(klass, prefun, postfun): \"\"\"Instrument the class 'klass' with pre/post invariant checker functions. Args: klass: A class object, whose methods to be instrumented. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants pre-call. \"\"\" instrumented = {} for attrname, object_ in klass.__dict__.items(): if attrname.startswith('_'): continue if not isinstance(object_, types.FunctionType): continue instrumented[attrname] = object_ setattr(klass, attrname, invariant_check(object_, prefun, postfun)) klass.__instrumented = instrumented beancount.utils.invariants.invariant_check(method, prefun, postfun) \uf0c1 Decorate a method with the pre/post invariant checkers. Parameters: method \u2013 An unbound method to instrument. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants post-call. Returns: An unbound method, decorated. Source code in beancount/utils/invariants.py def invariant_check(method, prefun, postfun): \"\"\"Decorate a method with the pre/post invariant checkers. Args: method: An unbound method to instrument. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants post-call. Returns: An unbound method, decorated. \"\"\" reentrant = [] def new_method(self, *args, **kw): reentrant.append(None) if len(reentrant) == 1: prefun(self) result = method(self, *args, **kw) if len(reentrant) == 1: postfun(self) reentrant.pop() return result return new_method beancount.utils.invariants.uninstrument_invariants(klass) \uf0c1 Undo the instrumentation for invariants. Parameters: klass \u2013 A class object, whose methods to be uninstrumented. Source code in beancount/utils/invariants.py def uninstrument_invariants(klass): \"\"\"Undo the instrumentation for invariants. Args: klass: A class object, whose methods to be uninstrumented. \"\"\" instrumented = getattr(klass, '__instrumented', None) if instrumented: for attrname, object_ in instrumented.items(): setattr(klass, attrname, object_) del klass.__instrumented beancount.utils.memo \uf0c1 Memoization utilities. beancount.utils.memo.memoize_recent_fileobj(function, cache_filename, expiration=None) \uf0c1 Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Parameters: function \u2013 A callable object. cache_filename \u2013 A string, the path to the database file to cache to. expiration \u2013 The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. Source code in beancount/utils/memo.py def memoize_recent_fileobj(function, cache_filename, expiration=None): \"\"\"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Args: function: A callable object. cache_filename: A string, the path to the database file to cache to. expiration: The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. \"\"\" urlcache = shelve.open(cache_filename, 'c') urlcache.lock = threading.Lock() # Note: 'shelve' is not thread-safe. @functools.wraps(function) def memoized(*args, **kw): # Encode the arguments, including a date string in order to invalidate # results over some time. md5 = hashlib.md5() md5.update(str(args).encode('utf-8')) md5.update(str(sorted(kw.items())).encode('utf-8')) hash_ = md5.hexdigest() time_now = now() try: with urlcache.lock: time_orig, contents = urlcache[hash_] if expiration is not None and (time_now - time_orig) > expiration: raise KeyError except KeyError: fileobj = function(*args, **kw) if fileobj: contents = fileobj.read() with urlcache.lock: urlcache[hash_] = (time_now, contents) else: contents = None return io.BytesIO(contents) if contents else None return memoized beancount.utils.memo.now() \uf0c1 Indirection on datetime.datetime.now() for testing. Source code in beancount/utils/memo.py def now(): \"Indirection on datetime.datetime.now() for testing.\" return datetime.datetime.now() beancount.utils.misc_utils \uf0c1 Generic utility packages and functions. beancount.utils.misc_utils.LineFileProxy \uf0c1 A file object that will delegate writing full lines to another logging function. This may be used for writing data to a logging level without having to worry about lines. beancount.utils.misc_utils.LineFileProxy.__init__(self, line_writer, prefix=None, write_newlines=False) special \uf0c1 Construct a new line delegator file object proxy. Parameters: line_writer \u2013 A callable function, used to write to the delegated output. prefix \u2013 An optional string, the prefix to insert before every line. write_newlines \u2013 A boolean, true if we should output the newline characters. Source code in beancount/utils/misc_utils.py def __init__(self, line_writer, prefix=None, write_newlines=False): \"\"\"Construct a new line delegator file object proxy. Args: line_writer: A callable function, used to write to the delegated output. prefix: An optional string, the prefix to insert before every line. write_newlines: A boolean, true if we should output the newline characters. \"\"\" self.line_writer = line_writer self.prefix = prefix self.write_newlines = write_newlines self.data = [] beancount.utils.misc_utils.LineFileProxy.close(self) \uf0c1 Close the line delegator. Source code in beancount/utils/misc_utils.py def close(self): \"\"\"Close the line delegator.\"\"\" self.flush() beancount.utils.misc_utils.LineFileProxy.flush(self) \uf0c1 Flush the data to the line writer. Source code in beancount/utils/misc_utils.py def flush(self): \"\"\"Flush the data to the line writer.\"\"\" data = ''.join(self.data) if data: lines = data.splitlines() self.data = [lines.pop(-1)] if data[-1] != '\\n' else [] for line in lines: if self.prefix: line = self.prefix + line if self.write_newlines: line += '\\n' self.line_writer(line) beancount.utils.misc_utils.LineFileProxy.write(self, data) \uf0c1 Write some string data to the output. Parameters: data \u2013 A string, with or without newlines. Source code in beancount/utils/misc_utils.py def write(self, data): \"\"\"Write some string data to the output. Args: data: A string, with or without newlines. \"\"\" if '\\n' in data: self.data.append(data) self.flush() else: self.data.append(data) beancount.utils.misc_utils.TypeComparable \uf0c1 A base class whose equality comparison includes comparing the type of the instance itself. beancount.utils.misc_utils.box(name=None, file=None) \uf0c1 A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Parameters: name \u2013 A string, the name of the box to use. file \u2013 The file object to print to. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def box(name=None, file=None): \"\"\"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Args: name: A string, the name of the box to use. file: The file object to print to. Yields: None. \"\"\" file = file or sys.stdout file.write('\\n') if name: header = ',--------({})--------\\n'.format(name) footer = '`{}\\n'.format('-' * (len(header)-2)) else: header = ',----------------\\n' footer = '`----------------\\n' file.write(header) yield file.write(footer) file.flush() beancount.utils.misc_utils.cmptuple(name, attributes) \uf0c1 Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Parameters: name \u2013 The given name of the class. attributes \u2013 A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. Source code in beancount/utils/misc_utils.py def cmptuple(name, attributes): \"\"\"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Args: name: The given name of the class. attributes: A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. \"\"\" base = collections.namedtuple('_{}'.format(name), attributes) return type(name, (TypeComparable, base,), {}) beancount.utils.misc_utils.compute_unique_clean_ids(strings) \uf0c1 Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Parameters: strings \u2013 A list of strings. Returns: A list of (id, string) pairs. Source code in beancount/utils/misc_utils.py def compute_unique_clean_ids(strings): \"\"\"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Args: strings: A list of strings. Returns: A list of (id, string) pairs. \"\"\" string_set = set(strings) # Try multiple methods until we get one that has no collisions. for regexp, replacement in [(r'[^A-Za-z0-9.-]', '_'), (r'[^A-Za-z0-9_]', ''),]: seen = set() idmap = {} mre = re.compile(regexp) for string in string_set: id_ = mre.sub(replacement, string) if id_ in seen: break # Collision. seen.add(id_) idmap[id_] = string else: break else: return None # Could not find a unique mapping. return idmap beancount.utils.misc_utils.deprecated(message) \uf0c1 A decorator generator to mark functions as deprecated and log a warning. Source code in beancount/utils/misc_utils.py def deprecated(message): \"\"\"A decorator generator to mark functions as deprecated and log a warning.\"\"\" def decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn(\"Call to deprecated function {}: {}\".format(func.__name__, message), category=DeprecationWarning, stacklevel=2) return func(*args, **kwargs) return new_func return decorator beancount.utils.misc_utils.dictmap(mdict, keyfun=None, valfun=None) \uf0c1 Map a dictionary's value. Parameters: mdict \u2013 A dict. key \u2013 A callable to apply to the keys. value \u2013 A callable to apply to the values. Source code in beancount/utils/misc_utils.py def dictmap(mdict, keyfun=None, valfun=None): \"\"\"Map a dictionary's value. Args: mdict: A dict. key: A callable to apply to the keys. value: A callable to apply to the values. \"\"\" if keyfun is None: keyfun = lambda x: x if valfun is None: valfun = lambda x: x return {keyfun(key): valfun(val) for key, val in mdict.items()} beancount.utils.misc_utils.escape_string(string) \uf0c1 Escape quotes and backslashes in payee and narration. Parameters: string \u2013 Any string. Returns. The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def escape_string(string): \"\"\"Escape quotes and backslashes in payee and narration. Args: string: Any string. Returns. The input string, with offending characters replaced. \"\"\" return string.replace('\\\\', r'\\\\')\\ .replace('\"', r'\\\"') beancount.utils.misc_utils.filter_type(elist, types) \uf0c1 Filter the given list to yield only instances of the given types. Parameters: elist \u2013 A sequence of elements. types \u2013 A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. Source code in beancount/utils/misc_utils.py def filter_type(elist, types): \"\"\"Filter the given list to yield only instances of the given types. Args: elist: A sequence of elements. types: A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. \"\"\" for element in elist: if not isinstance(element, types): continue yield element beancount.utils.misc_utils.first_paragraph(docstring) \uf0c1 Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Parameters: docstring \u2013 A doc string. Returns: A string with just the first sentence on a single line. Source code in beancount/utils/misc_utils.py def first_paragraph(docstring): \"\"\"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Args: docstring: A doc string. Returns: A string with just the first sentence on a single line. \"\"\" lines = [] for line in docstring.strip().splitlines(): if not line: break lines.append(line.rstrip()) return ' '.join(lines) beancount.utils.misc_utils.get_screen_height() \uf0c1 Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_height(): \"\"\"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('lines', 0) beancount.utils.misc_utils.get_screen_width() \uf0c1 Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_width(): \"\"\"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('cols', 0) beancount.utils.misc_utils.get_tuple_values(ntuple, predicate, memo=None) \uf0c1 Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Parameters: ntuple \u2013 A tuple or namedtuple. predicate \u2013 A predicate function that returns true if an attribute is to be output. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def get_tuple_values(ntuple, predicate, memo=None): \"\"\"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Args: ntuple: A tuple or namedtuple. predicate: A predicate function that returns true if an attribute is to be output. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return memo.add(id_ntuple) if predicate(ntuple): yield for attribute in ntuple: if predicate(attribute): yield attribute if isinstance(attribute, (list, tuple)): for value in get_tuple_values(attribute, predicate, memo): yield value beancount.utils.misc_utils.groupby(keyfun, elements) \uf0c1 Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Parameters: keyfun \u2013 A callable, used to obtain the group key from each element. elements \u2013 An iterable of the elements to group. Returns: A dict of key to list of sequences. Source code in beancount/utils/misc_utils.py def groupby(keyfun, elements): \"\"\"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Args: keyfun: A callable, used to obtain the group key from each element. elements: An iterable of the elements to group. Returns: A dict of key to list of sequences. \"\"\" # Note: We could allow a custom aggregation function. Another option is # provide another method to reduce the list values of a dict, but that can # be accomplished using a dict comprehension. grouped = defaultdict(list) for element in elements: grouped[keyfun(element)].append(element) return grouped beancount.utils.misc_utils.idify(string) \uf0c1 Replace characters objectionable for a filename with underscores. Parameters: string \u2013 Any string. Returns: The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def idify(string): \"\"\"Replace characters objectionable for a filename with underscores. Args: string: Any string. Returns: The input string, with offending characters replaced. \"\"\" for sfrom, sto in [(r'[ \\(\\)]+', '_'), (r'_*\\._*', '.')]: string = re.sub(sfrom, sto, string) string = string.strip('_') return string beancount.utils.misc_utils.import_curses() \uf0c1 Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Exceptions: ImportError \u2013 If the module could not be imported. Source code in beancount/utils/misc_utils.py def import_curses(): \"\"\"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Raises: ImportError: If the module could not be imported. \"\"\" # Note: There's a recipe for getting terminal size on Windows here, without # curses, I should probably implement that at some point: # https://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window # Also, consider just using 'blessings' instead, which provides this across # multiple platforms. # pylint: disable=import-outside-toplevel import curses return curses beancount.utils.misc_utils.is_sorted(iterable, key= at 0x7a3208a21bc0>, cmp= at 0x7a3208a21c60>) \uf0c1 Return true if the sequence is sorted. Parameters: iterable \u2013 An iterable sequence. key \u2013 A function to extract the quantity by which to sort. cmp \u2013 A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. Source code in beancount/utils/misc_utils.py def is_sorted(iterable, key=lambda x: x, cmp=lambda x, y: x <= y): \"\"\"Return true if the sequence is sorted. Args: iterable: An iterable sequence. key: A function to extract the quantity by which to sort. cmp: A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. \"\"\" iterator = map(key, iterable) prev = next(iterator) for element in iterator: if not cmp(prev, element): return False prev = element return True beancount.utils.misc_utils.log_time(operation_name, log_timings, indent=0) \uf0c1 A context manager that times the block and logs it to info level. Parameters: operation_name \u2013 A string, a label for the name of the operation. log_timings \u2013 A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent \u2013 An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def log_time(operation_name, log_timings, indent=0): \"\"\"A context manager that times the block and logs it to info level. Args: operation_name: A string, a label for the name of the operation. log_timings: A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent: An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. \"\"\" time1 = time() yield time1 time2 = time() if log_timings: log_timings(\"Operation: {:48} Time: {}{:6.0f} ms\".format( \"'{}'\".format(operation_name), ' '*indent, (time2 - time1) * 1000)) beancount.utils.misc_utils.longest(seq) \uf0c1 Return the longest of the given subsequences. Parameters: seq \u2013 An iterable sequence of lists. Returns: The longest list from the sequence. Source code in beancount/utils/misc_utils.py def longest(seq): \"\"\"Return the longest of the given subsequences. Args: seq: An iterable sequence of lists. Returns: The longest list from the sequence. \"\"\" longest, length = None, -1 for element in seq: len_element = len(element) if len_element > length: longest, length = element, len_element return longest beancount.utils.misc_utils.map_namedtuple_attributes(attributes, mapper, object_) \uf0c1 Map the value of the named attributes of object by mapper. Parameters: attributes \u2013 A sequence of string, the attribute names to map. mapper \u2013 A callable that accepts the value of a field and returns the new value. object_ \u2013 Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. Source code in beancount/utils/misc_utils.py def map_namedtuple_attributes(attributes, mapper, object_): \"\"\"Map the value of the named attributes of object by mapper. Args: attributes: A sequence of string, the attribute names to map. mapper: A callable that accepts the value of a field and returns the new value. object_: Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. \"\"\" return object_._replace(**{attribute: mapper(getattr(object_, attribute)) for attribute in attributes}) beancount.utils.misc_utils.replace_namedtuple_values(ntuple, predicate, mapper, memo=None) \uf0c1 Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Parameters: ntuple \u2013 A namedtuple instance. predicate \u2013 A predicate function that returns true if an attribute is to be output. mapper \u2013 A callable, that will accept a single argument and return its replacement value. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def replace_namedtuple_values(ntuple, predicate, mapper, memo=None): \"\"\"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Args: ntuple: A namedtuple instance. predicate: A predicate function that returns true if an attribute is to be output. mapper: A callable, that will accept a single argument and return its replacement value. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return None memo.add(id_ntuple) # pylint: disable=unidiomatic-typecheck if not (type(ntuple) is not tuple and isinstance(ntuple, tuple)): return ntuple replacements = {} for attribute_name, attribute in zip(ntuple._fields, ntuple): if predicate(attribute): replacements[attribute_name] = mapper(attribute) elif type(attribute) is not tuple and isinstance(attribute, tuple): replacements[attribute_name] = replace_namedtuple_values( attribute, predicate, mapper, memo) elif type(attribute) in (list, tuple): replacements[attribute_name] = [ replace_namedtuple_values(member, predicate, mapper, memo) for member in attribute] return ntuple._replace(**replacements) beancount.utils.misc_utils.skipiter(iterable, num_skip) \uf0c1 Skip some elements from an iterator. Parameters: iterable \u2013 An iterator. num_skip \u2013 The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. Source code in beancount/utils/misc_utils.py def skipiter(iterable, num_skip): \"\"\"Skip some elements from an iterator. Args: iterable: An iterator. num_skip: The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. \"\"\" assert num_skip > 0 sit = iter(iterable) while 1: try: value = next(sit) except StopIteration: return yield value for _ in range(num_skip-1): try: next(sit) except StopIteration: return beancount.utils.misc_utils.sorted_uniquify(iterable, keyfunc=None, last=False) \uf0c1 Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does not maintain the ordering of the original elements, they are returned sorted (by key) instead. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def sorted_uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does _not_ maintain the ordering of the original elements, they are returned sorted (by key) instead. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x if last: prev_obj = UNSET prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key and prev_obj is not UNSET: yield prev_obj prev_obj = obj prev_key = key if prev_obj is not UNSET: yield prev_obj else: prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key: yield obj prev_key = key beancount.utils.misc_utils.staticvar(varname, initial_value) \uf0c1 Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Parameters: varname \u2013 A string, the name of the variable to define. initial_value \u2013 The value to initialize the variable to. Returns: A function decorator. Source code in beancount/utils/misc_utils.py def staticvar(varname, initial_value): \"\"\"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Args: varname: A string, the name of the variable to define. initial_value: The value to initialize the variable to. Returns: A function decorator. \"\"\" def deco(fun): setattr(fun, varname, initial_value) return fun return deco beancount.utils.misc_utils.swallow(*exception_types) \uf0c1 Catch and ignore certain exceptions. Parameters: exception_types \u2013 A tuple of exception classes to ignore. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def swallow(*exception_types): \"\"\"Catch and ignore certain exceptions. Args: exception_types: A tuple of exception classes to ignore. Yields: None. \"\"\" try: yield except Exception as exc: if not isinstance(exc, exception_types): raise beancount.utils.misc_utils.uniquify(iterable, keyfunc=None, last=False) \uf0c1 Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x seen = set() if last: unique_reversed_list = [] for obj in reversed(iterable): key = keyfunc(obj) if key not in seen: seen.add(key) unique_reversed_list.append(obj) yield from reversed(unique_reversed_list) else: for obj in iterable: key = keyfunc(obj) if key not in seen: seen.add(key) yield obj beancount.utils.net_utils \uf0c1 Network utilities. beancount.utils.net_utils.retrying_urlopen(url, timeout=5, max_retry=5) \uf0c1 Open and download the given URL, retrying if it times out. Parameters: url \u2013 A string, the URL to fetch. timeout \u2013 A timeout after which to stop waiting for a response and return an error. max_retry \u2013 The maximum number of times to retry. Returns: The contents of the fetched URL. Source code in beancount/utils/net_utils.py def retrying_urlopen(url, timeout=5, max_retry=5): \"\"\"Open and download the given URL, retrying if it times out. Args: url: A string, the URL to fetch. timeout: A timeout after which to stop waiting for a response and return an error. max_retry: The maximum number of times to retry. Returns: The contents of the fetched URL. \"\"\" for _ in range(max_retry): logging.debug(\"Reading %s\", url) try: response = request.urlopen(url, timeout=timeout) if response: break except error.URLError: return None if response and response.getcode() != 200: return None return response beancount.utils.pager \uf0c1 Code to write output to a pager. This module contains an object accumulates lines up to a minimum and then decides whether to flush them to the original output directly if under the threshold (no pager) or creates a pager and flushes the lines to it if above the threshold and then forwards all future lines to it. The purpose of this object is to pipe output to a pager only if the number of lines to be printed exceeds a minimum number of lines. The contextmanager is intended to be used to pipe output to a pager and wait on the pager to complete before continuing. Simply write to the file object and upon exit we close the file object. This also silences broken pipe errors triggered by the user exiting the sub-process, and recovers from a failing pager command by just using stdout. beancount.utils.pager.ConditionalPager \uf0c1 A proxy file for a pager that only creates a pager after a minimum number of lines has been printed to it. beancount.utils.pager.ConditionalPager.__enter__(self) special \uf0c1 Initialize the context manager and return this instance as it. Source code in beancount/utils/pager.py def __enter__(self): \"\"\"Initialize the context manager and return this instance as it.\"\"\" # The file and pipe object we're writing to. This gets set after the # number of accumulated lines reaches the threshold. if self.minlines: self.file = None self.pipe = None else: self.file, self.pipe = create_pager(self.command, self.default_file) # Lines accumulated before the threshold. self.accumulated_data = [] self.accumulated_lines = 0 # Return this object to be used as the context manager itself. return self beancount.utils.pager.ConditionalPager.__exit__(self, type, value, unused_traceback) special \uf0c1 Context manager exit. This flushes the output to our output file. Parameters: type \u2013 Optional exception type, as per context managers. value \u2013 Optional exception value, as per context managers. unused_traceback \u2013 Optional trace. Source code in beancount/utils/pager.py def __exit__(self, type, value, unused_traceback): \"\"\"Context manager exit. This flushes the output to our output file. Args: type: Optional exception type, as per context managers. value: Optional exception value, as per context managers. unused_traceback: Optional trace. \"\"\" try: if self.file: # Flush the output file and close it. self.file.flush() else: # Oops... we never reached the threshold. Flush the accumulated # output to the file. self.flush_accumulated(self.default_file) # Wait for the subprocess (if we have one). if self.pipe: self.file.close() self.pipe.wait() # Absorb broken pipes that may occur on flush or close above. except BrokenPipeError: return True # Absorb broken pipes. if isinstance(value, BrokenPipeError): return True elif value: raise beancount.utils.pager.ConditionalPager.__init__(self, command, minlines=None) special \uf0c1 Create a conditional pager. Parameters: command \u2013 A string, the shell command to run as a pager. minlines \u2013 If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). Source code in beancount/utils/pager.py def __init__(self, command, minlines=None): \"\"\"Create a conditional pager. Args: command: A string, the shell command to run as a pager. minlines: If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). \"\"\" self.command = command self.minlines = minlines self.default_file = (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) beancount.utils.pager.ConditionalPager.flush_accumulated(self, file) \uf0c1 Flush the existing lines to the newly created pager. This also disabled the accumulator. Parameters: file \u2013 A file object to flush the accumulated data to. Source code in beancount/utils/pager.py def flush_accumulated(self, file): \"\"\"Flush the existing lines to the newly created pager. This also disabled the accumulator. Args: file: A file object to flush the accumulated data to. \"\"\" if self.accumulated_data: write = file.write for data in self.accumulated_data: write(data) self.accumulated_data = None self.accumulated_lines = None beancount.utils.pager.ConditionalPager.write(self, data) \uf0c1 Write the data out. Overridden from the file object interface. Parameters: data \u2013 A string, data to write to the output. Source code in beancount/utils/pager.py def write(self, data): \"\"\"Write the data out. Overridden from the file object interface. Args: data: A string, data to write to the output. \"\"\" if self.file is None: # Accumulate the new lines. self.accumulated_lines += data.count('\\n') self.accumulated_data.append(data) # If we've reached the threshold, create a file. if self.accumulated_lines > self.minlines: self.file, self.pipe = create_pager(self.command, self.default_file) self.flush_accumulated(self.file) else: # We've already created a pager subprocess... flush the lines to it. self.file.write(data) # try: # except BrokenPipeError: # # Make sure we don't barf on __exit__(). # self.file = self.pipe = None # raise beancount.utils.pager.create_pager(command, file) \uf0c1 Try to create and return a pager subprocess. Parameters: command \u2013 A string, the shell command to run as a pager. file \u2013 The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. Source code in beancount/utils/pager.py def create_pager(command, file): \"\"\"Try to create and return a pager subprocess. Args: command: A string, the shell command to run as a pager. file: The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. \"\"\" if command is None: command = os.environ.get('PAGER', DEFAULT_PAGER) if not command: command = DEFAULT_PAGER pipe = None # In case of using 'less', make sure the charset is set properly. In theory # you could override this by setting PAGER to \"LESSCHARSET=utf-8 less\" but # this shouldn't affect other programs and is unlikely to cause problems, so # we set it here to make default behavior work for most people (we always # write UTF-8). env = os.environ.copy() env['LESSCHARSET'] = \"utf-8\" try: pipe = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=file, env=env) except OSError as exc: logging.error(\"Invalid pager: {}\".format(exc)) else: stdin_wrapper = io.TextIOWrapper(pipe.stdin, 'utf-8') file = stdin_wrapper return file, pipe beancount.utils.pager.flush_only(fileobj) \uf0c1 A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Parameters: fileobj \u2013 A file object, to remain open after running the context manager. Yields: A context manager that yields this object. Source code in beancount/utils/pager.py @contextlib.contextmanager def flush_only(fileobj): \"\"\"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Args: fileobj: A file object, to remain open after running the context manager. Yields: A context manager that yields this object. \"\"\" try: yield fileobj finally: fileobj.flush() beancount.utils.regexp_utils \uf0c1 Regular expression helpers. beancount.utils.regexp_utils.re_replace_unicode(regexp) \uf0c1 Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Parameters: regexp \u2013 A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. Source code in beancount/utils/regexp_utils.py def re_replace_unicode(regexp): \"\"\"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Args: regexp: A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. \"\"\" for category, rangestr in UNICODE_RANGES.items(): regexp = regexp.replace(r'\\p{' + category + '}', rangestr) return regexp beancount.utils.snoop \uf0c1 Text manipulation utilities. beancount.utils.snoop.Snoop \uf0c1 A snooper callable that just saves the returned values of a function. This is particularly useful for re.match and re.search in conditionals, e.g.:: snoop = Snoop() ... if snoop(re.match(r\"(\\d+)-(\\d+)-(\\d+)\", text)): year, month, date = snoop.value.group(1, 2, 3) Attributes: Name Type Description value The last value snooped from a function call. history If 'maxlen' was specified, the last few values that were snooped. beancount.utils.snoop.Snoop.__call__(self, value) special \uf0c1 Save a value to the snooper. This is meant to wrap a function call. Parameters: value \u2013 The value to push/save. Returns: Value itself. Source code in beancount/utils/snoop.py def __call__(self, value): \"\"\"Save a value to the snooper. This is meant to wrap a function call. Args: value: The value to push/save. Returns: Value itself. \"\"\" self.value = value if self.history is not None: self.history.append(value) return value beancount.utils.snoop.Snoop.__getattr__(self, attr) special \uf0c1 Forward the attribute to the value. Parameters: attr \u2013 A string, the name of the attribute. Returns: The value of the attribute. Source code in beancount/utils/snoop.py def __getattr__(self, attr): \"\"\"Forward the attribute to the value. Args: attr: A string, the name of the attribute. Returns: The value of the attribute. \"\"\" return getattr(self.value, attr) beancount.utils.snoop.Snoop.__init__(self, maxlen=None) special \uf0c1 Create a new snooper. Parameters: maxlen \u2013 If specified, an integer, which enables the saving of that Source code in beancount/utils/snoop.py def __init__(self, maxlen=None): \"\"\"Create a new snooper. Args: maxlen: If specified, an integer, which enables the saving of that number of last values in the history attribute. \"\"\" self.value = None self.history = (collections.deque(maxlen=maxlen) if maxlen else None) beancount.utils.snoop.snoopify(function) \uf0c1 Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. Source code in beancount/utils/snoop.py def snoopify(function): \"\"\"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. \"\"\" @functools.wraps(function) def wrapper(*args, **kw): value = function(*args, **kw) wrapper.value = value return value wrapper.value = None return wrapper beancount.utils.test_utils \uf0c1 Support utilities for testing scripts. beancount.utils.test_utils.RCall ( tuple ) \uf0c1 RCall(args, kwargs, return_value) beancount.utils.test_utils.RCall.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/utils/test_utils.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.utils.test_utils.RCall.__new__(_cls, args, kwargs, return_value) special staticmethod \uf0c1 Create new instance of RCall(args, kwargs, return_value) beancount.utils.test_utils.RCall.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/utils/test_utils.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.utils.test_utils.TestCase ( TestCase ) \uf0c1 beancount.utils.test_utils.TestCase.assertLines(self, text1, text2, message=None) \uf0c1 Compare the lines of text1 and text2, ignoring whitespace. Parameters: text1 \u2013 A string, the expected text. text2 \u2013 A string, the actual text. message \u2013 An optional string message in case the assertion fails. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/utils/test_utils.py def assertLines(self, text1, text2, message=None): \"\"\"Compare the lines of text1 and text2, ignoring whitespace. Args: text1: A string, the expected text. text2: A string, the actual text. message: An optional string message in case the assertion fails. Raises: AssertionError: If the exception fails. \"\"\" clean_text1 = textwrap.dedent(text1.strip()) clean_text2 = textwrap.dedent(text2.strip()) lines1 = [line.strip() for line in clean_text1.splitlines()] lines2 = [line.strip() for line in clean_text2.splitlines()] # Compress all space longer than 4 spaces to exactly 4. # This affords us to be even looser. lines1 = [re.sub(' [ \\t]*', ' ', line) for line in lines1] lines2 = [re.sub(' [ \\t]*', ' ', line) for line in lines2] self.assertEqual(lines1, lines2, message) beancount.utils.test_utils.TestCase.assertOutput(self, expected_text) \uf0c1 Expect text printed to stdout. Parameters: expected_text \u2013 A string, the text that should have been printed to stdout. Exceptions: AssertionError \u2013 If the text differs. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def assertOutput(self, expected_text): \"\"\"Expect text printed to stdout. Args: expected_text: A string, the text that should have been printed to stdout. Raises: AssertionError: If the text differs. \"\"\" with capture() as oss: yield oss self.assertLines(textwrap.dedent(expected_text), oss.getvalue()) beancount.utils.test_utils.call_command(command) \uf0c1 Run the script with a subprocess. Parameters: script_args \u2013 A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). Source code in beancount/utils/test_utils.py def call_command(command): \"\"\"Run the script with a subprocess. Args: script_args: A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). \"\"\" assert isinstance(command, list), command p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return p.returncode, stdout.decode(), stderr.decode() beancount.utils.test_utils.capture(*attributes) \uf0c1 A context manager that captures what's printed to stdout. Parameters: *attributes \u2013 A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. Source code in beancount/utils/test_utils.py def capture(*attributes): \"\"\"A context manager that captures what's printed to stdout. Args: *attributes: A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. \"\"\" if not attributes: attributes = 'stdout' elif len(attributes) == 1: attributes = attributes[0] return patch(sys, attributes, io.StringIO) beancount.utils.test_utils.create_temporary_files(root, contents_map) \uf0c1 Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Parameters: root \u2013 A string, the name of the directory under which to create the files. contents_map \u2013 A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. Source code in beancount/utils/test_utils.py def create_temporary_files(root, contents_map): \"\"\"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Args: root: A string, the name of the directory under which to create the files. contents_map: A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. \"\"\" os.makedirs(root, exist_ok=True) for relative_filename, contents in contents_map.items(): assert not path.isabs(relative_filename) filename = path.join(root, relative_filename) os.makedirs(path.dirname(filename), exist_ok=True) clean_contents = textwrap.dedent(contents.replace('{root}', root)) with open(filename, 'w') as f: f.write(clean_contents) beancount.utils.test_utils.docfile(function, **kwargs) \uf0c1 A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Parameters: function \u2013 A function to decorate. Returns: The decorated function. Source code in beancount/utils/test_utils.py def docfile(function, **kwargs): \"\"\"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Args: function: A function to decorate. Returns: The decorated function. \"\"\" @functools.wraps(function) def new_function(self): allowed = ('buffering', 'encoding', 'newline', 'dir', 'prefix', 'suffix') if any([key not in allowed for key in kwargs]): raise ValueError(\"Invalid kwarg to docfile_extra\") with tempfile.NamedTemporaryFile('w', **kwargs) as file: text = function.__doc__ file.write(textwrap.dedent(text)) file.flush() return function(self, file.name) new_function.__doc__ = None return new_function beancount.utils.test_utils.docfile_extra(**kwargs) \uf0c1 A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile Source code in beancount/utils/test_utils.py def docfile_extra(**kwargs): \"\"\" A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile \"\"\" return functools.partial(docfile, **kwargs) beancount.utils.test_utils.environ(varname, newvalue) \uf0c1 A context manager which pushes varname's value and restores it later. Parameters: varname \u2013 A string, the environ variable name. newvalue \u2013 A string, the desired value. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def environ(varname, newvalue): \"\"\"A context manager which pushes varname's value and restores it later. Args: varname: A string, the environ variable name. newvalue: A string, the desired value. \"\"\" oldvalue = os.environ.get(varname, None) os.environ[varname] = newvalue yield if oldvalue is not None: os.environ[varname] = oldvalue else: del os.environ[varname] beancount.utils.test_utils.find_python_lib() \uf0c1 Return the path to the root of the Python libraries. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_python_lib(): \"\"\"Return the path to the root of the Python libraries. Returns: A string, the root directory. \"\"\" return path.dirname(path.dirname(path.dirname(__file__))) beancount.utils.test_utils.find_repository_root(filename=None) \uf0c1 Return the path to the repository root. Parameters: filename \u2013 A string, the name of a file within the repository. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_repository_root(filename=None): \"\"\"Return the path to the repository root. Args: filename: A string, the name of a file within the repository. Returns: A string, the root directory. \"\"\" if filename is None: filename = __file__ # Support root directory under Bazel. match = re.match(r\"(.*\\.runfiles/beancount)/\", filename) if match: return match.group(1) while not all(path.exists(path.join(filename, sigfile)) for sigfile in ('PKG-INFO', 'COPYING', 'README.rst')): prev_filename = filename filename = path.dirname(filename) if prev_filename == filename: raise ValueError(\"Failed to find the root directory.\") return filename beancount.utils.test_utils.make_failing_importer(*removed_module_names) \uf0c1 Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins. import ', make_failing_importer('setuptools')) def test_... Parameters: removed_module_name \u2013 The name of the module import that should raise an exception. Returns: A decorated test decorator. Source code in beancount/utils/test_utils.py def make_failing_importer(*removed_module_names): \"\"\"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins.__import__', make_failing_importer('setuptools')) def test_... Args: removed_module_name: The name of the module import that should raise an exception. Returns: A decorated test decorator. \"\"\" def failing_import(name, *args, **kwargs): if name in removed_module_names: raise ImportError(\"Could not import {}\".format(name)) return builtins.__import__(name, *args, **kwargs) return failing_import beancount.utils.test_utils.nottest(func) \uf0c1 Make the given function not testable. Source code in beancount/utils/test_utils.py def nottest(func): \"Make the given function not testable.\" func.__test__ = False return func beancount.utils.test_utils.patch(obj, attributes, replacement_type) \uf0c1 A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Parameters: obj \u2013 The object to patch up. attributes \u2013 A string or a sequence of strings, the names of attributes to replace. replacement_type \u2013 A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def patch(obj, attributes, replacement_type): \"\"\"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Args: obj: The object to patch up. attributes: A string or a sequence of strings, the names of attributes to replace. replacement_type: A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. \"\"\" single = isinstance(attributes, str) if single: attributes = [attributes] saved = [] replacements = [] for attribute in attributes: replacement = replacement_type() replacements.append(replacement) saved.append(getattr(obj, attribute)) setattr(obj, attribute, replacement) yield replacements[0] if single else replacements for attribute, saved_attr in zip(attributes, saved): setattr(obj, attribute, saved_attr) beancount.utils.test_utils.record(fun) \uf0c1 Decorates the function to intercept and record all calls and return values. Parameters: fun \u2013 A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. Source code in beancount/utils/test_utils.py def record(fun): \"\"\"Decorates the function to intercept and record all calls and return values. Args: fun: A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. \"\"\" @functools.wraps(fun) def wrapped(*args, **kw): return_value = fun(*args, **kw) wrapped.calls.append(RCall(args, kw, return_value)) return return_value wrapped.calls = [] return wrapped beancount.utils.test_utils.run_with_args(function, args) \uf0c1 Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Parameters: function \u2013 A function object to call with no arguments. argv \u2013 A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. Source code in beancount/utils/test_utils.py def run_with_args(function, args): \"\"\"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Args: function: A function object to call with no arguments. argv: A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. \"\"\" saved_argv = sys.argv saved_handlers = logging.root.handlers try: module = sys.modules[function.__module__] sys.argv = [module.__file__] + args logging.root.handlers = [] return function() finally: sys.argv = saved_argv logging.root.handlers = saved_handlers beancount.utils.test_utils.search_words(words, line) \uf0c1 Search for a sequence of words in a line. Parameters: words \u2013 A list of strings, the words to look for, or a space-separated string. line \u2013 A string, the line to search into. Returns: A MatchObject, or None. Source code in beancount/utils/test_utils.py def search_words(words, line): \"\"\"Search for a sequence of words in a line. Args: words: A list of strings, the words to look for, or a space-separated string. line: A string, the line to search into. Returns: A MatchObject, or None. \"\"\" if isinstance(words, str): words = words.split() return re.search('.*'.join(r'\\b{}\\b'.format(word) for word in words), line) beancount.utils.test_utils.skipIfRaises(*exc_types) \uf0c1 A context manager (or decorator) that skips a test if an exception is raised. Yields: Nothing, for you to execute the function code. Exceptions: SkipTest \u2013 if the test raised the expected exception. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def skipIfRaises(*exc_types): \"\"\"A context manager (or decorator) that skips a test if an exception is raised. Args: exc_type Yields: Nothing, for you to execute the function code. Raises: SkipTest: if the test raised the expected exception. \"\"\" try: yield except exc_types as exception: raise unittest.SkipTest(exception) beancount.utils.test_utils.subprocess_env() \uf0c1 Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def subprocess_env(): \"\"\"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. \"\"\" # Ensure we have locations to invoke our Python executable and our # runnable binaries in the test environment to run subprocesses. binpath = ':'.join([ path.dirname(sys.executable), path.join(find_repository_root(__file__), 'bin'), os.environ.get('PATH', '').strip(':')]).strip(':') return {'PATH': binpath, 'PYTHONPATH': find_python_lib()} beancount.utils.test_utils.tempdir(delete=True, **kw) \uf0c1 A context manager that creates a temporary directory and deletes its contents unconditionally once done. Parameters: delete \u2013 A boolean, true if we want to delete the directory after running. **kw \u2013 Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def tempdir(delete=True, **kw): \"\"\"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Args: delete: A boolean, true if we want to delete the directory after running. **kw: Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. \"\"\" tempdir = tempfile.mkdtemp(prefix=\"beancount-test-tmpdir.\", **kw) try: yield tempdir finally: if delete: shutil.rmtree(tempdir, ignore_errors=True) beancount.utils.text_utils \uf0c1 Text manipulation utilities. beancount.utils.text_utils.entitize_ampersand(filename) \uf0c1 Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Parameters: filename \u2013 A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. Source code in beancount/utils/text_utils.py def entitize_ampersand(filename): \"\"\"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Args: filename: A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. \"\"\" tidy_file = tempfile.NamedTemporaryFile(suffix='.xls', mode='w', delete=False) with open(filename) as infile: contents = infile.read() new_contents = re.sub('&([^;&]{12})', '&\\\\1', contents) tidy_file.write(new_contents) tidy_file.flush() return tidy_file beancount.utils.text_utils.replace_number(match) \uf0c1 Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Parameters: match \u2013 A MatchObject. Returns: A replacement string, consisting only of X'es. Source code in beancount/utils/text_utils.py def replace_number(match): \"\"\"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Args: match: A MatchObject. Returns: A replacement string, consisting only of X'es. \"\"\" return re.sub('[0-9]', 'X', match.group(1)) + match.group(2) beancount.utils.text_utils.replace_numbers(text) \uf0c1 Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Parameters: text \u2013 An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. Source code in beancount/utils/text_utils.py def replace_numbers(text): \"\"\"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Args: text: An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. \"\"\" return re.sub(r'\\b([0-9,]+(?:\\.[0-9]*)?)\\b([ \\t<]+[^0-9,.]|$)', replace_number, text) beancount.utils.version \uf0c1 Implement common options across all programs. beancount.utils.version.ArgumentParser(*args, **kwargs) \uf0c1 Add a standard --version option to an ArgumentParser. Parameters: *args \u2013 Arguments for the parser. *kwargs \u2013 Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. Source code in beancount/utils/version.py def ArgumentParser(*args, **kwargs): \"\"\"Add a standard --version option to an ArgumentParser. Args: *args: Arguments for the parser. *kwargs: Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. \"\"\" parser = argparse.ArgumentParser(*args, **kwargs) parser.add_argument('--version', '-V', action='version', version=compute_version_string( beancount.__version__, _parser.__vc_changeset__, _parser.__vc_timestamp__)) return parser beancount.utils.version.compute_version_string(version, changeset, timestamp) \uf0c1 Compute a version string from the changeset and timestamp baked in the parser. Parameters: version \u2013 A string, the version number. changeset \u2013 A string, a version control string identifying the commit of the version. timestamp \u2013 An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. Source code in beancount/utils/version.py def compute_version_string(version, changeset, timestamp): \"\"\"Compute a version string from the changeset and timestamp baked in the parser. Args: version: A string, the version number. changeset: A string, a version control string identifying the commit of the version. timestamp: An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. \"\"\" # Shorten changeset. if changeset: if re.match('hg:', changeset): changeset = changeset[:15] elif re.match('git:', changeset): changeset = changeset[:12] # Convert timestamp to a date. date = None if timestamp > 0: date = datetime.datetime.utcfromtimestamp(timestamp).date() version = 'Beancount {}'.format(version) if changeset or date: version = '{} ({})'.format( version, '; '.join(map(str, filter(None, [changeset, date])))) return version","title":"beancount.utils"},{"location":"api_reference/beancount.utils.html#beancountutils","text":"Generic utility packages and functions.","title":"beancount.utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key","text":"A version of bisect that accepts a custom key function, like the sorting ones do.","title":"bisect_key"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key.bisect_left_with_key","text":"Find the last element before the given value in a sorted list. Parameters: sequence \u2013 A sorted sequence of elements. value \u2013 The value to search for. key \u2013 An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. Source code in beancount/utils/bisect_key.py def bisect_left_with_key(sequence, value, key=None): \"\"\"Find the last element before the given value in a sorted list. Args: sequence: A sorted sequence of elements. value: The value to search for. key: An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. \"\"\" # pylint: disable=invalid-name if key is None: key = lambda x: x # Identity. lo = 0 hi = len(sequence) while lo < hi: mid = (lo + hi) // 2 if key(sequence[mid]) < value: lo = mid + 1 else: hi = mid return lo","title":"bisect_left_with_key()"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key.bisect_right_with_key","text":"Like bisect.bisect_right, but with a key lookup parameter. Parameters: a \u2013 The list to search in. x \u2013 The element to search for. key \u2013 A function, to extract the value from the list. lo \u2013 The smallest index to search. hi \u2013 The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. Source code in beancount/utils/bisect_key.py def bisect_right_with_key(a, x, key, lo=0, hi=None): \"\"\"Like bisect.bisect_right, but with a key lookup parameter. Args: a: The list to search in. x: The element to search for. key: A function, to extract the value from the list. lo: The smallest index to search. hi: The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. \"\"\" # pylint: disable=invalid-name if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 if x < key(a[mid]): hi = mid else: lo = mid+1 return lo","title":"bisect_right_with_key()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils","text":"Utilities for reading and writing CSV files.","title":"csv_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.as_rows","text":"Split a string as rows of a CSV file. Parameters: string \u2013 A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. Source code in beancount/utils/csv_utils.py def as_rows(string): \"\"\"Split a string as rows of a CSV file. Args: string: A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. \"\"\" return list(csv.reader(io.StringIO(textwrap.dedent(string))))","title":"as_rows()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_clean_header","text":"Create a new class for the following rows from the header line. This both normalizes the header line and assign Parameters: header_row \u2013 A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. Source code in beancount/utils/csv_utils.py def csv_clean_header(header_row): \"\"\"Create a new class for the following rows from the header line. This both normalizes the header line and assign Args: header_row: A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. \"\"\" fieldnames = [] for index, column in enumerate(header_row): field = column.lower() field = re.sub(r'\\bp/l\\b', 'pnl', field) field = re.sub('[^a-z0-9]', '_', field) field = field.strip(' _') field = re.sub('__+', '_', field) if not field: field = 'col{}'.format(index) assert field not in fieldnames, field fieldnames.append(field) return fieldnames","title":"csv_clean_header()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_dict_reader","text":"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. Source code in beancount/utils/csv_utils.py def csv_dict_reader(fileobj, **kw): \"\"\"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. \"\"\" reader = csv.DictReader(fileobj, **kw) reader.fieldnames = csv_clean_header(reader.fieldnames) return reader","title":"csv_dict_reader()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_split_sections","text":"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Parameters: rows \u2013 A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. Source code in beancount/utils/csv_utils.py def csv_split_sections(rows): \"\"\"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Args: rows: A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. \"\"\" sections = [] current_section = [] for row in rows: if row: current_section.append(row) else: sections.append(current_section) current_section = [] if current_section: sections.append(current_section) return sections","title":"csv_split_sections()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_split_sections_with_titles","text":"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Parameters: rows \u2013 A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). Source code in beancount/utils/csv_utils.py def csv_split_sections_with_titles(rows): \"\"\"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Args: rows: A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). \"\"\" sections_map = {} for index, section in enumerate(csv_split_sections(rows)): # Skip too short sections, cannot possibly be a title. if len(section) < 2: continue if len(section[0]) == 1 and len(section[1]) != 1: name = section[0][0] section = section[1:] else: name = 'Section {}'.format(index) sections_map[name] = section return sections_map","title":"csv_split_sections_with_titles()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_tuple_reader","text":"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. Source code in beancount/utils/csv_utils.py def csv_tuple_reader(fileobj, **kw): \"\"\"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. \"\"\" reader = csv.reader(fileobj, **kw) ireader = iter(reader) fieldnames = csv_clean_header(next(ireader)) Tuple = collections.namedtuple('Row', fieldnames) for row in ireader: try: yield Tuple(*row) except TypeError: # If there's an error, it's usually from a line that has a 'END OF # LINE' marker at the end, or some comment line. assert len(row) in (0, 1)","title":"csv_tuple_reader()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.iter_sections","text":"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Parameters: fileobj \u2013 A file object to read from. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. Source code in beancount/utils/csv_utils.py def iter_sections(fileobj, separating_predicate=None): \"\"\"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Args: fileobj: A file object to read from. separating_predicate: A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) lineiter = iter(fileobj) for line in lineiter: if separating_predicate(line): iterator = itertools.chain((line,), iter_until_empty(lineiter, separating_predicate)) yield iterator for _ in iterator: pass","title":"iter_sections()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.iter_until_empty","text":"An iterator of lines that will stop at the first empty line. Parameters: iterator \u2013 An iterator of lines. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. Source code in beancount/utils/csv_utils.py def iter_until_empty(iterator, separating_predicate=None): \"\"\"An iterator of lines that will stop at the first empty line. Args: iterator: An iterator of lines. separating_predicate: A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) for line in iterator: if not separating_predicate(line): break yield line","title":"iter_until_empty()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils","text":"Parse the date from various formats.","title":"date_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.intimezone","text":"Temporarily reset the value of TZ. This is used for testing. Parameters: tz_value ( str ) \u2013 The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. Source code in beancount/utils/date_utils.py @contextlib.contextmanager def intimezone(tz_value: str): \"\"\"Temporarily reset the value of TZ. This is used for testing. Args: tz_value: The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. \"\"\" tz_old = os.environ.get('TZ', None) os.environ['TZ'] = tz_value time.tzset() try: yield finally: if tz_old is None: del os.environ['TZ'] else: os.environ['TZ'] = tz_old time.tzset()","title":"intimezone()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.iter_dates","text":"Yield all the dates between 'start_date' and 'end_date'. Parameters: start_date \u2013 An instance of datetime.date. end_date \u2013 An instance of datetime.date. Yields: Instances of datetime.date. Source code in beancount/utils/date_utils.py def iter_dates(start_date, end_date): \"\"\"Yield all the dates between 'start_date' and 'end_date'. Args: start_date: An instance of datetime.date. end_date: An instance of datetime.date. Yields: Instances of datetime.date. \"\"\" oneday = datetime.timedelta(days=1) date = start_date while date < end_date: yield date date += oneday","title":"iter_dates()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.next_month","text":"Compute the date at the beginning of the following month from the given date. Parameters: date \u2013 A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. Source code in beancount/utils/date_utils.py def next_month(date): \"\"\"Compute the date at the beginning of the following month from the given date. Args: date: A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. \"\"\" # Compute the date at the beginning of the following month. year = date.year month = date.month + 1 if date.month == 12: year += 1 month = 1 return datetime.date(year, month, 1)","title":"next_month()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.parse_date_liberally","text":"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Parameters: string \u2013 A string to parse. parse_kwargs_dict \u2013 Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. Source code in beancount/utils/date_utils.py def parse_date_liberally(string, parse_kwargs_dict=None): \"\"\"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Args: string: A string to parse. parse_kwargs_dict: Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. \"\"\" # At the moment, rely on the most excellent dateutil. if parse_kwargs_dict is None: parse_kwargs_dict = {} return dateutil.parser.parse(string, **parse_kwargs_dict).date()","title":"parse_date_liberally()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.render_ofx_date","text":"Render a datetime to the OFX format. Parameters: dtime \u2013 A datetime.datetime instance. Returns: A string, rendered to milliseconds. Source code in beancount/utils/date_utils.py def render_ofx_date(dtime): \"\"\"Render a datetime to the OFX format. Args: dtime: A datetime.datetime instance. Returns: A string, rendered to milliseconds. \"\"\" return '{}.{:03d}'.format(dtime.strftime('%Y%m%d%H%M%S'), int(dtime.microsecond / 1000))","title":"render_ofx_date()"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict","text":"An instance of collections.defaultdict whose factory accepts a key. Note: This really ought to be an enhancement to Python itself. I should bother adding this in eventually.","title":"defdict"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.DefaultDictWithKey","text":"A version of defaultdict whose factory accepts the key as an argument. Note: collections.defaultdict would be improved by supporting this directly, this is a common occurrence.","title":"DefaultDictWithKey"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault","text":"An immutable dict which returns a default value for missing keys. This differs from a defaultdict in that it does not insert a missing default value when one is materialized (from a missing fetch), and furthermore, the set method is make unavailable to prevent mutation beyond construction.","title":"ImmutableDictWithDefault"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault.__setitem__","text":"Disallow mutating the dict in the usual way. Source code in beancount/utils/defdict.py def __setitem__(self, key, value): \"\"\"Disallow mutating the dict in the usual way.\"\"\" raise NotImplementedError","title":"__setitem__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault.get","text":"Return the value for key if key is in the dictionary, else default. Source code in beancount/utils/defdict.py def get(self, key, _=None): return self.__getitem__(key)","title":"get()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption","text":"Support for encrypted tests.","title":"encryption"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.is_encrypted_file","text":"Return true if the given filename contains an encrypted file. Parameters: filename \u2013 A path string. Returns: A boolean, true if the file contains an encrypted file. Source code in beancount/utils/encryption.py def is_encrypted_file(filename): \"\"\"Return true if the given filename contains an encrypted file. Args: filename: A path string. Returns: A boolean, true if the file contains an encrypted file. \"\"\" _, ext = path.splitext(filename) if ext == '.gpg': return True if ext == '.asc': with open(filename) as encfile: head = encfile.read(1024) if re.search('--BEGIN PGP MESSAGE--', head): return True return False","title":"is_encrypted_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.is_gpg_installed","text":"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support. Source code in beancount/utils/encryption.py def is_gpg_installed(): \"\"\"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support.\"\"\" try: pipe = subprocess.Popen(['gpg', '--version'], shell=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = pipe.communicate() version_text = out.decode('utf8') return pipe.returncode == 0 and re.match(r'gpg \\(GnuPG\\) (1\\.4|2)\\.', version_text) except OSError: return False","title":"is_gpg_installed()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.read_encrypted_file","text":"Decrypt and read an encrypted file without temporary storage. Parameters: filename \u2013 A string, the path to the encrypted file. Returns: A string, the contents of the file. Exceptions: OSError \u2013 If we could not properly decrypt the file. Source code in beancount/utils/encryption.py def read_encrypted_file(filename): \"\"\"Decrypt and read an encrypted file without temporary storage. Args: filename: A string, the path to the encrypted file. Returns: A string, the contents of the file. Raises: OSError: If we could not properly decrypt the file. \"\"\" command = ['gpg', '--batch', '--decrypt', path.realpath(filename)] pipe = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) contents, errors = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Could not decrypt file ({}): {}\".format(pipe.returncode, errors.decode('utf8'))) return contents.decode('utf-8')","title":"read_encrypted_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_type","text":"Code that can guess a MIME type for a filename. This attempts to identify the mime-type of a file suing The built-in mimetypes library, then python-magic (if available), and finally some custom mappers for datatypes used in financial downloads, such as Quicken files.","title":"file_type"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_type.guess_file_type","text":"Attempt to guess the type of the input file. Parameters: filename \u2013 A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. Source code in beancount/utils/file_type.py def guess_file_type(filename): \"\"\"Attempt to guess the type of the input file. Args: filename: A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. \"\"\" # Try the standard mimetypes association. filetype, _ = mimetypes.guess_type(filename, False) if filetype: return filetype # Try out some extra types that only we know about. for regexp, mimetype in EXTRA_FILE_TYPES: if regexp.match(filename): return mimetype # Try out libmagic, if it is installed. if magic: filetype = magic.from_file(filename, mime=True) if isinstance(filetype, bytes): filetype = filetype.decode('utf8') return filetype else: raise ValueError((\"Could not identify the type of file '{}'; \" \"try installing python-magic\").format(filename))","title":"guess_file_type()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils","text":"File utilities.","title":"file_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.chdir","text":"Temporarily chdir to the given directory. Parameters: directory \u2013 The directory to switch do. Returns: A context manager which restores the cwd after running. Source code in beancount/utils/file_utils.py @contextlib.contextmanager def chdir(directory): \"\"\"Temporarily chdir to the given directory. Args: directory: The directory to switch do. Returns: A context manager which restores the cwd after running. \"\"\" cwd = os.getcwd() os.chdir(directory) try: yield cwd finally: os.chdir(cwd)","title":"chdir()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.find_files","text":"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Parameters: fords \u2013 A list of strings, file or directory names. ignore_dirs \u2013 A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. Source code in beancount/utils/file_utils.py def find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)): \"\"\"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Args: fords: A list of strings, file or directory names. ignore_dirs: A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. \"\"\" if isinstance(fords, str): fords = [fords] assert isinstance(fords, (list, tuple)) for ford in fords: if path.isdir(ford): for root, dirs, filenames in os.walk(ford): dirs[:] = sorted(dirname for dirname in dirs if dirname not in ignore_dirs) for filename in sorted(filenames): if filename in ignore_files: continue yield path.join(root, filename) elif path.isfile(ford) or path.islink(ford): yield ford elif not path.exists(ford): logging.error(\"File or directory '{}' does not exist.\".format(ford))","title":"find_files()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.guess_file_format","text":"Guess the file format from the filename. Parameters: filename \u2013 A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. Source code in beancount/utils/file_utils.py def guess_file_format(filename, default=None): \"\"\"Guess the file format from the filename. Args: filename: A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. \"\"\" if filename: if filename.endswith('.txt') or filename.endswith('.text'): format = 'text' elif filename.endswith('.csv'): format = 'csv' elif filename.endswith('.html') or filename.endswith('.xhtml'): format = 'html' else: format = default else: format = default return format","title":"guess_file_format()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.path_greedy_split","text":"Split a path, returning the longest possible extension. Parameters: filename \u2013 A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). Source code in beancount/utils/file_utils.py def path_greedy_split(filename): \"\"\"Split a path, returning the longest possible extension. Args: filename: A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). \"\"\" basename = path.basename(filename) index = basename.find('.') if index == -1: extension = None else: extension = basename[index:] basename = basename[:index] return (path.join(path.dirname(filename), basename), extension)","title":"path_greedy_split()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.touch_file","text":"Touch a file and wait until its timestamp has been changed. Parameters: filename \u2013 A string path, the name of the file to touch. otherfiles \u2013 A list of other files to ensure the timestamp is beyond of. Source code in beancount/utils/file_utils.py def touch_file(filename, *otherfiles): \"\"\"Touch a file and wait until its timestamp has been changed. Args: filename: A string path, the name of the file to touch. otherfiles: A list of other files to ensure the timestamp is beyond of. \"\"\" # Note: You could set os.stat_float_times() but then the main function would # have to set that up as well. It doesn't help so much, however, since # filesystems tend to have low resolutions, e.g. one second. orig_mtime_ns = max(os.stat(minfile).st_mtime_ns for minfile in (filename,) + otherfiles) delay_secs = 0.05 while True: with open(filename, 'a'): os.utime(filename) time.sleep(delay_secs) new_stat = os.stat(filename) if new_stat.st_mtime_ns > orig_mtime_ns: break","title":"touch_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.import_utils","text":"Utilities for importing symbols programmatically.","title":"import_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.import_utils.import_symbol","text":"Import a symbol in an arbitrary module. Parameters: dotted_name \u2013 A dotted path to a symbol. Returns: The object referenced by the given name. Exceptions: ImportError \u2013 If the module not not be imported. AttributeError \u2013 If the symbol could not be found in the module. Source code in beancount/utils/import_utils.py def import_symbol(dotted_name): \"\"\"Import a symbol in an arbitrary module. Args: dotted_name: A dotted path to a symbol. Returns: The object referenced by the given name. Raises: ImportError: If the module not not be imported. AttributeError: If the symbol could not be found in the module. \"\"\" comps = dotted_name.split('.') module_name = '.'.join(comps[:-1]) symbol_name = comps[-1] module = importlib.import_module(module_name) return getattr(module, symbol_name)","title":"import_symbol()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants","text":"Functions to register auxiliary functions on a class' methods to check for invariants. This is intended to be used in a test, whereby your test will setup a class to automatically run invariant verification functions before and after each function call, to ensure some extra sanity checks that wouldn't be used in non-tests. Example: Instrument the Inventory class with the check_inventory_invariants() function. def setUp(module): instrument_invariants(Inventory, check_inventory_invariants, check_inventory_invariants) def tearDown(module): uninstrument_invariants(Inventory)","title":"invariants"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.instrument_invariants","text":"Instrument the class 'klass' with pre/post invariant checker functions. Parameters: klass \u2013 A class object, whose methods to be instrumented. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants pre-call. Source code in beancount/utils/invariants.py def instrument_invariants(klass, prefun, postfun): \"\"\"Instrument the class 'klass' with pre/post invariant checker functions. Args: klass: A class object, whose methods to be instrumented. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants pre-call. \"\"\" instrumented = {} for attrname, object_ in klass.__dict__.items(): if attrname.startswith('_'): continue if not isinstance(object_, types.FunctionType): continue instrumented[attrname] = object_ setattr(klass, attrname, invariant_check(object_, prefun, postfun)) klass.__instrumented = instrumented","title":"instrument_invariants()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.invariant_check","text":"Decorate a method with the pre/post invariant checkers. Parameters: method \u2013 An unbound method to instrument. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants post-call. Returns: An unbound method, decorated. Source code in beancount/utils/invariants.py def invariant_check(method, prefun, postfun): \"\"\"Decorate a method with the pre/post invariant checkers. Args: method: An unbound method to instrument. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants post-call. Returns: An unbound method, decorated. \"\"\" reentrant = [] def new_method(self, *args, **kw): reentrant.append(None) if len(reentrant) == 1: prefun(self) result = method(self, *args, **kw) if len(reentrant) == 1: postfun(self) reentrant.pop() return result return new_method","title":"invariant_check()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.uninstrument_invariants","text":"Undo the instrumentation for invariants. Parameters: klass \u2013 A class object, whose methods to be uninstrumented. Source code in beancount/utils/invariants.py def uninstrument_invariants(klass): \"\"\"Undo the instrumentation for invariants. Args: klass: A class object, whose methods to be uninstrumented. \"\"\" instrumented = getattr(klass, '__instrumented', None) if instrumented: for attrname, object_ in instrumented.items(): setattr(klass, attrname, object_) del klass.__instrumented","title":"uninstrument_invariants()"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo","text":"Memoization utilities.","title":"memo"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo.memoize_recent_fileobj","text":"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Parameters: function \u2013 A callable object. cache_filename \u2013 A string, the path to the database file to cache to. expiration \u2013 The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. Source code in beancount/utils/memo.py def memoize_recent_fileobj(function, cache_filename, expiration=None): \"\"\"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Args: function: A callable object. cache_filename: A string, the path to the database file to cache to. expiration: The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. \"\"\" urlcache = shelve.open(cache_filename, 'c') urlcache.lock = threading.Lock() # Note: 'shelve' is not thread-safe. @functools.wraps(function) def memoized(*args, **kw): # Encode the arguments, including a date string in order to invalidate # results over some time. md5 = hashlib.md5() md5.update(str(args).encode('utf-8')) md5.update(str(sorted(kw.items())).encode('utf-8')) hash_ = md5.hexdigest() time_now = now() try: with urlcache.lock: time_orig, contents = urlcache[hash_] if expiration is not None and (time_now - time_orig) > expiration: raise KeyError except KeyError: fileobj = function(*args, **kw) if fileobj: contents = fileobj.read() with urlcache.lock: urlcache[hash_] = (time_now, contents) else: contents = None return io.BytesIO(contents) if contents else None return memoized","title":"memoize_recent_fileobj()"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo.now","text":"Indirection on datetime.datetime.now() for testing. Source code in beancount/utils/memo.py def now(): \"Indirection on datetime.datetime.now() for testing.\" return datetime.datetime.now()","title":"now()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils","text":"Generic utility packages and functions.","title":"misc_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy","text":"A file object that will delegate writing full lines to another logging function. This may be used for writing data to a logging level without having to worry about lines.","title":"LineFileProxy"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.__init__","text":"Construct a new line delegator file object proxy. Parameters: line_writer \u2013 A callable function, used to write to the delegated output. prefix \u2013 An optional string, the prefix to insert before every line. write_newlines \u2013 A boolean, true if we should output the newline characters. Source code in beancount/utils/misc_utils.py def __init__(self, line_writer, prefix=None, write_newlines=False): \"\"\"Construct a new line delegator file object proxy. Args: line_writer: A callable function, used to write to the delegated output. prefix: An optional string, the prefix to insert before every line. write_newlines: A boolean, true if we should output the newline characters. \"\"\" self.line_writer = line_writer self.prefix = prefix self.write_newlines = write_newlines self.data = []","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.close","text":"Close the line delegator. Source code in beancount/utils/misc_utils.py def close(self): \"\"\"Close the line delegator.\"\"\" self.flush()","title":"close()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.flush","text":"Flush the data to the line writer. Source code in beancount/utils/misc_utils.py def flush(self): \"\"\"Flush the data to the line writer.\"\"\" data = ''.join(self.data) if data: lines = data.splitlines() self.data = [lines.pop(-1)] if data[-1] != '\\n' else [] for line in lines: if self.prefix: line = self.prefix + line if self.write_newlines: line += '\\n' self.line_writer(line)","title":"flush()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.write","text":"Write some string data to the output. Parameters: data \u2013 A string, with or without newlines. Source code in beancount/utils/misc_utils.py def write(self, data): \"\"\"Write some string data to the output. Args: data: A string, with or without newlines. \"\"\" if '\\n' in data: self.data.append(data) self.flush() else: self.data.append(data)","title":"write()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.TypeComparable","text":"A base class whose equality comparison includes comparing the type of the instance itself.","title":"TypeComparable"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.box","text":"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Parameters: name \u2013 A string, the name of the box to use. file \u2013 The file object to print to. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def box(name=None, file=None): \"\"\"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Args: name: A string, the name of the box to use. file: The file object to print to. Yields: None. \"\"\" file = file or sys.stdout file.write('\\n') if name: header = ',--------({})--------\\n'.format(name) footer = '`{}\\n'.format('-' * (len(header)-2)) else: header = ',----------------\\n' footer = '`----------------\\n' file.write(header) yield file.write(footer) file.flush()","title":"box()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.cmptuple","text":"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Parameters: name \u2013 The given name of the class. attributes \u2013 A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. Source code in beancount/utils/misc_utils.py def cmptuple(name, attributes): \"\"\"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Args: name: The given name of the class. attributes: A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. \"\"\" base = collections.namedtuple('_{}'.format(name), attributes) return type(name, (TypeComparable, base,), {})","title":"cmptuple()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.compute_unique_clean_ids","text":"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Parameters: strings \u2013 A list of strings. Returns: A list of (id, string) pairs. Source code in beancount/utils/misc_utils.py def compute_unique_clean_ids(strings): \"\"\"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Args: strings: A list of strings. Returns: A list of (id, string) pairs. \"\"\" string_set = set(strings) # Try multiple methods until we get one that has no collisions. for regexp, replacement in [(r'[^A-Za-z0-9.-]', '_'), (r'[^A-Za-z0-9_]', ''),]: seen = set() idmap = {} mre = re.compile(regexp) for string in string_set: id_ = mre.sub(replacement, string) if id_ in seen: break # Collision. seen.add(id_) idmap[id_] = string else: break else: return None # Could not find a unique mapping. return idmap","title":"compute_unique_clean_ids()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.deprecated","text":"A decorator generator to mark functions as deprecated and log a warning. Source code in beancount/utils/misc_utils.py def deprecated(message): \"\"\"A decorator generator to mark functions as deprecated and log a warning.\"\"\" def decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn(\"Call to deprecated function {}: {}\".format(func.__name__, message), category=DeprecationWarning, stacklevel=2) return func(*args, **kwargs) return new_func return decorator","title":"deprecated()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.dictmap","text":"Map a dictionary's value. Parameters: mdict \u2013 A dict. key \u2013 A callable to apply to the keys. value \u2013 A callable to apply to the values. Source code in beancount/utils/misc_utils.py def dictmap(mdict, keyfun=None, valfun=None): \"\"\"Map a dictionary's value. Args: mdict: A dict. key: A callable to apply to the keys. value: A callable to apply to the values. \"\"\" if keyfun is None: keyfun = lambda x: x if valfun is None: valfun = lambda x: x return {keyfun(key): valfun(val) for key, val in mdict.items()}","title":"dictmap()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.escape_string","text":"Escape quotes and backslashes in payee and narration. Parameters: string \u2013 Any string. Returns. The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def escape_string(string): \"\"\"Escape quotes and backslashes in payee and narration. Args: string: Any string. Returns. The input string, with offending characters replaced. \"\"\" return string.replace('\\\\', r'\\\\')\\ .replace('\"', r'\\\"')","title":"escape_string()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.filter_type","text":"Filter the given list to yield only instances of the given types. Parameters: elist \u2013 A sequence of elements. types \u2013 A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. Source code in beancount/utils/misc_utils.py def filter_type(elist, types): \"\"\"Filter the given list to yield only instances of the given types. Args: elist: A sequence of elements. types: A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. \"\"\" for element in elist: if not isinstance(element, types): continue yield element","title":"filter_type()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.first_paragraph","text":"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Parameters: docstring \u2013 A doc string. Returns: A string with just the first sentence on a single line. Source code in beancount/utils/misc_utils.py def first_paragraph(docstring): \"\"\"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Args: docstring: A doc string. Returns: A string with just the first sentence on a single line. \"\"\" lines = [] for line in docstring.strip().splitlines(): if not line: break lines.append(line.rstrip()) return ' '.join(lines)","title":"first_paragraph()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_screen_height","text":"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_height(): \"\"\"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('lines', 0)","title":"get_screen_height()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_screen_width","text":"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_width(): \"\"\"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('cols', 0)","title":"get_screen_width()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_tuple_values","text":"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Parameters: ntuple \u2013 A tuple or namedtuple. predicate \u2013 A predicate function that returns true if an attribute is to be output. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def get_tuple_values(ntuple, predicate, memo=None): \"\"\"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Args: ntuple: A tuple or namedtuple. predicate: A predicate function that returns true if an attribute is to be output. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return memo.add(id_ntuple) if predicate(ntuple): yield for attribute in ntuple: if predicate(attribute): yield attribute if isinstance(attribute, (list, tuple)): for value in get_tuple_values(attribute, predicate, memo): yield value","title":"get_tuple_values()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.groupby","text":"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Parameters: keyfun \u2013 A callable, used to obtain the group key from each element. elements \u2013 An iterable of the elements to group. Returns: A dict of key to list of sequences. Source code in beancount/utils/misc_utils.py def groupby(keyfun, elements): \"\"\"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Args: keyfun: A callable, used to obtain the group key from each element. elements: An iterable of the elements to group. Returns: A dict of key to list of sequences. \"\"\" # Note: We could allow a custom aggregation function. Another option is # provide another method to reduce the list values of a dict, but that can # be accomplished using a dict comprehension. grouped = defaultdict(list) for element in elements: grouped[keyfun(element)].append(element) return grouped","title":"groupby()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.idify","text":"Replace characters objectionable for a filename with underscores. Parameters: string \u2013 Any string. Returns: The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def idify(string): \"\"\"Replace characters objectionable for a filename with underscores. Args: string: Any string. Returns: The input string, with offending characters replaced. \"\"\" for sfrom, sto in [(r'[ \\(\\)]+', '_'), (r'_*\\._*', '.')]: string = re.sub(sfrom, sto, string) string = string.strip('_') return string","title":"idify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.import_curses","text":"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Exceptions: ImportError \u2013 If the module could not be imported. Source code in beancount/utils/misc_utils.py def import_curses(): \"\"\"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Raises: ImportError: If the module could not be imported. \"\"\" # Note: There's a recipe for getting terminal size on Windows here, without # curses, I should probably implement that at some point: # https://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window # Also, consider just using 'blessings' instead, which provides this across # multiple platforms. # pylint: disable=import-outside-toplevel import curses return curses","title":"import_curses()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.is_sorted","text":"Return true if the sequence is sorted. Parameters: iterable \u2013 An iterable sequence. key \u2013 A function to extract the quantity by which to sort. cmp \u2013 A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. Source code in beancount/utils/misc_utils.py def is_sorted(iterable, key=lambda x: x, cmp=lambda x, y: x <= y): \"\"\"Return true if the sequence is sorted. Args: iterable: An iterable sequence. key: A function to extract the quantity by which to sort. cmp: A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. \"\"\" iterator = map(key, iterable) prev = next(iterator) for element in iterator: if not cmp(prev, element): return False prev = element return True","title":"is_sorted()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.log_time","text":"A context manager that times the block and logs it to info level. Parameters: operation_name \u2013 A string, a label for the name of the operation. log_timings \u2013 A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent \u2013 An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def log_time(operation_name, log_timings, indent=0): \"\"\"A context manager that times the block and logs it to info level. Args: operation_name: A string, a label for the name of the operation. log_timings: A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent: An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. \"\"\" time1 = time() yield time1 time2 = time() if log_timings: log_timings(\"Operation: {:48} Time: {}{:6.0f} ms\".format( \"'{}'\".format(operation_name), ' '*indent, (time2 - time1) * 1000))","title":"log_time()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.longest","text":"Return the longest of the given subsequences. Parameters: seq \u2013 An iterable sequence of lists. Returns: The longest list from the sequence. Source code in beancount/utils/misc_utils.py def longest(seq): \"\"\"Return the longest of the given subsequences. Args: seq: An iterable sequence of lists. Returns: The longest list from the sequence. \"\"\" longest, length = None, -1 for element in seq: len_element = len(element) if len_element > length: longest, length = element, len_element return longest","title":"longest()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.map_namedtuple_attributes","text":"Map the value of the named attributes of object by mapper. Parameters: attributes \u2013 A sequence of string, the attribute names to map. mapper \u2013 A callable that accepts the value of a field and returns the new value. object_ \u2013 Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. Source code in beancount/utils/misc_utils.py def map_namedtuple_attributes(attributes, mapper, object_): \"\"\"Map the value of the named attributes of object by mapper. Args: attributes: A sequence of string, the attribute names to map. mapper: A callable that accepts the value of a field and returns the new value. object_: Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. \"\"\" return object_._replace(**{attribute: mapper(getattr(object_, attribute)) for attribute in attributes})","title":"map_namedtuple_attributes()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.replace_namedtuple_values","text":"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Parameters: ntuple \u2013 A namedtuple instance. predicate \u2013 A predicate function that returns true if an attribute is to be output. mapper \u2013 A callable, that will accept a single argument and return its replacement value. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def replace_namedtuple_values(ntuple, predicate, mapper, memo=None): \"\"\"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Args: ntuple: A namedtuple instance. predicate: A predicate function that returns true if an attribute is to be output. mapper: A callable, that will accept a single argument and return its replacement value. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return None memo.add(id_ntuple) # pylint: disable=unidiomatic-typecheck if not (type(ntuple) is not tuple and isinstance(ntuple, tuple)): return ntuple replacements = {} for attribute_name, attribute in zip(ntuple._fields, ntuple): if predicate(attribute): replacements[attribute_name] = mapper(attribute) elif type(attribute) is not tuple and isinstance(attribute, tuple): replacements[attribute_name] = replace_namedtuple_values( attribute, predicate, mapper, memo) elif type(attribute) in (list, tuple): replacements[attribute_name] = [ replace_namedtuple_values(member, predicate, mapper, memo) for member in attribute] return ntuple._replace(**replacements)","title":"replace_namedtuple_values()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.skipiter","text":"Skip some elements from an iterator. Parameters: iterable \u2013 An iterator. num_skip \u2013 The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. Source code in beancount/utils/misc_utils.py def skipiter(iterable, num_skip): \"\"\"Skip some elements from an iterator. Args: iterable: An iterator. num_skip: The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. \"\"\" assert num_skip > 0 sit = iter(iterable) while 1: try: value = next(sit) except StopIteration: return yield value for _ in range(num_skip-1): try: next(sit) except StopIteration: return","title":"skipiter()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.sorted_uniquify","text":"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does not maintain the ordering of the original elements, they are returned sorted (by key) instead. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def sorted_uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does _not_ maintain the ordering of the original elements, they are returned sorted (by key) instead. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x if last: prev_obj = UNSET prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key and prev_obj is not UNSET: yield prev_obj prev_obj = obj prev_key = key if prev_obj is not UNSET: yield prev_obj else: prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key: yield obj prev_key = key","title":"sorted_uniquify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.staticvar","text":"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Parameters: varname \u2013 A string, the name of the variable to define. initial_value \u2013 The value to initialize the variable to. Returns: A function decorator. Source code in beancount/utils/misc_utils.py def staticvar(varname, initial_value): \"\"\"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Args: varname: A string, the name of the variable to define. initial_value: The value to initialize the variable to. Returns: A function decorator. \"\"\" def deco(fun): setattr(fun, varname, initial_value) return fun return deco","title":"staticvar()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.swallow","text":"Catch and ignore certain exceptions. Parameters: exception_types \u2013 A tuple of exception classes to ignore. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def swallow(*exception_types): \"\"\"Catch and ignore certain exceptions. Args: exception_types: A tuple of exception classes to ignore. Yields: None. \"\"\" try: yield except Exception as exc: if not isinstance(exc, exception_types): raise","title":"swallow()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.uniquify","text":"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x seen = set() if last: unique_reversed_list = [] for obj in reversed(iterable): key = keyfunc(obj) if key not in seen: seen.add(key) unique_reversed_list.append(obj) yield from reversed(unique_reversed_list) else: for obj in iterable: key = keyfunc(obj) if key not in seen: seen.add(key) yield obj","title":"uniquify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.net_utils","text":"Network utilities.","title":"net_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.net_utils.retrying_urlopen","text":"Open and download the given URL, retrying if it times out. Parameters: url \u2013 A string, the URL to fetch. timeout \u2013 A timeout after which to stop waiting for a response and return an error. max_retry \u2013 The maximum number of times to retry. Returns: The contents of the fetched URL. Source code in beancount/utils/net_utils.py def retrying_urlopen(url, timeout=5, max_retry=5): \"\"\"Open and download the given URL, retrying if it times out. Args: url: A string, the URL to fetch. timeout: A timeout after which to stop waiting for a response and return an error. max_retry: The maximum number of times to retry. Returns: The contents of the fetched URL. \"\"\" for _ in range(max_retry): logging.debug(\"Reading %s\", url) try: response = request.urlopen(url, timeout=timeout) if response: break except error.URLError: return None if response and response.getcode() != 200: return None return response","title":"retrying_urlopen()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager","text":"Code to write output to a pager. This module contains an object accumulates lines up to a minimum and then decides whether to flush them to the original output directly if under the threshold (no pager) or creates a pager and flushes the lines to it if above the threshold and then forwards all future lines to it. The purpose of this object is to pipe output to a pager only if the number of lines to be printed exceeds a minimum number of lines. The contextmanager is intended to be used to pipe output to a pager and wait on the pager to complete before continuing. Simply write to the file object and upon exit we close the file object. This also silences broken pipe errors triggered by the user exiting the sub-process, and recovers from a failing pager command by just using stdout.","title":"pager"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager","text":"A proxy file for a pager that only creates a pager after a minimum number of lines has been printed to it.","title":"ConditionalPager"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__enter__","text":"Initialize the context manager and return this instance as it. Source code in beancount/utils/pager.py def __enter__(self): \"\"\"Initialize the context manager and return this instance as it.\"\"\" # The file and pipe object we're writing to. This gets set after the # number of accumulated lines reaches the threshold. if self.minlines: self.file = None self.pipe = None else: self.file, self.pipe = create_pager(self.command, self.default_file) # Lines accumulated before the threshold. self.accumulated_data = [] self.accumulated_lines = 0 # Return this object to be used as the context manager itself. return self","title":"__enter__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__exit__","text":"Context manager exit. This flushes the output to our output file. Parameters: type \u2013 Optional exception type, as per context managers. value \u2013 Optional exception value, as per context managers. unused_traceback \u2013 Optional trace. Source code in beancount/utils/pager.py def __exit__(self, type, value, unused_traceback): \"\"\"Context manager exit. This flushes the output to our output file. Args: type: Optional exception type, as per context managers. value: Optional exception value, as per context managers. unused_traceback: Optional trace. \"\"\" try: if self.file: # Flush the output file and close it. self.file.flush() else: # Oops... we never reached the threshold. Flush the accumulated # output to the file. self.flush_accumulated(self.default_file) # Wait for the subprocess (if we have one). if self.pipe: self.file.close() self.pipe.wait() # Absorb broken pipes that may occur on flush or close above. except BrokenPipeError: return True # Absorb broken pipes. if isinstance(value, BrokenPipeError): return True elif value: raise","title":"__exit__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__init__","text":"Create a conditional pager. Parameters: command \u2013 A string, the shell command to run as a pager. minlines \u2013 If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). Source code in beancount/utils/pager.py def __init__(self, command, minlines=None): \"\"\"Create a conditional pager. Args: command: A string, the shell command to run as a pager. minlines: If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). \"\"\" self.command = command self.minlines = minlines self.default_file = (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout)","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.flush_accumulated","text":"Flush the existing lines to the newly created pager. This also disabled the accumulator. Parameters: file \u2013 A file object to flush the accumulated data to. Source code in beancount/utils/pager.py def flush_accumulated(self, file): \"\"\"Flush the existing lines to the newly created pager. This also disabled the accumulator. Args: file: A file object to flush the accumulated data to. \"\"\" if self.accumulated_data: write = file.write for data in self.accumulated_data: write(data) self.accumulated_data = None self.accumulated_lines = None","title":"flush_accumulated()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.write","text":"Write the data out. Overridden from the file object interface. Parameters: data \u2013 A string, data to write to the output. Source code in beancount/utils/pager.py def write(self, data): \"\"\"Write the data out. Overridden from the file object interface. Args: data: A string, data to write to the output. \"\"\" if self.file is None: # Accumulate the new lines. self.accumulated_lines += data.count('\\n') self.accumulated_data.append(data) # If we've reached the threshold, create a file. if self.accumulated_lines > self.minlines: self.file, self.pipe = create_pager(self.command, self.default_file) self.flush_accumulated(self.file) else: # We've already created a pager subprocess... flush the lines to it. self.file.write(data) # try: # except BrokenPipeError: # # Make sure we don't barf on __exit__(). # self.file = self.pipe = None # raise","title":"write()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.create_pager","text":"Try to create and return a pager subprocess. Parameters: command \u2013 A string, the shell command to run as a pager. file \u2013 The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. Source code in beancount/utils/pager.py def create_pager(command, file): \"\"\"Try to create and return a pager subprocess. Args: command: A string, the shell command to run as a pager. file: The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. \"\"\" if command is None: command = os.environ.get('PAGER', DEFAULT_PAGER) if not command: command = DEFAULT_PAGER pipe = None # In case of using 'less', make sure the charset is set properly. In theory # you could override this by setting PAGER to \"LESSCHARSET=utf-8 less\" but # this shouldn't affect other programs and is unlikely to cause problems, so # we set it here to make default behavior work for most people (we always # write UTF-8). env = os.environ.copy() env['LESSCHARSET'] = \"utf-8\" try: pipe = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=file, env=env) except OSError as exc: logging.error(\"Invalid pager: {}\".format(exc)) else: stdin_wrapper = io.TextIOWrapper(pipe.stdin, 'utf-8') file = stdin_wrapper return file, pipe","title":"create_pager()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.flush_only","text":"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Parameters: fileobj \u2013 A file object, to remain open after running the context manager. Yields: A context manager that yields this object. Source code in beancount/utils/pager.py @contextlib.contextmanager def flush_only(fileobj): \"\"\"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Args: fileobj: A file object, to remain open after running the context manager. Yields: A context manager that yields this object. \"\"\" try: yield fileobj finally: fileobj.flush()","title":"flush_only()"},{"location":"api_reference/beancount.utils.html#beancount.utils.regexp_utils","text":"Regular expression helpers.","title":"regexp_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.regexp_utils.re_replace_unicode","text":"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Parameters: regexp \u2013 A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. Source code in beancount/utils/regexp_utils.py def re_replace_unicode(regexp): \"\"\"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Args: regexp: A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. \"\"\" for category, rangestr in UNICODE_RANGES.items(): regexp = regexp.replace(r'\\p{' + category + '}', rangestr) return regexp","title":"re_replace_unicode()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop","text":"Text manipulation utilities.","title":"snoop"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop","text":"A snooper callable that just saves the returned values of a function. This is particularly useful for re.match and re.search in conditionals, e.g.:: snoop = Snoop() ... if snoop(re.match(r\"(\\d+)-(\\d+)-(\\d+)\", text)): year, month, date = snoop.value.group(1, 2, 3) Attributes: Name Type Description value The last value snooped from a function call. history If 'maxlen' was specified, the last few values that were snooped.","title":"Snoop"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__call__","text":"Save a value to the snooper. This is meant to wrap a function call. Parameters: value \u2013 The value to push/save. Returns: Value itself. Source code in beancount/utils/snoop.py def __call__(self, value): \"\"\"Save a value to the snooper. This is meant to wrap a function call. Args: value: The value to push/save. Returns: Value itself. \"\"\" self.value = value if self.history is not None: self.history.append(value) return value","title":"__call__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__getattr__","text":"Forward the attribute to the value. Parameters: attr \u2013 A string, the name of the attribute. Returns: The value of the attribute. Source code in beancount/utils/snoop.py def __getattr__(self, attr): \"\"\"Forward the attribute to the value. Args: attr: A string, the name of the attribute. Returns: The value of the attribute. \"\"\" return getattr(self.value, attr)","title":"__getattr__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__init__","text":"Create a new snooper. Parameters: maxlen \u2013 If specified, an integer, which enables the saving of that Source code in beancount/utils/snoop.py def __init__(self, maxlen=None): \"\"\"Create a new snooper. Args: maxlen: If specified, an integer, which enables the saving of that number of last values in the history attribute. \"\"\" self.value = None self.history = (collections.deque(maxlen=maxlen) if maxlen else None)","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.snoopify","text":"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. Source code in beancount/utils/snoop.py def snoopify(function): \"\"\"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. \"\"\" @functools.wraps(function) def wrapper(*args, **kw): value = function(*args, **kw) wrapper.value = value return value wrapper.value = None return wrapper","title":"snoopify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils","text":"Support utilities for testing scripts.","title":"test_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall","text":"RCall(args, kwargs, return_value)","title":"RCall"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/utils/test_utils.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__new__","text":"Create new instance of RCall(args, kwargs, return_value)","title":"__new__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__repr__","text":"Return a nicely formatted representation string Source code in beancount/utils/test_utils.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase","text":"","title":"TestCase"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase.assertLines","text":"Compare the lines of text1 and text2, ignoring whitespace. Parameters: text1 \u2013 A string, the expected text. text2 \u2013 A string, the actual text. message \u2013 An optional string message in case the assertion fails. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/utils/test_utils.py def assertLines(self, text1, text2, message=None): \"\"\"Compare the lines of text1 and text2, ignoring whitespace. Args: text1: A string, the expected text. text2: A string, the actual text. message: An optional string message in case the assertion fails. Raises: AssertionError: If the exception fails. \"\"\" clean_text1 = textwrap.dedent(text1.strip()) clean_text2 = textwrap.dedent(text2.strip()) lines1 = [line.strip() for line in clean_text1.splitlines()] lines2 = [line.strip() for line in clean_text2.splitlines()] # Compress all space longer than 4 spaces to exactly 4. # This affords us to be even looser. lines1 = [re.sub(' [ \\t]*', ' ', line) for line in lines1] lines2 = [re.sub(' [ \\t]*', ' ', line) for line in lines2] self.assertEqual(lines1, lines2, message)","title":"assertLines()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase.assertOutput","text":"Expect text printed to stdout. Parameters: expected_text \u2013 A string, the text that should have been printed to stdout. Exceptions: AssertionError \u2013 If the text differs. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def assertOutput(self, expected_text): \"\"\"Expect text printed to stdout. Args: expected_text: A string, the text that should have been printed to stdout. Raises: AssertionError: If the text differs. \"\"\" with capture() as oss: yield oss self.assertLines(textwrap.dedent(expected_text), oss.getvalue())","title":"assertOutput()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.call_command","text":"Run the script with a subprocess. Parameters: script_args \u2013 A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). Source code in beancount/utils/test_utils.py def call_command(command): \"\"\"Run the script with a subprocess. Args: script_args: A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). \"\"\" assert isinstance(command, list), command p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return p.returncode, stdout.decode(), stderr.decode()","title":"call_command()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.capture","text":"A context manager that captures what's printed to stdout. Parameters: *attributes \u2013 A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. Source code in beancount/utils/test_utils.py def capture(*attributes): \"\"\"A context manager that captures what's printed to stdout. Args: *attributes: A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. \"\"\" if not attributes: attributes = 'stdout' elif len(attributes) == 1: attributes = attributes[0] return patch(sys, attributes, io.StringIO)","title":"capture()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.create_temporary_files","text":"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Parameters: root \u2013 A string, the name of the directory under which to create the files. contents_map \u2013 A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. Source code in beancount/utils/test_utils.py def create_temporary_files(root, contents_map): \"\"\"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Args: root: A string, the name of the directory under which to create the files. contents_map: A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. \"\"\" os.makedirs(root, exist_ok=True) for relative_filename, contents in contents_map.items(): assert not path.isabs(relative_filename) filename = path.join(root, relative_filename) os.makedirs(path.dirname(filename), exist_ok=True) clean_contents = textwrap.dedent(contents.replace('{root}', root)) with open(filename, 'w') as f: f.write(clean_contents)","title":"create_temporary_files()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.docfile","text":"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Parameters: function \u2013 A function to decorate. Returns: The decorated function. Source code in beancount/utils/test_utils.py def docfile(function, **kwargs): \"\"\"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Args: function: A function to decorate. Returns: The decorated function. \"\"\" @functools.wraps(function) def new_function(self): allowed = ('buffering', 'encoding', 'newline', 'dir', 'prefix', 'suffix') if any([key not in allowed for key in kwargs]): raise ValueError(\"Invalid kwarg to docfile_extra\") with tempfile.NamedTemporaryFile('w', **kwargs) as file: text = function.__doc__ file.write(textwrap.dedent(text)) file.flush() return function(self, file.name) new_function.__doc__ = None return new_function","title":"docfile()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.docfile_extra","text":"A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile Source code in beancount/utils/test_utils.py def docfile_extra(**kwargs): \"\"\" A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile \"\"\" return functools.partial(docfile, **kwargs)","title":"docfile_extra()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.environ","text":"A context manager which pushes varname's value and restores it later. Parameters: varname \u2013 A string, the environ variable name. newvalue \u2013 A string, the desired value. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def environ(varname, newvalue): \"\"\"A context manager which pushes varname's value and restores it later. Args: varname: A string, the environ variable name. newvalue: A string, the desired value. \"\"\" oldvalue = os.environ.get(varname, None) os.environ[varname] = newvalue yield if oldvalue is not None: os.environ[varname] = oldvalue else: del os.environ[varname]","title":"environ()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.find_python_lib","text":"Return the path to the root of the Python libraries. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_python_lib(): \"\"\"Return the path to the root of the Python libraries. Returns: A string, the root directory. \"\"\" return path.dirname(path.dirname(path.dirname(__file__)))","title":"find_python_lib()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.find_repository_root","text":"Return the path to the repository root. Parameters: filename \u2013 A string, the name of a file within the repository. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_repository_root(filename=None): \"\"\"Return the path to the repository root. Args: filename: A string, the name of a file within the repository. Returns: A string, the root directory. \"\"\" if filename is None: filename = __file__ # Support root directory under Bazel. match = re.match(r\"(.*\\.runfiles/beancount)/\", filename) if match: return match.group(1) while not all(path.exists(path.join(filename, sigfile)) for sigfile in ('PKG-INFO', 'COPYING', 'README.rst')): prev_filename = filename filename = path.dirname(filename) if prev_filename == filename: raise ValueError(\"Failed to find the root directory.\") return filename","title":"find_repository_root()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.make_failing_importer","text":"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins. import ', make_failing_importer('setuptools')) def test_... Parameters: removed_module_name \u2013 The name of the module import that should raise an exception. Returns: A decorated test decorator. Source code in beancount/utils/test_utils.py def make_failing_importer(*removed_module_names): \"\"\"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins.__import__', make_failing_importer('setuptools')) def test_... Args: removed_module_name: The name of the module import that should raise an exception. Returns: A decorated test decorator. \"\"\" def failing_import(name, *args, **kwargs): if name in removed_module_names: raise ImportError(\"Could not import {}\".format(name)) return builtins.__import__(name, *args, **kwargs) return failing_import","title":"make_failing_importer()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.nottest","text":"Make the given function not testable. Source code in beancount/utils/test_utils.py def nottest(func): \"Make the given function not testable.\" func.__test__ = False return func","title":"nottest()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.patch","text":"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Parameters: obj \u2013 The object to patch up. attributes \u2013 A string or a sequence of strings, the names of attributes to replace. replacement_type \u2013 A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def patch(obj, attributes, replacement_type): \"\"\"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Args: obj: The object to patch up. attributes: A string or a sequence of strings, the names of attributes to replace. replacement_type: A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. \"\"\" single = isinstance(attributes, str) if single: attributes = [attributes] saved = [] replacements = [] for attribute in attributes: replacement = replacement_type() replacements.append(replacement) saved.append(getattr(obj, attribute)) setattr(obj, attribute, replacement) yield replacements[0] if single else replacements for attribute, saved_attr in zip(attributes, saved): setattr(obj, attribute, saved_attr)","title":"patch()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.record","text":"Decorates the function to intercept and record all calls and return values. Parameters: fun \u2013 A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. Source code in beancount/utils/test_utils.py def record(fun): \"\"\"Decorates the function to intercept and record all calls and return values. Args: fun: A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. \"\"\" @functools.wraps(fun) def wrapped(*args, **kw): return_value = fun(*args, **kw) wrapped.calls.append(RCall(args, kw, return_value)) return return_value wrapped.calls = [] return wrapped","title":"record()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.run_with_args","text":"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Parameters: function \u2013 A function object to call with no arguments. argv \u2013 A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. Source code in beancount/utils/test_utils.py def run_with_args(function, args): \"\"\"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Args: function: A function object to call with no arguments. argv: A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. \"\"\" saved_argv = sys.argv saved_handlers = logging.root.handlers try: module = sys.modules[function.__module__] sys.argv = [module.__file__] + args logging.root.handlers = [] return function() finally: sys.argv = saved_argv logging.root.handlers = saved_handlers","title":"run_with_args()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.search_words","text":"Search for a sequence of words in a line. Parameters: words \u2013 A list of strings, the words to look for, or a space-separated string. line \u2013 A string, the line to search into. Returns: A MatchObject, or None. Source code in beancount/utils/test_utils.py def search_words(words, line): \"\"\"Search for a sequence of words in a line. Args: words: A list of strings, the words to look for, or a space-separated string. line: A string, the line to search into. Returns: A MatchObject, or None. \"\"\" if isinstance(words, str): words = words.split() return re.search('.*'.join(r'\\b{}\\b'.format(word) for word in words), line)","title":"search_words()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.skipIfRaises","text":"A context manager (or decorator) that skips a test if an exception is raised. Yields: Nothing, for you to execute the function code. Exceptions: SkipTest \u2013 if the test raised the expected exception. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def skipIfRaises(*exc_types): \"\"\"A context manager (or decorator) that skips a test if an exception is raised. Args: exc_type Yields: Nothing, for you to execute the function code. Raises: SkipTest: if the test raised the expected exception. \"\"\" try: yield except exc_types as exception: raise unittest.SkipTest(exception)","title":"skipIfRaises()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.subprocess_env","text":"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def subprocess_env(): \"\"\"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. \"\"\" # Ensure we have locations to invoke our Python executable and our # runnable binaries in the test environment to run subprocesses. binpath = ':'.join([ path.dirname(sys.executable), path.join(find_repository_root(__file__), 'bin'), os.environ.get('PATH', '').strip(':')]).strip(':') return {'PATH': binpath, 'PYTHONPATH': find_python_lib()}","title":"subprocess_env()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.tempdir","text":"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Parameters: delete \u2013 A boolean, true if we want to delete the directory after running. **kw \u2013 Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def tempdir(delete=True, **kw): \"\"\"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Args: delete: A boolean, true if we want to delete the directory after running. **kw: Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. \"\"\" tempdir = tempfile.mkdtemp(prefix=\"beancount-test-tmpdir.\", **kw) try: yield tempdir finally: if delete: shutil.rmtree(tempdir, ignore_errors=True)","title":"tempdir()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils","text":"Text manipulation utilities.","title":"text_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.entitize_ampersand","text":"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Parameters: filename \u2013 A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. Source code in beancount/utils/text_utils.py def entitize_ampersand(filename): \"\"\"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Args: filename: A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. \"\"\" tidy_file = tempfile.NamedTemporaryFile(suffix='.xls', mode='w', delete=False) with open(filename) as infile: contents = infile.read() new_contents = re.sub('&([^;&]{12})', '&\\\\1', contents) tidy_file.write(new_contents) tidy_file.flush() return tidy_file","title":"entitize_ampersand()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.replace_number","text":"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Parameters: match \u2013 A MatchObject. Returns: A replacement string, consisting only of X'es. Source code in beancount/utils/text_utils.py def replace_number(match): \"\"\"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Args: match: A MatchObject. Returns: A replacement string, consisting only of X'es. \"\"\" return re.sub('[0-9]', 'X', match.group(1)) + match.group(2)","title":"replace_number()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.replace_numbers","text":"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Parameters: text \u2013 An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. Source code in beancount/utils/text_utils.py def replace_numbers(text): \"\"\"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Args: text: An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. \"\"\" return re.sub(r'\\b([0-9,]+(?:\\.[0-9]*)?)\\b([ \\t<]+[^0-9,.]|$)', replace_number, text)","title":"replace_numbers()"},{"location":"api_reference/beancount.utils.html#beancount.utils.version","text":"Implement common options across all programs.","title":"version"},{"location":"api_reference/beancount.utils.html#beancount.utils.version.ArgumentParser","text":"Add a standard --version option to an ArgumentParser. Parameters: *args \u2013 Arguments for the parser. *kwargs \u2013 Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. Source code in beancount/utils/version.py def ArgumentParser(*args, **kwargs): \"\"\"Add a standard --version option to an ArgumentParser. Args: *args: Arguments for the parser. *kwargs: Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. \"\"\" parser = argparse.ArgumentParser(*args, **kwargs) parser.add_argument('--version', '-V', action='version', version=compute_version_string( beancount.__version__, _parser.__vc_changeset__, _parser.__vc_timestamp__)) return parser","title":"ArgumentParser()"},{"location":"api_reference/beancount.utils.html#beancount.utils.version.compute_version_string","text":"Compute a version string from the changeset and timestamp baked in the parser. Parameters: version \u2013 A string, the version number. changeset \u2013 A string, a version control string identifying the commit of the version. timestamp \u2013 An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. Source code in beancount/utils/version.py def compute_version_string(version, changeset, timestamp): \"\"\"Compute a version string from the changeset and timestamp baked in the parser. Args: version: A string, the version number. changeset: A string, a version control string identifying the commit of the version. timestamp: An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. \"\"\" # Shorten changeset. if changeset: if re.match('hg:', changeset): changeset = changeset[:15] elif re.match('git:', changeset): changeset = changeset[:12] # Convert timestamp to a date. date = None if timestamp > 0: date = datetime.datetime.utcfromtimestamp(timestamp).date() version = 'Beancount {}'.format(version) if changeset or date: version = '{} ({})'.format( version, '; '.join(map(str, filter(None, [changeset, date])))) return version","title":"compute_version_string()"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"index.html","text":"Beancount User's Manual \uf0c1 This is the top-level page for all documentation related to Beancount. http://furius.ca/beancount/doc/index Documentation for Users \uf0c1 Command-line Accounting in Context : A motivational document that explains what command-line accounting is, why I do it, with a simple example of how I do it. Read this as an introduction. The Double-Entry Counting Method : A gentle introduction to the double-entry method, in terms compatible with the assumptions and simplifications that command-line accounting systems make. Installing Beancount : Instructions for download and installation on your computer, and software release information. Running Beancount and Generating Reports : How to run the Beancount executables and generate reports with it. This contains technical information for Beancount users. Getting Started with Beancount : A text that explains the basics of how to create, initialize and organize your input file, and the process of declaring accounts and adding padding directives. Beancount Language Syntax : A full description of the language syntax with examples. This is the main reference for the Beancount language. Beancount Options Reference : A description and explanation of all the possible option values. Precision & Tolerances : Transactions and Balance assertions tolerance small amounts of imprecision which are inferred from the context. This document explains how this works. Beancount Query Language : A high-level overview of the bean-query command-line client tool that allows you to extract various tables of data from your Beancount ledger file. Beancount Cheat Sheet : A single page \u201ccheat sheet\u201d that summarizes the Beancount syntax. How Inventories Work : An explanation of inventories, their representation, and the detail of how lots are matched to positions held in an inventory. This is preliminary reading for the trading doc. Exporting Your Portfolio : How to export your portfolio to external portfolio tracking websites. Tutorial & Example : A realistic example ledger file that contains a few years of a hypothetical Beancount user's financial life. This can be used to kick the tires on Beancount and see what reports or its web interface look like. This can give a quick idea of what you can get out of using Beancount. Beancount Mailing-list : Questions and discussions specific to Beancount occur there. Of related interest is also the Ledger-CLI Forum which contains lots of more general discussions and acts as a meta-list for command-line accounting topics. Beancount History and Credits: A description of the development history of Beancount and a shout out to its contributors. A Comparison of Beancount and Ledger & HLedger : A qualitative feature comparison between CLI accounting systems. Fetching Prices in Beancount : How to fetch and maintain a price database for your assets using Beancount\u2019s tools. Importing External Data : A description of the process of importing data from external files that can be downloaded from financial institutions and the tools and libraries that Beancount provides to automate some of this. Cookbook & Examples \uf0c1 These documents are examples of using the double-entry method to carry out specific tasks. Use these documents to develop an intuition for how to structure your accounts. Command-line Accounting Cookbook : Various examples of how to book many different kinds of financial transactions. This is undoubtedly the best way to build an intuition for how to best use the double-entry method. Trading with Beancount : An explanation of trading P/L and worked examples of how to deal with various investing and trading scenarios with Beancount. This is a complement to the cookbook. Stock Vesting in Beancount : An example that shows how to deal with restricted stock units and vesting events typical of those offered by technology companies & startups. Sharing Expenses with Beancount : A realistic and detailed example of using the double-entry method for sharing expenses for a trip or project with other people. How We Share Expenses : A more involved description of a continuous system for (a) sharing expenses between partners when both are contributing and (b) sharing expenses to a specific project (our son) who has a ledger of his own. This describes our real system. Health care Expenses (incomplete): A not-quite finished document explaining how to handle the sequence of health care expenses for in and out of network providers in the USA. Tracking Medical Claims : An example structure tracking out-of-network psychotherapy sessions with out-of-pocket payments, a medical claim, and an associated HSA repayment for the uncovered portion. Calculating Portolio Returns : How to compute portfolio returns from a Beancount ledger. This describes work done in an experimental script and the process that was involved in extracting the correct data for it. Documentation for Developers \uf0c1 Beancount Scripting & Plugins : A guide to writing scripts that load your ledger contents in memory and process the directives, and how to write plugins that transform them. Beancount Design Doc : Information about the program\u2019s architecture and design choices, code conventions, invariants and methodology. Read this if you want to get a deeper understanding. LedgerHub Design Doc : The design and architecture of the importing tools and library implemented in beancount.ingest . This used to be a separate project called LedgerHub (now defunct), whose useful parts have been eventually folded into Beancount. This is the somewhat dated original design doc. Source Code : The official repository of the Beancount source code lives at Github since May 2020. (From 2008 to May 2020 it was hosted at Bitbucket). External Contributions : A list of plugins, importers and other codes that build on Beancount\u2019s libraries that other people have made and shared. Enhancement Proposals & Discussions \uf0c1 I occasionally write proposals for enhancements in order to organize and summarize my thoughts before moving forward, and solicit feedback from other users. This is good material to find out what features I\u2019m planning to add, how things work in detail, or compare the differences with other similar software such as Ledger or HLedger. A Proposal for an Improvement on Inventory Booking : A proposal in which I outline the current state of affairs in Ledger and Beancount regarding inventory booking, and a better method for doing it that will support average cost booking and calculating capital gains without commissions. Settlement Dates in Beancount : A discussion of how settlement or auxiliary dates could be used in Beancount, and how they are used in Ledger. Balance Assertions in Beancount : A summary of the different semantics for making balance assertions in Beancount and Ledger and a proposal to extend Beancount\u2019s syntax to support more complex assertions. Fund Accounting with Beancount : A discussion of fund accounting and how best to go about using Beancount to track multiple funds in a single ledger. Rounding & Precision in Beancount : A discussion of important rounding and precision issues for balance checks and a proposal for a better method to infer required precision. ( Implemented ) External Links \uf0c1 Documents, links, blog entries, writings, etc. about Beancount written by other authors.] Beancount Source Code Documentation (Dominik Aumayr): Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . Beancount ou la comptabilit\u00e9 pour les hackers (Cyril Deguet): An overview blog entry (in french). V3 Documentation \uf0c1 As of summer 2020, a rewrite to C++ is underway. These are the documents related to that. Beancount V3 : Goals & Design: Motivation, goals and design for version 3 rewrite to C++. Beancount V3: Changes from V2 : A list of current changes presently done in v3 (on the \"master\" branch) whose documentation hasn't made it to the v2 documentation set. Installing Beancount (v3) : Installation procedure for v3. Beancount V3: Dependencies : Software dependencies for building version 3. Beangulp : The renewed ingestion framework for v3. About this Documentation \uf0c1 You may have noticed that I\u2019m using Google Docs . I realize that this is unusual for an open source project. If you take offense to this , so you know, I do like text formats too: in graduate school I used LaTeX extensively, and for the last decade I was in love with the reStructuredText format, I even wrote Emacs\u2019 support for it. But something happened around 2013: Google Docs became good enough to write solid technical documentation and I\u2019ve begun enjoying its revision and commenting facilities extensively: I am hooked, I love it. For users to be able to suggest a correction or place a comment in context is an incredibly useful feature that helps improve the quality of my writing a lot more than patches or comments on a mailing-list. It also gives users a chance to point out passages that need improvement. I have already received orders of magnitude more feedback on documentation than on any of my other projects; it works. It also looks really good , and this is helping motivate me to write more and better. I think maybe I\u2019m falling in love again with WYSIWYG... Don\u2019t get me wrong: LaTeX and no-markup formats are great, but the world is changing and it is more important to me to have community collaboration and a living document than a crufty TeX file slowly aging in my repository. My aim is to produce quality text that is easy to read, that prints nicely, and most especially these days, that can render well on a mobile device or a tablet so you can read it anywhere. I\u2019m also able to edit it while I\u2019m on the go, which is fun for jotting down ideas or making corrections. Finally, Google Docs has an API that provides access to the doc, so should it be needed, I could eventually download the meta-data and convert it all to another format. I don\u2019t like everything about it, but what it offers, I really do like. If you want to leave comments, I\u2019d appreciate it if you can log into your Google account while you read, so that your name appears next to your comment. Thank you! \u2014 Martin Blais","title":"Index"},{"location":"index.html#beancount-users-manual","text":"This is the top-level page for all documentation related to Beancount. http://furius.ca/beancount/doc/index","title":"Beancount User's Manual"},{"location":"index.html#documentation-for-users","text":"Command-line Accounting in Context : A motivational document that explains what command-line accounting is, why I do it, with a simple example of how I do it. Read this as an introduction. The Double-Entry Counting Method : A gentle introduction to the double-entry method, in terms compatible with the assumptions and simplifications that command-line accounting systems make. Installing Beancount : Instructions for download and installation on your computer, and software release information. Running Beancount and Generating Reports : How to run the Beancount executables and generate reports with it. This contains technical information for Beancount users. Getting Started with Beancount : A text that explains the basics of how to create, initialize and organize your input file, and the process of declaring accounts and adding padding directives. Beancount Language Syntax : A full description of the language syntax with examples. This is the main reference for the Beancount language. Beancount Options Reference : A description and explanation of all the possible option values. Precision & Tolerances : Transactions and Balance assertions tolerance small amounts of imprecision which are inferred from the context. This document explains how this works. Beancount Query Language : A high-level overview of the bean-query command-line client tool that allows you to extract various tables of data from your Beancount ledger file. Beancount Cheat Sheet : A single page \u201ccheat sheet\u201d that summarizes the Beancount syntax. How Inventories Work : An explanation of inventories, their representation, and the detail of how lots are matched to positions held in an inventory. This is preliminary reading for the trading doc. Exporting Your Portfolio : How to export your portfolio to external portfolio tracking websites. Tutorial & Example : A realistic example ledger file that contains a few years of a hypothetical Beancount user's financial life. This can be used to kick the tires on Beancount and see what reports or its web interface look like. This can give a quick idea of what you can get out of using Beancount. Beancount Mailing-list : Questions and discussions specific to Beancount occur there. Of related interest is also the Ledger-CLI Forum which contains lots of more general discussions and acts as a meta-list for command-line accounting topics. Beancount History and Credits: A description of the development history of Beancount and a shout out to its contributors. A Comparison of Beancount and Ledger & HLedger : A qualitative feature comparison between CLI accounting systems. Fetching Prices in Beancount : How to fetch and maintain a price database for your assets using Beancount\u2019s tools. Importing External Data : A description of the process of importing data from external files that can be downloaded from financial institutions and the tools and libraries that Beancount provides to automate some of this.","title":"Documentation for Users"},{"location":"index.html#cookbook-examples","text":"These documents are examples of using the double-entry method to carry out specific tasks. Use these documents to develop an intuition for how to structure your accounts. Command-line Accounting Cookbook : Various examples of how to book many different kinds of financial transactions. This is undoubtedly the best way to build an intuition for how to best use the double-entry method. Trading with Beancount : An explanation of trading P/L and worked examples of how to deal with various investing and trading scenarios with Beancount. This is a complement to the cookbook. Stock Vesting in Beancount : An example that shows how to deal with restricted stock units and vesting events typical of those offered by technology companies & startups. Sharing Expenses with Beancount : A realistic and detailed example of using the double-entry method for sharing expenses for a trip or project with other people. How We Share Expenses : A more involved description of a continuous system for (a) sharing expenses between partners when both are contributing and (b) sharing expenses to a specific project (our son) who has a ledger of his own. This describes our real system. Health care Expenses (incomplete): A not-quite finished document explaining how to handle the sequence of health care expenses for in and out of network providers in the USA. Tracking Medical Claims : An example structure tracking out-of-network psychotherapy sessions with out-of-pocket payments, a medical claim, and an associated HSA repayment for the uncovered portion. Calculating Portolio Returns : How to compute portfolio returns from a Beancount ledger. This describes work done in an experimental script and the process that was involved in extracting the correct data for it.","title":"Cookbook & Examples"},{"location":"index.html#documentation-for-developers","text":"Beancount Scripting & Plugins : A guide to writing scripts that load your ledger contents in memory and process the directives, and how to write plugins that transform them. Beancount Design Doc : Information about the program\u2019s architecture and design choices, code conventions, invariants and methodology. Read this if you want to get a deeper understanding. LedgerHub Design Doc : The design and architecture of the importing tools and library implemented in beancount.ingest . This used to be a separate project called LedgerHub (now defunct), whose useful parts have been eventually folded into Beancount. This is the somewhat dated original design doc. Source Code : The official repository of the Beancount source code lives at Github since May 2020. (From 2008 to May 2020 it was hosted at Bitbucket). External Contributions : A list of plugins, importers and other codes that build on Beancount\u2019s libraries that other people have made and shared.","title":"Documentation for Developers"},{"location":"index.html#enhancement-proposals-discussions","text":"I occasionally write proposals for enhancements in order to organize and summarize my thoughts before moving forward, and solicit feedback from other users. This is good material to find out what features I\u2019m planning to add, how things work in detail, or compare the differences with other similar software such as Ledger or HLedger. A Proposal for an Improvement on Inventory Booking : A proposal in which I outline the current state of affairs in Ledger and Beancount regarding inventory booking, and a better method for doing it that will support average cost booking and calculating capital gains without commissions. Settlement Dates in Beancount : A discussion of how settlement or auxiliary dates could be used in Beancount, and how they are used in Ledger. Balance Assertions in Beancount : A summary of the different semantics for making balance assertions in Beancount and Ledger and a proposal to extend Beancount\u2019s syntax to support more complex assertions. Fund Accounting with Beancount : A discussion of fund accounting and how best to go about using Beancount to track multiple funds in a single ledger. Rounding & Precision in Beancount : A discussion of important rounding and precision issues for balance checks and a proposal for a better method to infer required precision. ( Implemented )","title":"Enhancement Proposals & Discussions"},{"location":"index.html#external-links","text":"Documents, links, blog entries, writings, etc. about Beancount written by other authors.] Beancount Source Code Documentation (Dominik Aumayr): Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . Beancount ou la comptabilit\u00e9 pour les hackers (Cyril Deguet): An overview blog entry (in french).","title":"External Links"},{"location":"index.html#v3-documentation","text":"As of summer 2020, a rewrite to C++ is underway. These are the documents related to that. Beancount V3 : Goals & Design: Motivation, goals and design for version 3 rewrite to C++. Beancount V3: Changes from V2 : A list of current changes presently done in v3 (on the \"master\" branch) whose documentation hasn't made it to the v2 documentation set. Installing Beancount (v3) : Installation procedure for v3. Beancount V3: Dependencies : Software dependencies for building version 3. Beangulp : The renewed ingestion framework for v3.","title":"V3 Documentation"},{"location":"index.html#about-this-documentation","text":"You may have noticed that I\u2019m using Google Docs . I realize that this is unusual for an open source project. If you take offense to this , so you know, I do like text formats too: in graduate school I used LaTeX extensively, and for the last decade I was in love with the reStructuredText format, I even wrote Emacs\u2019 support for it. But something happened around 2013: Google Docs became good enough to write solid technical documentation and I\u2019ve begun enjoying its revision and commenting facilities extensively: I am hooked, I love it. For users to be able to suggest a correction or place a comment in context is an incredibly useful feature that helps improve the quality of my writing a lot more than patches or comments on a mailing-list. It also gives users a chance to point out passages that need improvement. I have already received orders of magnitude more feedback on documentation than on any of my other projects; it works. It also looks really good , and this is helping motivate me to write more and better. I think maybe I\u2019m falling in love again with WYSIWYG... Don\u2019t get me wrong: LaTeX and no-markup formats are great, but the world is changing and it is more important to me to have community collaboration and a living document than a crufty TeX file slowly aging in my repository. My aim is to produce quality text that is easy to read, that prints nicely, and most especially these days, that can render well on a mobile device or a tablet so you can read it anywhere. I\u2019m also able to edit it while I\u2019m on the go, which is fun for jotting down ideas or making corrections. Finally, Google Docs has an API that provides access to the doc, so should it be needed, I could eventually download the meta-data and convert it all to another format. I don\u2019t like everything about it, but what it offers, I really do like. If you want to leave comments, I\u2019d appreciate it if you can log into your Google account while you read, so that your name appears next to your comment. Thank you! \u2014 Martin Blais","title":"About this Documentation"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html","text":"A Comparison of Beancount and Ledger \uf0c1 Martin Blais , September 2014 http://furius.ca/beancount/doc/comparison The question of how Beancount differs from Ledger & HLedger has come up a few times on mailing-lists and in private emails. This document highlights key differences between these systems, as they differ sharply in their design and implementations. Keep in mind that this document is written from the perspective of Beancount and as its author, reflects my own biased views for what the design of a CLI accounting system should be. My purpose here is not to shoot down other systems, but rather to highlight material differences to help newcomers understand how these systems vary in their operation and capabilities, and perhaps to stimulate a fruitful discussion about design choices with the other developers. Philosophical Differences \uf0c1 (See this thread .) First, Ledger is optimistic. It assumes it's easy to input correct data by a user. My experience with data entry of the kind we're doing is that it's impossible to do this right without many automated checks. Sign errors on unasserted accounts are very common, for instance. In contrast, Beancount is highly pessimistic. It assumes the user is unreliable. It imposes a number of constraints on the input. For instance, if you added a share of AAPL at $100 to an empty account it won't let you remove a share of AAPL at $101 from it; you just don't have one. It doesn't assume the user is able or should be relied upon to input transactions in the correct order (dated assertions instead of file-order assertions). It optionally checks that proceeds match sale price (sellgains plugin). And it allows you to place extra constraints on your chart of accounts, e.g. a plugin that refuses postings to non-leaf accounts, or that refuses more than one commodity per account, or that requires you declare all accounts with Open directives; choose your level of pedanticity a-la-carte. It adds more automated cross-checks than the double-entry method provides. After all, cross-checking is why we choose to use the DE method in the first place, why not go hardcore on checking for correctness? Beancount should appeal to anyone who does not trust themselves too much. And because of this, it does not provide support for unbalanced/virtual postings; it's not a shortcoming, it's on purpose. Secondly, there's a design ethos difference. As is evidenced in the user manual, Ledger provides a myriad of options. This surely will be appealing to many, but to me it seems it has grown into a very complicated monolithic tool. How these options interact and some of the semantic consequences of many of these options are confusing and very subtle. Beancount offers a minimalistic approach: while there are some small number of options , it tries really hard not to have them. And those options that do affect the semantics of transactions always appear in the input file (nothing on the command-line) and are distinct from the options of particular tools. Loading a file always results in the same stream of transactions, regardless of the reporting tool that will consume them. The only command-line options present are those which affect the particular behavior of the reporting tool invoked; those never change the semantics of the stream itself. Thirdly, Beancount embraces stream processing to a deeper extent. Its loader creates a single ordered list of directives, and all the directives share some common attributes (a name, a date, metadata). This is all the data. Directives that are considered \"grammar\" in Ledger are defined as ordinary directive objects, e.g. \"Open\" is nothing special in Beancount and does nothing by itself. It's simply used in some routines that apply constraints (an account appears, has an Open directive been witnessed prior?) or that might want to hang per-account metadata to them. Prices are also specified as directives and are embedded in the stream, and can be generated in this way. All internal operations are defined as processing and outputting a stream of directives. This makes it possible to allow a user to insert their own code inside the processing pipeline to carry out arbitrary transformations on this stream of directives\u2014anything is possible, unlimited by the particular semantics of an expression language. It's a mechanism that allows users to build new features by writing a short add-on in Python, which gets run at the core of Beancount, not an API to access its data at the edges. If anything, Beancount's own internal processing will evolve towards doing less and less and moving all the work to these plugins, perhaps even to the extent of allowing plugins to declare the directive types (with the exception of Transaction objects). It is evolving into a shallow driver that just puts together a processing pipeline to produce a stream of directives, with a handy library and functional operations. Specific Differences \uf0c1 Inventory Booking & Cost Basis Treatment \uf0c1 Beancount applies strict rules regarding reductions in positions from inventories tracking the contents of its accounts. This means that you can only take out of an account something that you placed in it previously (in time), or an error will be generated. This is enforced for units \u201cheld at cost\u201d (e.g., stock shares), to ensure that no cost basis can ever leak from accounts, we can detect errors in data entry for trades (which are all too common), and we are able to correctly calculate capital gains. In contrast, Ledger does not implement inventory booking checks over time: all lots are simply accumulated regardless of the previous contents of an inventory (there is no distinction between lot addition vs. reduction). In Beancount, reductions to the contents of an inventory are required to match particular lots with a specified cost basis. In Ledger, the output is slightly misleading about this: in order to simplify the reporting output the user may specify one of a few types of lot merging algorithms. By default, the sum of units of all lots is printed, but by using these options you can tell the reporting generation to consider the cost basis (what it calls \u201cprices\u201d) and/or the dates the lots were created at in which case it will report the set of lots with distinct cost bases and/or dates individually. You can select which type of merging occurs via command-line options, e.g. --lot-dates. Most importantly, this means that it is legal in Ledger to remove from an account a lot that was never added to it. This results in a mix of long and short lots, which do not accurately represent the actual changes that occur in accounts. However, it trivially allows for average cost basis reporting. I believe that this is not only confusing, but also an incorrect treatment of account inventories and have argued it can lead to leakage and incorrect calculations. More details and an example are available here . Furthermore, until recently Ledger was not using cost basis to balance postings in a way that correctly computes capital gains. For this reason I suspect Ledger users have probably not used it to compute and compare their gains against the values reported by their broker. In order for Beancount to detect such errors meaningfully and implement a strict matching discipline when reducing lots, it turns out that the only constraint it needs to apply is that a particular account not be allowed to simultaneously hold long and short positions of the same commodity. For example, all it has to do is enforce that you could not hold a lot of 1 GOOG units and a lot of -1 GOOG units simultaneously in the same inventory (regardless of their acquisition cost). Any reduction of a particular set of units is detected as a \u201clot reduction\u201d and a search is found for a lot that matches the specification of the reducing posting, normally, a search for a lot held at the same cost as the posting specifies. Enforcing this constraint is not of much concern in practice, as there are no cases where you would want both long and short positions in the same account, but if you ever needed this, you could simply resort to using separate accounts to hold your long and short positions (it is already recommended that you use a sub-account to track your positions in any one commodity anyway, this would be quite natural). Finally, Beancount is implementing a proposal that significantly extends the scope of its inventory booking: the syntax will allow a loose specification for lot reductions that makes it easy for users to write postings where there is no ambiguity, as well as supporting booking lots at their average cost as is common in Canada and in all tax-deferred accounts across the world. It will also provide a new syntax for specifying cost per unit and total cost at the same time, a feature which will make it possible to correctly track capital gains without commissions . Currency Conversions \uf0c1 Beancount makes an important semantic distinction between simple currency conversions and conversions of commodities to be held at cost, i.e., for which we want to track the cost basis. For example, converting 20,000 USD to 22,000 CAD is a currency conversion (e.g., between banks), and after inserting the resulting CAD units in the destination account, they are not considered \u201cCAD at a cost basis of 1.1 USD each,\u201d they are simply left as CAD units in the account, with no record of the rate which was used to convert them. This models accurately how the real world operates. On the other hand, converting 5,000 USD into 10 units of GOOG shares with a cost basis of 500 USD each is treated as a distinct operation from a currency conversion: the resulting GOOG units have a particular cost basis attached to them, a memory of the price per unit is kept on the inventory lot (as well as the date) and strict rules are applied to the reduction of such lots (what I call \u201cbooking\u201d) as mentioned previously. This is used to calculate and report capital gains, and most importantly, it detects a lot of errors in data entry. In contrast, Ledger does not distinguish between these two types of conversions . Ledger treats currency conversions the same way as for the conversion of commodities held at cost. The reason that this is not causing as many headaches as one might intuit, is because there is no inventory booking - all lots at whichever conversion rate they occur accumulate in accounts, with positive or negative values - and for simple commodities (e.g. currencies, such as dollars), netting the total amount of units without considering the cost of each unit provides the correct answer. Inspecting the full list of an account\u2019s inventory lots is provided as an option (See Ledger\u2019s \u201c--lot-dates\u201d) and is not the default way to render account balances. I suspect few users make use of the feature: if you did render the list of lots for real-world accounts in which many currency conversions occurred in the past, you would observe a large number of irrelevant lots. I think the cost basis of currency conversions would be best elided instead. HLedger does not parse cost basis syntax and as such does not recognize it. Isolation of Inputs \uf0c1 Beancount reads its entire input only from the text file you provide for it. This isolation is by design. There is no linkage to external data formats nor online services, such as fetchers for historical price values. Fetching and converting external data is disparate enough that I feel it should be the province of separate projects. These problem domains also segment very well and quite naturally: Beancount provides an isolated core which allows you to ingest all the transactional data and derive reports of various aggregations from it, and its syntax is the hinge that connects it to external transaction repositories or price databases. It isolates itself from the ugly details of external sources of data in this way. There are too many external formats for downloadable files that contains transactional information to be able to cover all of them. The data files you can obtain from most institutions are provided in various formats: OFX, Quicken, XLS or CSV, and looking at their contents makes it glaringly obvious that the programmers who built the codes that outputs them did not pay much attention to detail or the standard definition of their format; it\u2019s quite an ugly affair. Those files are almost always very messy, and the details of that messiness varies over time as well as these files evolve. Fetching historical or current price information is a similarly annoying task. While Yahoo and Google Finance are able to provide some basic level of price data for common stocks on US exchanges, when you need to fetch information for instruments traded on foreign exchanges, or instruments typically not traded on exchanges, such as mutual funds, either the data is not available, or if it is, you need to figure out what ticker symbol they decided to map it to, there are few standards for this. You must manually sign the ticker. Finally, it is entirely possible that you want to manage instruments for which there is no existing external price source, so it is necessary that your bookkeeping software provide a mechanism whereby you can manually input price values (both Beancount and Ledger provide a way). The same declaration mechanism is used for caching historical price data, so that Beancount need not require the network at all. Most users will want to write their own scripts for import, but some libraries exist: the beancount.ingest library (within Beancount) provides a framework to automate the identification, extraction of transactions from and filing of downloadable files for various institutions. See its design doc for details. In comparison, Ledger and HLedger support rudimentary conversions of transactions from CSV files as well as automated fetching of current prices that uses an external script you are meant to provide ( getquote ). CSV import is far insufficient for real world usage if you are to track all your accounts, so this needs to be extended. Hooking into an external script is the right thing to do, but Beancount favors taking a strong stance about this issue and instead not to provide code that would trigger any kind of network access nor support any external format as input. In Beancount you are meant to integrate price updates on your own, perhaps with your own script, and maybe bring in the data via an include file. (But if you don't like this, you could also write your own plugin module that could fetch live prices.) Language Syntax \uf0c1 Beancount\u2019s syntax is somewhat simpler and quite a bit more restrictive than Ledger\u2019s. For its 2.0 version, the Beancount syntax was redesigned to be easily specifiable to a parser generator by a grammar. The tokens were simplified in order to make tokenization unambiguous. For example, Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. (On the other hand, currencies with numbers require quoting in Ledger.) Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Description strings must be quoted, like this: \u201cAMEX PMNT\u201d. No freestyle text as strings is supported anymore. Dates are only parsed from ISO8601 format, that is, \u201cYYYY-MM-DD\u201d. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. Apart from the tag stack, all context information has been removed. There is no account alias, for example, nor is there a notion of \u201capply\u201d as in Ledger (see \u201capply root\u201d and \u201capply tag\u201d). It requires a bit more verbose input\u2014full account names\u2014and so assumes that you have account name completion setup in your editor. As a side effect, these changes make the input syntax look a bit more like a programming language. These restrictions may annoy some users, but overall they make the task of parsing the contents of a ledger simpler and the resulting simplicity will pave the way for people to more easily write parsers in other languages. (Some subtleties remain around parsing indentation, which is meaningful in the syntax, but should be easily addressable in all contexts by building a custom lexer.) Due to its looser and more user-friendly syntax, Ledger uses a custom parser. If you need to parse its contents from another language, the best approach is probably to create bindings into its source code or to use it to export the contents of a ledger to XML and then parse that (which works well). I suspect the parsing method might be reviewed in the next version of Ledger, because using a parser generator is liberating for experimentation. Order Independence \uf0c1 Beancount offers a guarantee that the ordering of directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input file and reorder any declaration as is most convenient for you without having to worry about how the software will make its calculations. Not even directives that declare accounts (\u201cOpen\u201d) are required to appear before these accounts get used in the file. All directives are parsed and then basically stably sorted before any calculation or validation occurs. This also makes it trivial to implement inclusions of multiple files (you can just concatenate the files if you want). In contrast, Ledger processes the amounts on its postings as it parses the input file. In terms of implementation, this has the advantage that only a single pass is needed to check all the assertions and balances, whereas in Beancount, numerous passes are made on the entire list of directives (this has not been much of a problem in Beancount, however, because even a realistically large number of transactions is rather modest for our computers; most of Beancount\u2019s processing time is due to parsing and numerical calculations). An unfortunate side-effect of Ledger\u2019s method of calculation is that a user must be careful with the order in which the transactions appear in the file. This can be treacherous and difficult to understand when editing a very large input file. This difference is particularly visible in balance assertions . Ledger\u2019s balance assertions are attached to the postings of transaction directives and calculated in file order (I call these \u201cfile assertions\u201d). Beancount\u2019s balance assertions are separate directives that are applied at the beginning of the date for which they are declared, regardless of their position in the file (call these \u201cdated assertions\u201d). I believe that dated assertions are more useful and less error-prone, as they do not depend on the ordering of declarations. On the other hand, Ledger-style file assertions naturally support balance checks on intra-day balances without having to specify the time on transactions, which is impossible with dated assertions. For this reason, a proposal has been written up to consider implementing file assertions in Beancount (in addition to its dated assertions). This will probably be carried out as a plugin. Ledger does not support dated assertions. Account Types \uf0c1 Beancount accounts must have a particular type from one of five categories: Assets, Liabilities, Income, Expenses and Equity. Ledger accounts are not constrained in this way, you can define any root account you desire and there is no requirement to identify an account as belonging to one of these categories. This reflects the more liberal approach of Ledger: its design aims to be a more general \u201ccalculator\u201d for anything you want. No account types are enforced or used by the system. In my experience, I haven\u2019t seen any case where I could not classify one of my accounts in one of those categories. For the more exotic commodities, e.g., \u201cUS dollars allowable to contribute to an IRA\u201d, it might require a bit of imagination to understand which account fits which category, but there is a logic to it: if the account has an absolute value that we care about, then it is an Assets or Liabilities account; if we care only about the transitional values, or the value accumulated during a time period, then it should be an Income or Expenses account. If the sign is positive, then it should be an Assets or Expenses account; conversely, if the sign is negative, it should be a Liabilities or Income account. Equity accounts are almost never used explicitly, and are defined and used by Beancount itself to transfer opening balances, retained earnings and net income to the balance sheet report for a particular reporting period (any period you choose). This principle makes it easy to resolve which type an account should have. I have data from 2008 to 2014 and have been able to represent everything I ever wanted using these five categories. Also, I don\u2019t think asking the user to categorize their accounts in this way is limiting in any way; it just requires a bit of foresight. The reason for requiring these account types is that it allows us to carry out logic based on their types. We can isolate the Income and Expenses accounts and derive an income statement and a single net income value. We can then transfer retained earnings (income from before the period under consideration) and net income (income during the period) to Equity accounts and draw up a balance sheet. We can generate lists of holdings which automatically exclude income and expenses, to compute net worth, value per account, etc. In addition, we can use account types to identify external flows to a group of accounts and compute the correct return on investments for these accounts in a way that can be compared with a target allocation\u2019s market return (note: not integrated yet, but prototyped and spec\u2019ed out, it works). The bottom line is that having account types is a useful attribute, so we enforce that you should choose a type for each account. The absence of account types is probably also why Ledger does not provide a balance sheet or income statement reports, only a trial balance. The advantage is an apparently looser and more permissive naming structure. But also note that requiring types does not itself cause any difference in calculations between the two systems, you can still accumulate any kind of \"beans\" you may want in these accounts, it is no less general. The type is only extra information that Beancount\u2019s reporting makes use of. Transactions Must Balance \uf0c1 Beancount transactions are required to balance, period. I make no compromise in this, there is no way out. The benefit of this is that the sum of the balance amounts of the postings of any subset of transactions is always precisely zero (and I check for it). Ledger allows the user two special kinds of postings: Virtual postings : These are postings input with parentheses around them, and they are not considered in the transaction balance\u2019s sum, you can put any value in them without causing an error. Balanced virtual postings : These postings are input with square brackets around them. These are less permissive: the set of postings in square brackets is enforced to balance within itself. The second case can be shown to be equivalent to two transactions: a transaction with the set of regular postings, and a separate transaction with only the balanced virtual ones (this causes no problem). The first case is the problematic one: in attempting to solve accounting problems, beginning users routinely revert to them as a cop-out instead of modeling their problem with the double-entry method. It is apparent that most adopters of CLI accounting systems are computer scientists and not accounting professionals, and as we are all learning how to create our charts of accounts and fill up our ledgers, we are making mistakes. Oftentimes it is truly not obvious how to solve these problems; it simply requires experience. But the fact is that in 8 years\u2019 worth of usage, there isn\u2019t a single case I have come across that truly required having virtual postings. The first version of Beancount used to have support for virtual postings, but I\u2019ve managed to remove all of them over time. I was always able to come up with a better set of accounts, or to use an imaginary currency that would allow me to track whatever I needed to track. And it has always resulted in a better solution with unexpected and sometimes elegant side-effects. But these systems have to be easy to use, so how do we address this problem? The mailing-list is a good place to begin and ask questions, where people share information regarding how they have solved similar problems (there aren\u2019t that many \u201caccounting problems\u201d per-se in the first place). I am also in the process of documenting all the solutions that I have come up with to solve my own accounting problems in the Beancount Cookbook , basically everything I\u2019ve learned so far; this is work in progress. I hope for this evolving document to become a helpful reference to guide others in coming up with solutions that fit the double-entry accounting framework, and to provide ample examples that will act as templates for others to replicate, to fit in their own data. In the words of Ledger\u2019s author: \u201cIf people don't want to use them [virtual accounts], that's fine. But Ledger is not an accounting tool; it's a tool that may be used to do accounting. As such, I believe virtual accounts serve a role that others with non-accounting problems may wish to fill.\u201d I respectfully beg to differ. Therefore Beancount takes a more radical stance and explicitly avoids supporting virtual postings. If you feel strongly that you should need them, you should use Ledger. Numbers and Precision of Operations \uf0c1 Beancount, Ledger and HLedger all differ in how they represent their numbers internally, and in how they handle the precision of balance checks for a transaction\u2019s postings. First, about how number are represented: Ledger uses rational numbers in an attempt to maintain the full precision of numbers resulting from mathematical operations. This works, but I believe this is perhaps not the most appropriate choice . The great majority of the cases where operations occur involve the conversion from a number of units and a price or a cost to the total value of an account\u2019s posted change (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters. I think the approach implemented by Ledger is to keep as much of the original precision as possible. Beancount chooses a decimal number representation to store the numbers parsed from the input with the same precision they are written as. This method suffers from the same problem as using rational numbers does in that the result of mathematical operations between the decimal numbers will currently be stored with their full precision (albeit in decimal). Admittedly, I have yet to apply explicit quantization where required, which would be the correct thing to do. A scheme has to be devised to infer suitable precisions for automatically quantizing the numbers after operations. The decimal representation provides natural opportunities for rounding after operations, and it is a suitable choice for this, implementations even typically provide a context for the precision to take place. Also note that it will never be required to store numbers to an infinite precision: the institutions never do it themselves. HLedger, oddly enough, selects \u201cdouble\u201d fractional binary representation for its prices . This is an unfortunate choice, a worse one than using a precise representation: fractional decimal numbers input by the user are never represented precisely by their corresponding binary form. So all the numbers are incorrect but \u201cclose enough\u201d that it works overall, and the only way to display a clean final result is by rounding to a suitable number of digits at the time of rendering a report. One could argue that the large number of digits provided by a 64-bit double representation is unlikely to cause significant errors given the quantity of operations we make\u2026 but binary rounding error could potentially accumulate, and the numbers are basically all incorrectly stored internally, rounded to their closest binary relative. Given that our task is accounting, why not just represent them correctly? Secondly, when checking that the postings of a transaction balance to zero, with all three systems it is necessary to allow for some tolerance on those amounts. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits. You need to allow for some looseness somehow. The systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used. Ultimately, it depends on the numbers of digits used to represent the particular postings. We have a proposal en route to fix this. I am planning to fix this: Beancount will eventually derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults (this is still in the works - Oct 2014 ). Half of the most precise digit will be the tolerance. This will be derived similarly to HLedger\u2019s method, but for each transaction separately. This will allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision. No global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context. Rounding error will be optionally accumulated to an Equity account if you want to monitor it . As for automatically quantizing the numbers resulting from operations, I still need to figure out an automatic method for doing so. Filtering at the Transactional Level \uf0c1 Another area where the systems differ is in that Beancount will not support filtering at the postings level but only at the transaction level. That is, when applying filters to the input data, even filtering that applies predicates to postings, only sets of complete transactions will be produced. This is carried out in order to produce sets of postings whose sum balances to exactly zero. We will not ignore only some postings of a transaction. We will nevertheless allow reports to cull out arbitrary subsets of accounts once all balances have been computed. Ledger filtering works at either the transactional or postings level . I think this is confusing. I don\u2019t really understand why it works this way or how it actually works. (Note that this is not a very relevant point at this moment, because I have yet to implement custom arbitrary filtering; the only filtering available at the moment are \u201cviews\u201d you can obtain through the web interface, but I will soon provide a simple logical expression language to apply custom filters to the parsed transactions as in Ledger, as I think it\u2019s a powerful feature. Until recently, most reports were only rendered through a web interface and it wasn\u2019t as needed as it is now that Beancount implements console reports.) Extension Mechanisms \uf0c1 Both Beancount and Ledger provide mechanisms for writing scripts against their corpus of data. These mechanisms are all useful but different. Ledger provides A custom expression language which it interprets to produce reports A command to export contents to XML A command to export contents to LISP A library of Python bindings which provides access to its C++ data structures (\u2026 others?) (Note: due to its dependencies, C++ features being used and build system, I have found it difficult to build Ledger on my vanilla Ubuntu machine or Mac OS computer. Building it with the Python bindings is even more difficult. If you have the patience, the time and that\u2019s not a problem for you, great, but if you can find a pre-packaged version I would recommend using that instead.) Beancount provides A native Python plugin system whereby you may specify lists of Python modules to import and call to filter and transform the parsed directives to implement new features; An easy loader function that allows you to access the internal data structures resulting from parsing and processing a Beancount ledger. This is also a native Python library. So basically, you must write Python to extend Beancount. I\u2019m planning to provide output to XML and SQL as well (due to the simple data structures I\u2019m using these will be both very simple to implement). Moreover, using Beancount\u2019s own \u201cprinter\u201d module produces text that is guaranteed to parse back into exactly the same data structure (Beancount guarantees round-tripping of its syntax and its data structures). One advantage is that the plugins system allows you to perform in-stream arbitrary transformations of the list of directives produced, and this is a great way to prototype new functionality and easier syntax. For example, you can allocate a special tag that will trigger some arbitrary transformation on such tagged transactions. And these modules don\u2019t have to be integrated with Beancount: they can just live anywhere on your own PYTHONPATH, so you can experiment without having to contribute new features upstream or patch the source code. (Note: Beancount is implemented in Python 3, so you might have to install a recent version in order to work with it, such as Python-3.4, if you don\u2019t have it. At this point, Python 3 is becoming pretty widespread, so I don\u2019t see this as a problem, but you might be using an older OS.) Automated Transactions via Plugins \uf0c1 Ledger provides a special syntax to automatically insert postings on existing transactions , based on some matching criteria. The syntax allows the user to access some of a posting\u2019s data, such as amount and account name. The syntax is specialized to also allow the application of transaction and posting \u201ctags.\u201d Beancount allows you to do the same thing and more via its plugin extension mechanism. Plugins that you write are able to completely modify, create or even delete any object and attribute of any object in the parsed flow of transactions, allowing you to carry out as much automation and summarization as you like. This does not require a special syntax - you simply work in Python, with access to all its features - and you can piggyback your automation on top of existing syntax. Some examples are provided under beancount.plugins.* . There is an argument to be made, however, for the availability of a quick and easy method for specifying the most common case of just adding some postings. I\u2019m not entirely convinced yet, but Beancount may acquire this eventually (you could easily prototype this now in a plugin file if you wanted). No Support for Time or Effective Dates \uf0c1 Beancount does not represent the intra-day time of transactions, its granularity is a day. Ledger allows you to specify the time of transactions down to seconds precision. I choose to limit its scope in the interest of simplicity, and I also think there are few use cases for supporting intra-day operations. Note that while Beancount\u2019s maximum resolution is one day, when it sorts the directives it will maintain the relative order of all transactions that occurs within one day, so it is possible to represent multiple transactions occurring on the same day in Beancount and still do correct inventory bookings. But I believe that if you were to do day-trading you would need a more specialized system to compute intra-day returns and do technical analysis and intraday P/L calculations. Beancount is not suited for those (a lot of other features would be needed if that was its scope). Ledger also has support for effective dates which are essentially an alternative date for the transaction. Reporting features allow Ledger users to use the main date or the alternative date. I used to have this feature in Beancount and I removed it, mainly because I did not want to introduce reporting options, and to handle two dates, say a transaction date and a settlement date, I wanted to enforce that at any point in time all transactions would balance. I also never made much use of it which indicates it was probably futile. Handling postings to occur at different points in time would have created imbalances, or I would have had to come up with a solution that involved \u201climbo\u201d or \u201ctransfer\u201d accounts. I preferred to just remove the feature: in 8 years of data, I have always been able to fudge the dates to make everything balance. This is not a big issue. Note that handling those split transaction and merging them together will be handled; a proposal is underway. Documents \uf0c1 Beancount offers support for integrating a directory hierarchy of documents with the contents of a ledger\u2019s chart of accounts. You can provide a directory path and Beancount will automatically find and create corresponding Document directives for filenames that begin with a date in directories that mirror account names, and attach those documents to those accounts. And given a bit of configuration, the bean-file tool can automatically file downloaded documents to such a directory hierarchy. Ledger\u2019s binding of documents to transactions works by generic meta-data. Users can attach arbitrary key-value pairs to their transactions, and those can be filenames. Beyond that, there is no particular support for document organization. Simpler and More Strict \uf0c1 Finally, Beancount has a generally simpler input syntax than Ledger. There are very few command-line options\u2014and this is on purpose, I want to localize all the input within the file\u2014and the directive syntax is more homogeneous: all transactions begin with a date and a keyword. If the argument of simplicity appeals to you, you might prefer to work with Beancount. I feel that the number of options offered in Ledger is daunting, and I could not claim to understand all of the possible ways they might interact with each other. If this does not worry you, you might prefer to use Ledger. It is also more strict than Ledger. Certain kinds of Beancount inputs are not valid. Any transaction in an account needs to have an open directive to initiate the account, for example (though some of these constraints can be relaxed via optional plugins). If you maintain a Beancount ledger, you can expect to have to normalize it to fix a number of common errors being reported. I view this as a good thing: it detects many potential problems and applies a number of strict constraints to its input which allows us to make reasonable assumptions later on when we process the stream of directives. If you don\u2019t care about precision or detecting potential mistakes, Ledger will allow you to be more liberal. On the other hand if you care to produce a precise and flawless account of transactions, Beancount offers more support in its validation of your inputs. Web Interface \uf0c1 Beancount has a built-in web interface, and there is an external project called Fava which significantly improves on the same theme. This is the default mode for browsing reports. I believe HLedger also has a web interface as well. Missing Features \uf0c1 Beancount generally attempts to minimize the number of features it provides. This is in contrast with Ledger\u2019s implementation, which has received a substantial amount of feature addition in order to experiment with double-entry bookkeeping. There are a large number of options. This reflects a difference in approach: I believe that there is a small core of essential features to be identified and that forward progress is made when we are able to minimize and remove any feature that is not strictly necessary. My goal is to provide the smallest possible kernel of features that will allow one to carry out the full spectrum of bookkeeping activities, and to make it possible for users to extend the system to automate repetitive syntax. But here is a list of features that Beancount does not support that are supported in Ledger, and that I think would be nice to have eventually. The list below is unlikely to be exhaustive. Console Output \uf0c1 Beancount\u2019s original implementation focused on providing a web view for all of its contents. During the 2.0 rewrite I began implementing some console/text outputs, mainly because I want to be able for reports to be exportable to share with others. I have a trial balance view (like Ledger\u2019s \u201cbal\u201d report) but for now the journal view isn\u2019t implemented. Ledger, on the other hand, has always focused on console reports. I\u2019ll make all the reports in Beancount support output to text format first thing after the initial release, as I\u2019m increasingly enjoying text reports. Use bean-query --list-formats to view current status of this. Filtering Language \uf0c1 Beancount does not yet have a filtering language. Until recently, its web interface was the main mode for rendering reports and exploring the contents of its ledger, and it provided limited subsets of transactions in the form of \u201cviews\u201d, e.g., per-year, per-tag, etc. Having a filtering language in particular allows one to do away with many sub-accounts. I want to simplify my chart of accounts so I need this. I\u2019m working on adding a simple logical expression language to do arbitrary filters on the set of Beancount transactions. This is straightforward to implement and a high priority. No Meta-data \uf0c1 Beancount does not currently support meta-data. Ledger users routinely make liberal use of metadata. This has been identified as a powerful feature and a prototype has already been implemented. Meta-data will be supported on any directive type as well as on any posting. A dictionary of key-value pairs will be attached to each of these objects. Supported values will include strings, dates, numbers, currencies and amounts. So far the plan is to restrict Beancount\u2019s own code to make no specific use of meta-data, on purpose. The meta-data will be strictly made available for user-plugins and custom user scripts to make use of. No Arithmetic Expressions \uf0c1 Beancount does not support arbitrary expression evaluation in the syntax, in the places where numbers are allowed in the input. Ledger does. I have had no use for these yet, but I have no particular reason against adding this, I just haven\u2019t implemented it, as I don\u2019t need it myself. I think an implementation would be straightforward and very low risk, a simple change to the parser and there is already a callback for numbers. I think there are many legitimate uses for it. Limited Support for Unicode \uf0c1 Beancount supports UTF8 or and other encodings in strings only (that is, for input that is in quotes). For example, you can enter payees and narrations with non-ASCII characters, but not account names (which aren\u2019t in quotes). Ledger supports other encodings over the entire file. The reason for the lack of a more general encoding support in Beancount is the current limitation of tokenizer tools. I\u2019ve been using GNU flex to implement my lexer and it does not support arbitrary encodings. I just need to write a better lexer and make that work with Bison , it\u2019s not a difficult task. I will eventually write my own lexer manually\u2014this has other advantages\u2014and will write it to support Unicode (Python 3 has full support for this, so all that is required is to modify the lexer, which is one simple compilation unit). This is a relatively easy and isolated task to implement. No Forecasting or Periodic Transactions \uf0c1 Beancount has no support for generating periodic transactions for forecasting, though there is a plugin provided that implements a simplistic form of it to be used as an example for how plugins work (see beancount.plugins.forecast ). Ledger supports periodic transaction generation . I do want to add this to core Beancount eventually, but I want to define the semantics very clearly before I do. Updating one\u2019s ledger is essentially the process of copying and replicating transactional data that happens somewhere else. I don\u2019t believe that regular transactions are that \u201cregular\u201d in reality; in my experience, there is always some amount of small variations in real transactions that makes it impossible to automatically generate a series of transactions by a generator in a way that would allow the user to forgo updating them one-by-one. What it is useful for in my view, is for generating tentative future transactions. I feel strongly that those transactions should be limited not to straddle reconciled history, and that reconciled history should replace any kind of automatically generated transactions. I have some fairly complete ideas for implementing this feature, but I\u2019m not using forecasting myself at the moment, so it\u2019s on the backburner. While in theory you can forecast using Ledger\u2019s periodic transactions, to precisely represent your account history you will likely need to adjust the beginning dates of those transactions every time you append new transactions to your accounts and replace forecasted ones. I don\u2019t find the current semantics of automated transactions in Ledger to be very useful beyond creating an approximation of account contents. (In the meantime, given the ease of extending Beancount with plugins, I suggest you begin experimenting with forecast transactions on your own for now, and if we can derive a generic way to create them, I\u2019d be open to merging that into the main code.)","title":"A Comparison of Beancount and Ledger Hledger"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#a-comparison-of-beancount-and-ledger","text":"Martin Blais , September 2014 http://furius.ca/beancount/doc/comparison The question of how Beancount differs from Ledger & HLedger has come up a few times on mailing-lists and in private emails. This document highlights key differences between these systems, as they differ sharply in their design and implementations. Keep in mind that this document is written from the perspective of Beancount and as its author, reflects my own biased views for what the design of a CLI accounting system should be. My purpose here is not to shoot down other systems, but rather to highlight material differences to help newcomers understand how these systems vary in their operation and capabilities, and perhaps to stimulate a fruitful discussion about design choices with the other developers.","title":"A Comparison of Beancount and Ledger"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#philosophical-differences","text":"(See this thread .) First, Ledger is optimistic. It assumes it's easy to input correct data by a user. My experience with data entry of the kind we're doing is that it's impossible to do this right without many automated checks. Sign errors on unasserted accounts are very common, for instance. In contrast, Beancount is highly pessimistic. It assumes the user is unreliable. It imposes a number of constraints on the input. For instance, if you added a share of AAPL at $100 to an empty account it won't let you remove a share of AAPL at $101 from it; you just don't have one. It doesn't assume the user is able or should be relied upon to input transactions in the correct order (dated assertions instead of file-order assertions). It optionally checks that proceeds match sale price (sellgains plugin). And it allows you to place extra constraints on your chart of accounts, e.g. a plugin that refuses postings to non-leaf accounts, or that refuses more than one commodity per account, or that requires you declare all accounts with Open directives; choose your level of pedanticity a-la-carte. It adds more automated cross-checks than the double-entry method provides. After all, cross-checking is why we choose to use the DE method in the first place, why not go hardcore on checking for correctness? Beancount should appeal to anyone who does not trust themselves too much. And because of this, it does not provide support for unbalanced/virtual postings; it's not a shortcoming, it's on purpose. Secondly, there's a design ethos difference. As is evidenced in the user manual, Ledger provides a myriad of options. This surely will be appealing to many, but to me it seems it has grown into a very complicated monolithic tool. How these options interact and some of the semantic consequences of many of these options are confusing and very subtle. Beancount offers a minimalistic approach: while there are some small number of options , it tries really hard not to have them. And those options that do affect the semantics of transactions always appear in the input file (nothing on the command-line) and are distinct from the options of particular tools. Loading a file always results in the same stream of transactions, regardless of the reporting tool that will consume them. The only command-line options present are those which affect the particular behavior of the reporting tool invoked; those never change the semantics of the stream itself. Thirdly, Beancount embraces stream processing to a deeper extent. Its loader creates a single ordered list of directives, and all the directives share some common attributes (a name, a date, metadata). This is all the data. Directives that are considered \"grammar\" in Ledger are defined as ordinary directive objects, e.g. \"Open\" is nothing special in Beancount and does nothing by itself. It's simply used in some routines that apply constraints (an account appears, has an Open directive been witnessed prior?) or that might want to hang per-account metadata to them. Prices are also specified as directives and are embedded in the stream, and can be generated in this way. All internal operations are defined as processing and outputting a stream of directives. This makes it possible to allow a user to insert their own code inside the processing pipeline to carry out arbitrary transformations on this stream of directives\u2014anything is possible, unlimited by the particular semantics of an expression language. It's a mechanism that allows users to build new features by writing a short add-on in Python, which gets run at the core of Beancount, not an API to access its data at the edges. If anything, Beancount's own internal processing will evolve towards doing less and less and moving all the work to these plugins, perhaps even to the extent of allowing plugins to declare the directive types (with the exception of Transaction objects). It is evolving into a shallow driver that just puts together a processing pipeline to produce a stream of directives, with a handy library and functional operations.","title":"Philosophical Differences"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#specific-differences","text":"","title":"Specific Differences"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#inventory-booking-cost-basis-treatment","text":"Beancount applies strict rules regarding reductions in positions from inventories tracking the contents of its accounts. This means that you can only take out of an account something that you placed in it previously (in time), or an error will be generated. This is enforced for units \u201cheld at cost\u201d (e.g., stock shares), to ensure that no cost basis can ever leak from accounts, we can detect errors in data entry for trades (which are all too common), and we are able to correctly calculate capital gains. In contrast, Ledger does not implement inventory booking checks over time: all lots are simply accumulated regardless of the previous contents of an inventory (there is no distinction between lot addition vs. reduction). In Beancount, reductions to the contents of an inventory are required to match particular lots with a specified cost basis. In Ledger, the output is slightly misleading about this: in order to simplify the reporting output the user may specify one of a few types of lot merging algorithms. By default, the sum of units of all lots is printed, but by using these options you can tell the reporting generation to consider the cost basis (what it calls \u201cprices\u201d) and/or the dates the lots were created at in which case it will report the set of lots with distinct cost bases and/or dates individually. You can select which type of merging occurs via command-line options, e.g. --lot-dates. Most importantly, this means that it is legal in Ledger to remove from an account a lot that was never added to it. This results in a mix of long and short lots, which do not accurately represent the actual changes that occur in accounts. However, it trivially allows for average cost basis reporting. I believe that this is not only confusing, but also an incorrect treatment of account inventories and have argued it can lead to leakage and incorrect calculations. More details and an example are available here . Furthermore, until recently Ledger was not using cost basis to balance postings in a way that correctly computes capital gains. For this reason I suspect Ledger users have probably not used it to compute and compare their gains against the values reported by their broker. In order for Beancount to detect such errors meaningfully and implement a strict matching discipline when reducing lots, it turns out that the only constraint it needs to apply is that a particular account not be allowed to simultaneously hold long and short positions of the same commodity. For example, all it has to do is enforce that you could not hold a lot of 1 GOOG units and a lot of -1 GOOG units simultaneously in the same inventory (regardless of their acquisition cost). Any reduction of a particular set of units is detected as a \u201clot reduction\u201d and a search is found for a lot that matches the specification of the reducing posting, normally, a search for a lot held at the same cost as the posting specifies. Enforcing this constraint is not of much concern in practice, as there are no cases where you would want both long and short positions in the same account, but if you ever needed this, you could simply resort to using separate accounts to hold your long and short positions (it is already recommended that you use a sub-account to track your positions in any one commodity anyway, this would be quite natural). Finally, Beancount is implementing a proposal that significantly extends the scope of its inventory booking: the syntax will allow a loose specification for lot reductions that makes it easy for users to write postings where there is no ambiguity, as well as supporting booking lots at their average cost as is common in Canada and in all tax-deferred accounts across the world. It will also provide a new syntax for specifying cost per unit and total cost at the same time, a feature which will make it possible to correctly track capital gains without commissions .","title":"Inventory Booking & Cost Basis Treatment"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#currency-conversions","text":"Beancount makes an important semantic distinction between simple currency conversions and conversions of commodities to be held at cost, i.e., for which we want to track the cost basis. For example, converting 20,000 USD to 22,000 CAD is a currency conversion (e.g., between banks), and after inserting the resulting CAD units in the destination account, they are not considered \u201cCAD at a cost basis of 1.1 USD each,\u201d they are simply left as CAD units in the account, with no record of the rate which was used to convert them. This models accurately how the real world operates. On the other hand, converting 5,000 USD into 10 units of GOOG shares with a cost basis of 500 USD each is treated as a distinct operation from a currency conversion: the resulting GOOG units have a particular cost basis attached to them, a memory of the price per unit is kept on the inventory lot (as well as the date) and strict rules are applied to the reduction of such lots (what I call \u201cbooking\u201d) as mentioned previously. This is used to calculate and report capital gains, and most importantly, it detects a lot of errors in data entry. In contrast, Ledger does not distinguish between these two types of conversions . Ledger treats currency conversions the same way as for the conversion of commodities held at cost. The reason that this is not causing as many headaches as one might intuit, is because there is no inventory booking - all lots at whichever conversion rate they occur accumulate in accounts, with positive or negative values - and for simple commodities (e.g. currencies, such as dollars), netting the total amount of units without considering the cost of each unit provides the correct answer. Inspecting the full list of an account\u2019s inventory lots is provided as an option (See Ledger\u2019s \u201c--lot-dates\u201d) and is not the default way to render account balances. I suspect few users make use of the feature: if you did render the list of lots for real-world accounts in which many currency conversions occurred in the past, you would observe a large number of irrelevant lots. I think the cost basis of currency conversions would be best elided instead. HLedger does not parse cost basis syntax and as such does not recognize it.","title":"Currency Conversions"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#isolation-of-inputs","text":"Beancount reads its entire input only from the text file you provide for it. This isolation is by design. There is no linkage to external data formats nor online services, such as fetchers for historical price values. Fetching and converting external data is disparate enough that I feel it should be the province of separate projects. These problem domains also segment very well and quite naturally: Beancount provides an isolated core which allows you to ingest all the transactional data and derive reports of various aggregations from it, and its syntax is the hinge that connects it to external transaction repositories or price databases. It isolates itself from the ugly details of external sources of data in this way. There are too many external formats for downloadable files that contains transactional information to be able to cover all of them. The data files you can obtain from most institutions are provided in various formats: OFX, Quicken, XLS or CSV, and looking at their contents makes it glaringly obvious that the programmers who built the codes that outputs them did not pay much attention to detail or the standard definition of their format; it\u2019s quite an ugly affair. Those files are almost always very messy, and the details of that messiness varies over time as well as these files evolve. Fetching historical or current price information is a similarly annoying task. While Yahoo and Google Finance are able to provide some basic level of price data for common stocks on US exchanges, when you need to fetch information for instruments traded on foreign exchanges, or instruments typically not traded on exchanges, such as mutual funds, either the data is not available, or if it is, you need to figure out what ticker symbol they decided to map it to, there are few standards for this. You must manually sign the ticker. Finally, it is entirely possible that you want to manage instruments for which there is no existing external price source, so it is necessary that your bookkeeping software provide a mechanism whereby you can manually input price values (both Beancount and Ledger provide a way). The same declaration mechanism is used for caching historical price data, so that Beancount need not require the network at all. Most users will want to write their own scripts for import, but some libraries exist: the beancount.ingest library (within Beancount) provides a framework to automate the identification, extraction of transactions from and filing of downloadable files for various institutions. See its design doc for details. In comparison, Ledger and HLedger support rudimentary conversions of transactions from CSV files as well as automated fetching of current prices that uses an external script you are meant to provide ( getquote ). CSV import is far insufficient for real world usage if you are to track all your accounts, so this needs to be extended. Hooking into an external script is the right thing to do, but Beancount favors taking a strong stance about this issue and instead not to provide code that would trigger any kind of network access nor support any external format as input. In Beancount you are meant to integrate price updates on your own, perhaps with your own script, and maybe bring in the data via an include file. (But if you don't like this, you could also write your own plugin module that could fetch live prices.)","title":"Isolation of Inputs"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#language-syntax","text":"Beancount\u2019s syntax is somewhat simpler and quite a bit more restrictive than Ledger\u2019s. For its 2.0 version, the Beancount syntax was redesigned to be easily specifiable to a parser generator by a grammar. The tokens were simplified in order to make tokenization unambiguous. For example, Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. (On the other hand, currencies with numbers require quoting in Ledger.) Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Description strings must be quoted, like this: \u201cAMEX PMNT\u201d. No freestyle text as strings is supported anymore. Dates are only parsed from ISO8601 format, that is, \u201cYYYY-MM-DD\u201d. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. Apart from the tag stack, all context information has been removed. There is no account alias, for example, nor is there a notion of \u201capply\u201d as in Ledger (see \u201capply root\u201d and \u201capply tag\u201d). It requires a bit more verbose input\u2014full account names\u2014and so assumes that you have account name completion setup in your editor. As a side effect, these changes make the input syntax look a bit more like a programming language. These restrictions may annoy some users, but overall they make the task of parsing the contents of a ledger simpler and the resulting simplicity will pave the way for people to more easily write parsers in other languages. (Some subtleties remain around parsing indentation, which is meaningful in the syntax, but should be easily addressable in all contexts by building a custom lexer.) Due to its looser and more user-friendly syntax, Ledger uses a custom parser. If you need to parse its contents from another language, the best approach is probably to create bindings into its source code or to use it to export the contents of a ledger to XML and then parse that (which works well). I suspect the parsing method might be reviewed in the next version of Ledger, because using a parser generator is liberating for experimentation.","title":"Language Syntax"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#order-independence","text":"Beancount offers a guarantee that the ordering of directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input file and reorder any declaration as is most convenient for you without having to worry about how the software will make its calculations. Not even directives that declare accounts (\u201cOpen\u201d) are required to appear before these accounts get used in the file. All directives are parsed and then basically stably sorted before any calculation or validation occurs. This also makes it trivial to implement inclusions of multiple files (you can just concatenate the files if you want). In contrast, Ledger processes the amounts on its postings as it parses the input file. In terms of implementation, this has the advantage that only a single pass is needed to check all the assertions and balances, whereas in Beancount, numerous passes are made on the entire list of directives (this has not been much of a problem in Beancount, however, because even a realistically large number of transactions is rather modest for our computers; most of Beancount\u2019s processing time is due to parsing and numerical calculations). An unfortunate side-effect of Ledger\u2019s method of calculation is that a user must be careful with the order in which the transactions appear in the file. This can be treacherous and difficult to understand when editing a very large input file. This difference is particularly visible in balance assertions . Ledger\u2019s balance assertions are attached to the postings of transaction directives and calculated in file order (I call these \u201cfile assertions\u201d). Beancount\u2019s balance assertions are separate directives that are applied at the beginning of the date for which they are declared, regardless of their position in the file (call these \u201cdated assertions\u201d). I believe that dated assertions are more useful and less error-prone, as they do not depend on the ordering of declarations. On the other hand, Ledger-style file assertions naturally support balance checks on intra-day balances without having to specify the time on transactions, which is impossible with dated assertions. For this reason, a proposal has been written up to consider implementing file assertions in Beancount (in addition to its dated assertions). This will probably be carried out as a plugin. Ledger does not support dated assertions.","title":"Order Independence"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#account-types","text":"Beancount accounts must have a particular type from one of five categories: Assets, Liabilities, Income, Expenses and Equity. Ledger accounts are not constrained in this way, you can define any root account you desire and there is no requirement to identify an account as belonging to one of these categories. This reflects the more liberal approach of Ledger: its design aims to be a more general \u201ccalculator\u201d for anything you want. No account types are enforced or used by the system. In my experience, I haven\u2019t seen any case where I could not classify one of my accounts in one of those categories. For the more exotic commodities, e.g., \u201cUS dollars allowable to contribute to an IRA\u201d, it might require a bit of imagination to understand which account fits which category, but there is a logic to it: if the account has an absolute value that we care about, then it is an Assets or Liabilities account; if we care only about the transitional values, or the value accumulated during a time period, then it should be an Income or Expenses account. If the sign is positive, then it should be an Assets or Expenses account; conversely, if the sign is negative, it should be a Liabilities or Income account. Equity accounts are almost never used explicitly, and are defined and used by Beancount itself to transfer opening balances, retained earnings and net income to the balance sheet report for a particular reporting period (any period you choose). This principle makes it easy to resolve which type an account should have. I have data from 2008 to 2014 and have been able to represent everything I ever wanted using these five categories. Also, I don\u2019t think asking the user to categorize their accounts in this way is limiting in any way; it just requires a bit of foresight. The reason for requiring these account types is that it allows us to carry out logic based on their types. We can isolate the Income and Expenses accounts and derive an income statement and a single net income value. We can then transfer retained earnings (income from before the period under consideration) and net income (income during the period) to Equity accounts and draw up a balance sheet. We can generate lists of holdings which automatically exclude income and expenses, to compute net worth, value per account, etc. In addition, we can use account types to identify external flows to a group of accounts and compute the correct return on investments for these accounts in a way that can be compared with a target allocation\u2019s market return (note: not integrated yet, but prototyped and spec\u2019ed out, it works). The bottom line is that having account types is a useful attribute, so we enforce that you should choose a type for each account. The absence of account types is probably also why Ledger does not provide a balance sheet or income statement reports, only a trial balance. The advantage is an apparently looser and more permissive naming structure. But also note that requiring types does not itself cause any difference in calculations between the two systems, you can still accumulate any kind of \"beans\" you may want in these accounts, it is no less general. The type is only extra information that Beancount\u2019s reporting makes use of.","title":"Account Types"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#transactions-must-balance","text":"Beancount transactions are required to balance, period. I make no compromise in this, there is no way out. The benefit of this is that the sum of the balance amounts of the postings of any subset of transactions is always precisely zero (and I check for it). Ledger allows the user two special kinds of postings: Virtual postings : These are postings input with parentheses around them, and they are not considered in the transaction balance\u2019s sum, you can put any value in them without causing an error. Balanced virtual postings : These postings are input with square brackets around them. These are less permissive: the set of postings in square brackets is enforced to balance within itself. The second case can be shown to be equivalent to two transactions: a transaction with the set of regular postings, and a separate transaction with only the balanced virtual ones (this causes no problem). The first case is the problematic one: in attempting to solve accounting problems, beginning users routinely revert to them as a cop-out instead of modeling their problem with the double-entry method. It is apparent that most adopters of CLI accounting systems are computer scientists and not accounting professionals, and as we are all learning how to create our charts of accounts and fill up our ledgers, we are making mistakes. Oftentimes it is truly not obvious how to solve these problems; it simply requires experience. But the fact is that in 8 years\u2019 worth of usage, there isn\u2019t a single case I have come across that truly required having virtual postings. The first version of Beancount used to have support for virtual postings, but I\u2019ve managed to remove all of them over time. I was always able to come up with a better set of accounts, or to use an imaginary currency that would allow me to track whatever I needed to track. And it has always resulted in a better solution with unexpected and sometimes elegant side-effects. But these systems have to be easy to use, so how do we address this problem? The mailing-list is a good place to begin and ask questions, where people share information regarding how they have solved similar problems (there aren\u2019t that many \u201caccounting problems\u201d per-se in the first place). I am also in the process of documenting all the solutions that I have come up with to solve my own accounting problems in the Beancount Cookbook , basically everything I\u2019ve learned so far; this is work in progress. I hope for this evolving document to become a helpful reference to guide others in coming up with solutions that fit the double-entry accounting framework, and to provide ample examples that will act as templates for others to replicate, to fit in their own data. In the words of Ledger\u2019s author: \u201cIf people don't want to use them [virtual accounts], that's fine. But Ledger is not an accounting tool; it's a tool that may be used to do accounting. As such, I believe virtual accounts serve a role that others with non-accounting problems may wish to fill.\u201d I respectfully beg to differ. Therefore Beancount takes a more radical stance and explicitly avoids supporting virtual postings. If you feel strongly that you should need them, you should use Ledger.","title":"Transactions Must Balance"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#numbers-and-precision-of-operations","text":"Beancount, Ledger and HLedger all differ in how they represent their numbers internally, and in how they handle the precision of balance checks for a transaction\u2019s postings. First, about how number are represented: Ledger uses rational numbers in an attempt to maintain the full precision of numbers resulting from mathematical operations. This works, but I believe this is perhaps not the most appropriate choice . The great majority of the cases where operations occur involve the conversion from a number of units and a price or a cost to the total value of an account\u2019s posted change (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters. I think the approach implemented by Ledger is to keep as much of the original precision as possible. Beancount chooses a decimal number representation to store the numbers parsed from the input with the same precision they are written as. This method suffers from the same problem as using rational numbers does in that the result of mathematical operations between the decimal numbers will currently be stored with their full precision (albeit in decimal). Admittedly, I have yet to apply explicit quantization where required, which would be the correct thing to do. A scheme has to be devised to infer suitable precisions for automatically quantizing the numbers after operations. The decimal representation provides natural opportunities for rounding after operations, and it is a suitable choice for this, implementations even typically provide a context for the precision to take place. Also note that it will never be required to store numbers to an infinite precision: the institutions never do it themselves. HLedger, oddly enough, selects \u201cdouble\u201d fractional binary representation for its prices . This is an unfortunate choice, a worse one than using a precise representation: fractional decimal numbers input by the user are never represented precisely by their corresponding binary form. So all the numbers are incorrect but \u201cclose enough\u201d that it works overall, and the only way to display a clean final result is by rounding to a suitable number of digits at the time of rendering a report. One could argue that the large number of digits provided by a 64-bit double representation is unlikely to cause significant errors given the quantity of operations we make\u2026 but binary rounding error could potentially accumulate, and the numbers are basically all incorrectly stored internally, rounded to their closest binary relative. Given that our task is accounting, why not just represent them correctly? Secondly, when checking that the postings of a transaction balance to zero, with all three systems it is necessary to allow for some tolerance on those amounts. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits. You need to allow for some looseness somehow. The systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used. Ultimately, it depends on the numbers of digits used to represent the particular postings. We have a proposal en route to fix this. I am planning to fix this: Beancount will eventually derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults (this is still in the works - Oct 2014 ). Half of the most precise digit will be the tolerance. This will be derived similarly to HLedger\u2019s method, but for each transaction separately. This will allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision. No global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context. Rounding error will be optionally accumulated to an Equity account if you want to monitor it . As for automatically quantizing the numbers resulting from operations, I still need to figure out an automatic method for doing so.","title":"Numbers and Precision of Operations"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#filtering-at-the-transactional-level","text":"Another area where the systems differ is in that Beancount will not support filtering at the postings level but only at the transaction level. That is, when applying filters to the input data, even filtering that applies predicates to postings, only sets of complete transactions will be produced. This is carried out in order to produce sets of postings whose sum balances to exactly zero. We will not ignore only some postings of a transaction. We will nevertheless allow reports to cull out arbitrary subsets of accounts once all balances have been computed. Ledger filtering works at either the transactional or postings level . I think this is confusing. I don\u2019t really understand why it works this way or how it actually works. (Note that this is not a very relevant point at this moment, because I have yet to implement custom arbitrary filtering; the only filtering available at the moment are \u201cviews\u201d you can obtain through the web interface, but I will soon provide a simple logical expression language to apply custom filters to the parsed transactions as in Ledger, as I think it\u2019s a powerful feature. Until recently, most reports were only rendered through a web interface and it wasn\u2019t as needed as it is now that Beancount implements console reports.)","title":"Filtering at the Transactional Level"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#extension-mechanisms","text":"Both Beancount and Ledger provide mechanisms for writing scripts against their corpus of data. These mechanisms are all useful but different. Ledger provides A custom expression language which it interprets to produce reports A command to export contents to XML A command to export contents to LISP A library of Python bindings which provides access to its C++ data structures (\u2026 others?) (Note: due to its dependencies, C++ features being used and build system, I have found it difficult to build Ledger on my vanilla Ubuntu machine or Mac OS computer. Building it with the Python bindings is even more difficult. If you have the patience, the time and that\u2019s not a problem for you, great, but if you can find a pre-packaged version I would recommend using that instead.) Beancount provides A native Python plugin system whereby you may specify lists of Python modules to import and call to filter and transform the parsed directives to implement new features; An easy loader function that allows you to access the internal data structures resulting from parsing and processing a Beancount ledger. This is also a native Python library. So basically, you must write Python to extend Beancount. I\u2019m planning to provide output to XML and SQL as well (due to the simple data structures I\u2019m using these will be both very simple to implement). Moreover, using Beancount\u2019s own \u201cprinter\u201d module produces text that is guaranteed to parse back into exactly the same data structure (Beancount guarantees round-tripping of its syntax and its data structures). One advantage is that the plugins system allows you to perform in-stream arbitrary transformations of the list of directives produced, and this is a great way to prototype new functionality and easier syntax. For example, you can allocate a special tag that will trigger some arbitrary transformation on such tagged transactions. And these modules don\u2019t have to be integrated with Beancount: they can just live anywhere on your own PYTHONPATH, so you can experiment without having to contribute new features upstream or patch the source code. (Note: Beancount is implemented in Python 3, so you might have to install a recent version in order to work with it, such as Python-3.4, if you don\u2019t have it. At this point, Python 3 is becoming pretty widespread, so I don\u2019t see this as a problem, but you might be using an older OS.)","title":"Extension Mechanisms"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#automated-transactions-via-plugins","text":"Ledger provides a special syntax to automatically insert postings on existing transactions , based on some matching criteria. The syntax allows the user to access some of a posting\u2019s data, such as amount and account name. The syntax is specialized to also allow the application of transaction and posting \u201ctags.\u201d Beancount allows you to do the same thing and more via its plugin extension mechanism. Plugins that you write are able to completely modify, create or even delete any object and attribute of any object in the parsed flow of transactions, allowing you to carry out as much automation and summarization as you like. This does not require a special syntax - you simply work in Python, with access to all its features - and you can piggyback your automation on top of existing syntax. Some examples are provided under beancount.plugins.* . There is an argument to be made, however, for the availability of a quick and easy method for specifying the most common case of just adding some postings. I\u2019m not entirely convinced yet, but Beancount may acquire this eventually (you could easily prototype this now in a plugin file if you wanted).","title":"Automated Transactions via Plugins"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#no-support-for-time-or-effective-dates","text":"Beancount does not represent the intra-day time of transactions, its granularity is a day. Ledger allows you to specify the time of transactions down to seconds precision. I choose to limit its scope in the interest of simplicity, and I also think there are few use cases for supporting intra-day operations. Note that while Beancount\u2019s maximum resolution is one day, when it sorts the directives it will maintain the relative order of all transactions that occurs within one day, so it is possible to represent multiple transactions occurring on the same day in Beancount and still do correct inventory bookings. But I believe that if you were to do day-trading you would need a more specialized system to compute intra-day returns and do technical analysis and intraday P/L calculations. Beancount is not suited for those (a lot of other features would be needed if that was its scope). Ledger also has support for effective dates which are essentially an alternative date for the transaction. Reporting features allow Ledger users to use the main date or the alternative date. I used to have this feature in Beancount and I removed it, mainly because I did not want to introduce reporting options, and to handle two dates, say a transaction date and a settlement date, I wanted to enforce that at any point in time all transactions would balance. I also never made much use of it which indicates it was probably futile. Handling postings to occur at different points in time would have created imbalances, or I would have had to come up with a solution that involved \u201climbo\u201d or \u201ctransfer\u201d accounts. I preferred to just remove the feature: in 8 years of data, I have always been able to fudge the dates to make everything balance. This is not a big issue. Note that handling those split transaction and merging them together will be handled; a proposal is underway.","title":"No Support for Time or Effective Dates"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#documents","text":"Beancount offers support for integrating a directory hierarchy of documents with the contents of a ledger\u2019s chart of accounts. You can provide a directory path and Beancount will automatically find and create corresponding Document directives for filenames that begin with a date in directories that mirror account names, and attach those documents to those accounts. And given a bit of configuration, the bean-file tool can automatically file downloaded documents to such a directory hierarchy. Ledger\u2019s binding of documents to transactions works by generic meta-data. Users can attach arbitrary key-value pairs to their transactions, and those can be filenames. Beyond that, there is no particular support for document organization.","title":"Documents"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#simpler-and-more-strict","text":"Finally, Beancount has a generally simpler input syntax than Ledger. There are very few command-line options\u2014and this is on purpose, I want to localize all the input within the file\u2014and the directive syntax is more homogeneous: all transactions begin with a date and a keyword. If the argument of simplicity appeals to you, you might prefer to work with Beancount. I feel that the number of options offered in Ledger is daunting, and I could not claim to understand all of the possible ways they might interact with each other. If this does not worry you, you might prefer to use Ledger. It is also more strict than Ledger. Certain kinds of Beancount inputs are not valid. Any transaction in an account needs to have an open directive to initiate the account, for example (though some of these constraints can be relaxed via optional plugins). If you maintain a Beancount ledger, you can expect to have to normalize it to fix a number of common errors being reported. I view this as a good thing: it detects many potential problems and applies a number of strict constraints to its input which allows us to make reasonable assumptions later on when we process the stream of directives. If you don\u2019t care about precision or detecting potential mistakes, Ledger will allow you to be more liberal. On the other hand if you care to produce a precise and flawless account of transactions, Beancount offers more support in its validation of your inputs.","title":"Simpler and More Strict"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#web-interface","text":"Beancount has a built-in web interface, and there is an external project called Fava which significantly improves on the same theme. This is the default mode for browsing reports. I believe HLedger also has a web interface as well.","title":"Web Interface"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#missing-features","text":"Beancount generally attempts to minimize the number of features it provides. This is in contrast with Ledger\u2019s implementation, which has received a substantial amount of feature addition in order to experiment with double-entry bookkeeping. There are a large number of options. This reflects a difference in approach: I believe that there is a small core of essential features to be identified and that forward progress is made when we are able to minimize and remove any feature that is not strictly necessary. My goal is to provide the smallest possible kernel of features that will allow one to carry out the full spectrum of bookkeeping activities, and to make it possible for users to extend the system to automate repetitive syntax. But here is a list of features that Beancount does not support that are supported in Ledger, and that I think would be nice to have eventually. The list below is unlikely to be exhaustive.","title":"Missing Features"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#console-output","text":"Beancount\u2019s original implementation focused on providing a web view for all of its contents. During the 2.0 rewrite I began implementing some console/text outputs, mainly because I want to be able for reports to be exportable to share with others. I have a trial balance view (like Ledger\u2019s \u201cbal\u201d report) but for now the journal view isn\u2019t implemented. Ledger, on the other hand, has always focused on console reports. I\u2019ll make all the reports in Beancount support output to text format first thing after the initial release, as I\u2019m increasingly enjoying text reports. Use bean-query --list-formats to view current status of this.","title":"Console Output"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#filtering-language","text":"Beancount does not yet have a filtering language. Until recently, its web interface was the main mode for rendering reports and exploring the contents of its ledger, and it provided limited subsets of transactions in the form of \u201cviews\u201d, e.g., per-year, per-tag, etc. Having a filtering language in particular allows one to do away with many sub-accounts. I want to simplify my chart of accounts so I need this. I\u2019m working on adding a simple logical expression language to do arbitrary filters on the set of Beancount transactions. This is straightforward to implement and a high priority.","title":"Filtering Language"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#no-meta-data","text":"Beancount does not currently support meta-data. Ledger users routinely make liberal use of metadata. This has been identified as a powerful feature and a prototype has already been implemented. Meta-data will be supported on any directive type as well as on any posting. A dictionary of key-value pairs will be attached to each of these objects. Supported values will include strings, dates, numbers, currencies and amounts. So far the plan is to restrict Beancount\u2019s own code to make no specific use of meta-data, on purpose. The meta-data will be strictly made available for user-plugins and custom user scripts to make use of.","title":"No Meta-data"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#no-arithmetic-expressions","text":"Beancount does not support arbitrary expression evaluation in the syntax, in the places where numbers are allowed in the input. Ledger does. I have had no use for these yet, but I have no particular reason against adding this, I just haven\u2019t implemented it, as I don\u2019t need it myself. I think an implementation would be straightforward and very low risk, a simple change to the parser and there is already a callback for numbers. I think there are many legitimate uses for it.","title":"No Arithmetic Expressions"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#limited-support-for-unicode","text":"Beancount supports UTF8 or and other encodings in strings only (that is, for input that is in quotes). For example, you can enter payees and narrations with non-ASCII characters, but not account names (which aren\u2019t in quotes). Ledger supports other encodings over the entire file. The reason for the lack of a more general encoding support in Beancount is the current limitation of tokenizer tools. I\u2019ve been using GNU flex to implement my lexer and it does not support arbitrary encodings. I just need to write a better lexer and make that work with Bison , it\u2019s not a difficult task. I will eventually write my own lexer manually\u2014this has other advantages\u2014and will write it to support Unicode (Python 3 has full support for this, so all that is required is to modify the lexer, which is one simple compilation unit). This is a relatively easy and isolated task to implement.","title":"Limited Support for Unicode"},{"location":"a_comparison_of_beancount_and_ledger_hledger.html#no-forecasting-or-periodic-transactions","text":"Beancount has no support for generating periodic transactions for forecasting, though there is a plugin provided that implements a simplistic form of it to be used as an example for how plugins work (see beancount.plugins.forecast ). Ledger supports periodic transaction generation . I do want to add this to core Beancount eventually, but I want to define the semantics very clearly before I do. Updating one\u2019s ledger is essentially the process of copying and replicating transactional data that happens somewhere else. I don\u2019t believe that regular transactions are that \u201cregular\u201d in reality; in my experience, there is always some amount of small variations in real transactions that makes it impossible to automatically generate a series of transactions by a generator in a way that would allow the user to forgo updating them one-by-one. What it is useful for in my view, is for generating tentative future transactions. I feel strongly that those transactions should be limited not to straddle reconciled history, and that reconciled history should replace any kind of automatically generated transactions. I have some fairly complete ideas for implementing this feature, but I\u2019m not using forecasting myself at the moment, so it\u2019s on the backburner. While in theory you can forecast using Ledger\u2019s periodic transactions, to precisely represent your account history you will likely need to adjust the beginning dates of those transactions every time you append new transactions to your accounts and replace forecasted ones. I don\u2019t find the current semantics of automated transactions in Ledger to be very useful beyond creating an approximation of account contents. (In the meantime, given the ease of extending Beancount with plugins, I suggest you begin experimenting with forecast transactions on your own for now, and if we can derive a generic way to create them, I\u2019d be open to merging that into the main code.)","title":"No Forecasting or Periodic Transactions"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html","text":"Inventory Booking \uf0c1 A Proposal for an Improvement on Command-Line Bookkeeping Martin Blais, June 2014 http://furius.ca/beancount/doc/proposal-booking Motivation Problem Description Lot Date Average Cost Basis Capital Gains Sans Commissions Cost Basis Adjustments Dealing with Stock Splits Previous Solutions Shortcomings in Ledger Shortcomings in Beancount Requirements Debugging Tools Explicit vs. Implicit Booking Reduction Design Proposal Inventory Input Syntax & Filtering Algorithm Implicit Booking Methods Dates Inserted by Default Matching with No Information Reducing Multiple Lots Examples No Conflict Explicit Selection By Cost Explicit Selection By Date Explicit Selection By Label Explicit Selection By Combination Not Enough Units Redundant Selection of Same Lot Automatic Price Extrapolation Average Cost Booking Future Work Implementation Note Conclusion Motivation \uf0c1 The problem of \u201cinventory booking,\u201d that is, selecting which of an inventory\u2019s trade lots to reduce when closing a position, is a tricky one. So far, in the command-line accounting community, relatively little progress has been made in supporting the flexibility to deal with many common real-life cases. This document offers a discussion of the current state of affairs, describes the common use cases we would like to be able to solve, identifies a set of requirements for a better booking method, and proposes a syntax and implementation design to support these requirements, along with clearly defined semantics for the booking method. Problem Description \uf0c1 The problem under consideration is the problem of deciding, for a double-entry transaction that intends to reduce the size of a position at a particular point in time in a particular account, which of the account inventory lots contained at that point should be reduced. This should be specified using a simple data entry syntax that minimizes the burden on the user. For example, one could enter a position in HOOL stock by buying two lots of it at different points in time: 2014-02-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade\" Assets:Investments:Stock 8 HOOL {510 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD We will call the two sets of shares \u201ctrade lots\u201d and assume that the underlying system that counts them is keeping track of each of their cost and date of acquisition separately. The question here is, if we were to sell some of this stock, which of the shares should we select? Those from the first trade, or those from the second? This is an important decision, because it has an impact on capital gains, and thus on taxes. (The account of capital gains is described in more detail in the \u201cStock Trading\u201d section of the Beancount cookbook if you need an explanation of this.) Depending on our financial situation, this year\u2019s trading history, and the unrealized gains that a trade would realize, sometimes we may want to select one lot over another. By now, most discount brokers even let you select your specific lot when you place a trade that reduces or closes a position. It is important to emphasize here the two directions of inventory booking: Augmenting a position. This is the process of creating a new trading lot, or adding to an existing trading lot (if the cost and other attributes are identical). This is easy, and amounts to adding a record to some sort of mapping that describes an account\u2019s balance (I call this an \"inventory\"). Reducing a position. This is generally where booking complications take place, and the problem is essentially to figure out which lot of an existing position to remove units from. Most of this document is dedicated to the second direction. Lot Date \uf0c1 Even if the cost of each lot is identical, the acquisition date of the position you\u2019re intending to close matters, because different taxation rules may apply, for example, in the US, positions held for more than 12 months are subject to a significantly lower tax rate (\u201cthe long-term capital gains rate\u201d). For example, if you entered your position like this: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade at same price\" Assets:Investments:Stock 8 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD and we are booking a trade for 2014-03-01, selecting the first lot would result in a long-term (low) gain, because two years have passed since acquisition (position held from 2012-05-01 to 2014-03-01) while booking against the second would result in a short-term (high) capital gains tax. So it\u2019s not just a matter of the cost of the lots. Our systems don't aim to file taxes automatically at this point but we would like to enter our data in a way that eventually allows this kind of reporting to take place. Note that this discussion assumes that you were able to decide which of the lots you could trade against. There are several taxation rules that may apply, and in some cases, depending on which country you live in, you may not have a choice, the government may dictate how you are meant to report your gains. In some cases you may even need to be able to apply multiple booking methods to the same account (e.g., if an account is subject to a taxation claim from multiple countries). We will ignore this exotic case in this document, as it is quite rare. Average Cost Basis \uf0c1 Things get more complicated for tax-sheltered accounts. Because there are no tax consequences to closing positions in these accounts, brokers will normally choose to account for the book value of your positions as if they were a single lot. That is, the cost of each share is computed using the weighted average of the cost of the position you are holding. This can equivalently be calculated by summing the total cost of each position and then dividing by the total number of shares. To continue with our first example: 10 HOOL x 500 USD/HOOL = 5000 USD 8 HOOL x 510 USD/HOOL = 4080 USD Cost basis = 5000 USD + 4080 USD = 9080 USD Total number of shares = 10 HOOL + 8 HOOL = 18 HOOL Cost basis per share = 9080 USD / 18 HOOL = 504.44 USD/HOOL So if you were to close some of your position and sell some shares, you would do so at a cost of 504.44 USD/HOOL. The gain you would realize would be relative to this cost; for example if you sold 5 shares at 520 USD/HOOL, your gain would be calculated like this: (520 USD/HOOL - 504.44 USD/HOOL) x 5 HOOL = 77.78 USD This type of booking method is made evident by two kinds of financial events that you might witness occur in these types of accounts: Fees may be withdrawn by selling some shares reported at their current market price. You will see these if you hold a retirement account at Vanguard in the USA. The broker takes a fixed amount of dollars per quarter (5$, with my employer's plan) that is backed out in terms of a small, fractional number of shares of each fund to sell. That transaction is oblivious to capital gains: they simply figure out how many shares to sell to cover their fee and don\u2019t report a gain to you. You have to assume it doesn\u2019t matter. Most people don't track their gains in non-taxable accounts but freaks like us may want to compute the return nonetheless. Cost basis readjustments may occur spontaneously. This is rare, but I have seen this once or twice in the case of mutual funds in a Canadian tax-deferred account: based on some undisclosed details of their trading activity, the fund management has had to issue an adjustment to the cost basis, which you are meant to apply to your positions. You get an email telling you how much the adjustment was for. 99% of people probably don\u2019t blink, don't understand and ignore this message\u2026 perhaps rightly so, as it has no impact on their taxes, but if you want to account for your trading returns in that account, you do need to count it. So we need to be able to add or remove to the cost basis of existing positions. Note that the average cost of your positions changes on every trade and needs to get recalculated every time that you add to an existing position. A problem with this is that if you track the cost per share, these multiple recalculations may result in some errors if you store the amounts using a fixed numerical representation (typically decimal numbers). An alternative method to track the cost in our data structures, one that is more stable numerically, is to simply keep track of the total cost instead of the cost-per-share. Also note that using fractional numbers does not provide a sufficient answer to this problem: in practice, the broker may report a specific cost, one that is rounded to specific number of decimals, and we may want to be able to book our reducing trade using that reported number rather than maintain the idealized amount that a fractional representation would. Both using a fixed decimal or a fractional representation pose problems. We will largely ignore those problems here. In summary: we need to be able to support book against lots at their average cost, and we need to be able to adjust the cost basis of an existing position. I\u2019ve been thinking about a solution to implement this that would not force an account to be marked with a special state. I think we can do this using only inventory manipulations. All we need is a small change to the syntax that allows the user to indicate to the system that it should book against the average cost of all positions. Using the first example above, acquiring the shares would take the same form as previously, that is after buying two lots of 10 and 8 HOOL units, we end up with an inventory that holds two lots, one of 10 HOOL {500 USD} and one of 8 HOOL {510 USD}. However, when it\u2019s time to sell, the following syntax would be used (this is an old idea I\u2019ve been meaning to implement for a while): 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 HOOL {*} Assets:Investments:Cash 2759.95 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains When the \u201c * \u201d is encountered in lieu of the cost, like this, it would: Merge all the lots together and recalculate the average price per share (504.44 USD) Book against this merged inventory, reducing the resulting lot. After the transaction, we would end up with a single position of 13 HOOL {504.44 USD). Adding to this position at a different price would create a new lot, and those would get merged again the next time a reduction occurs at average cost. We do not need to merge lots until there is a reduction. Apart from concerns of accumulating rounding error, this solution is correct mathematically, and maintains the property that accounts aren\u2019t coerced into any specific booking method\u2014a mix of methods could be used on every reduction. This is a nice property, even if not strictly necessary, and even if we want to be able to just specify a \u201cdefault\u201d booking method to use per account and just stick with it throughout. It\u2019s always nice to have the flexibility to support exceptions, because in real life, they sometimes occur. Capital Gains Sans Commissions \uf0c1 We would like to be able to support the automatic calculation of capital gains without the commissions. This problem is described in much detail in the Commissions section of the trading documentation and in a thread on the mailing-list . The essence of the complication that occurs is that one cannot simply subtract the commissions incurred during the reporting period from the gains that include commissions, because the commissions that were incurred to acquire the position must be pro-rated to the shares of the position that are sold. The simplest and most common way to implement this is to include the costs of acquiring the position into the cost basis of the position itself, and deduct the selling costs from the market value when a position is reduced. Whatever new design we come up with must allow us to count these adjusted gains as this is essential to various individuals' situations. In Beancount, I have figured out a solution for this problem, which luckily enough involves only an automated transformation of a transaction flagged by the presence of a special flag on its postings... if and only if I can find a way to specify which lot to book against without having to specify its cost, because once the cost of commissions gets folded into the cost of the position, the adjusted cost does not show up anywhere in the input file, the user would have to calculate it manually, which is unreasonable. In the solution I\u2019m proposing, the following transactions would be transformed automatically, triggered by the presence of the \u201cC\u201d flag: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD C Expenses:Commissions 9.95 USD Assets:US:Invest:HOOL 10.00 HOOL {500 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains 2014-05-10 * \"Sell #2\" Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains They would be automatically transformed by a plugin into the following and replace the original transactions above: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD X Assets:US:Invest:HOOL 10.00 HOOL {500.995 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" X Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains ; Should be (530-500)*4 - 9.95*(4/10) - 9.95 = ; 106.07 USD 2014-05-10 * \"Sell #2\" X Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains ; Should be (540-500)*6 - 9.95*(6/10) - 9.95 = ; 224.08 USD The \u201cX\u201d flag would just mark the postings that have been transformed, so maybe they can be rendered with a special color or attribute later on. The rebates account is included as a separate counter just to make the transaction balance. The reason I need to relax the disambiguation of trading lots is that the user needs to be able to specify the matching leg without having to specify the cost of the position, because at the point where the position is reduced (2014-04-10), there is no way to figure out what the cost of the original lot was. Just to be clear, in the example above, this means that if all the information have is 4 HOOL and 500 USD, there is no way to back out a cost of 500.995 USD that could specify a match with the opening trade lot, because that happened with 10 HOOLs. So we need to be more explicit about booking. In the example above, I\u2019m selecting the matching lot \u201cby label,\u201d that is, the user has the option to provide a unique lot identifier in the cost specification, and that can later on be used to disambiguate which lot we want to book a reduction/sale against. The example above uses the string \u201c aa2ba9695cc7 \u201d in this way. (An alternative solution to this problem would involve keep track of both the original cost (without commissions), and the actual cost (with commissions), and then finding the lot against the former, but using the latter to balance the transaction. This idea is to allow the user to keep using the cost of a position to select the lot, but I\u2019m not even sure if that is possible, in the presence of various changes to the inventory. More thought is required on this matter.) Cost Basis Adjustments \uf0c1 In order to adjust the cost basis, one can replace the contents of an account explicitly like this: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {510 USD} Income:US:Invest:Gains This method works well and lets the system automatically compute the gains. But it would be nice to support making adjustments in price units against the total cost of the position, for example, \u201cadd 340.51 USD to the cost basis of this position\u201d. The problem is that the adjusted cost per share now needs to be computed by the user\u2026 it\u2019s inconvenient. Here\u2019s one way we could support this well, however: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD If all the legs are fully specified - they have a calculatable balance - we allow a single price to be elided. This solves this problem well. Dealing with Stock Splits \uf0c1 Accounting for stock splits creates some complications in terms of booking. In general, the problem is that we need to deal with changes in the meaning of a commodity over time. For example, you could do this in Beancount right now and it works: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD} Assets:Investments:Stock 10 HOOL {500.00 USD} Assets:Investments:Stock 10 HOOLL {500.00 USD} One problem with splitting lots this way is that the price and meaning of a HOOL unit before and after the split differs, but let\u2019s just assume we\u2019re okay with that for now (this can be solved by relabeling the commodity by using another name, ideally in a way that is not visible to the user - we have a pending discussion elsewhere ). The issue that concerns booking is, when you sell that position, which cost do you use? The inventory will contain positions at 500 USD, so that\u2019s what you should be using, but is it obvious to the user? We can assume this can be learned with a recipe . Now, what if we automatically attach the transaction date? Does it get reset on the split and now you would have to use 2014-04-17? If so, we could not automatically inspect the list of trades to determine whether this is a long-term vs. short-term trade. We need to somehow preserve some of the attributes of the original event that augmented this position, which includes the original trade date (not the split date) and the original user-specified label on the position, if one was provided. Forcing a Single Method per Account \uf0c1 For accounts that will use the average booking method, it may be useful to allow specifying that an account should only use this booking method . The idea is to avoid data entry errors when we know that an account is only able to use this method. One way to ensure this is to automatically aggregate inventory lots when augmenting a position. This ensures that at any point in time, there is only a single lot for each commodity. If we associated an inventory booking type to each account, we could define a special one that also triggers aggregation upon the addition of a position. Another approach would be to not enforce these aggregations, but to provide a plugin that would check that for those accounts that are marked as using the average cost inventory booking method by default, that only such bookings actually take place. Previous Solutions \uf0c1 This section reviews existing implementations of booking methods in command-line bookkeeping systems and highlights specific shortcomings that motivate why we need to define a new method. Shortcomings in Ledger \uf0c1 (Please note: I\u2019m not deeply familiar with the finer details of the inner workings of Ledger; I inferred its behavior from building test examples and reading the docs. If I got any of this wrong, please do let me know by leaving a comment.) Ledger\u2019s approach to booking is quite liberal. Internally, Ledger does not distinguish between conversions held at cost and regular price conversions : all conversions are assumed held at cost, and the only place trading lots appear is at reporting time (using its --lots option). Its \u201c {} \u201d cost syntax is meant to be used to disambiguate lots on a position reduction , not to be used when acquiring a position. The cost of acquiring a position is specified using the \u201c @ \u201d price notation: 2014/05/01 * Buy some stock (Ledger syntax) Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash This will result in an inventory of 10 HOOL {500 USD}, that is, the cost is always stored in the inventory that holds the position: $ ledger -f l1.lgr bal --lots 10 HOOL {USD500} [2014/05/01] USD-5000 Assets:Investments USD-5000 Cash 10 HOOL {USD500} [2014/05/01] Stock -------------------- 10 HOOL {USD500} [2014/05/01] USD-5000 There is no distinction between conversions at cost and without cost\u2014all conversions are tracked as if \u201cat cost.\u201d As we will see in the next section, this behaviour is distinct from Beancount\u2019s semantics, which requires a conversion held at cost to be specified using the \u201c {} \u201d notation for both its augmenting and reducing postings, and distinguishes between positions held at cost and price conversions. The advantage of the Ledger method is a simpler input mechanism, but it leads to confusing outcomes if you accumulate many conversions of many types of currencies in the same account. An inventory can easily become fragmented in its lot composition. Consider what happens, for instance, if you convert in various directions between 5 different currencies\u2026 you may easily end up with USD held in CAD, JPY, EUR and GBP and vice-versa\u2026 any possible combinations of those are possible. For instance, the following currency conversions will maintain their original cost against their converted currencies: 2014/05/01 Transfer from Canada Assets:CA:Checking -500 CAD Assets:US:Checking 400 USD @ 1.25 CAD 2014/05/15 Transfers from Europe Assets:UK:Checking -200 GBP Assets:US:Checking 500 USD @ 0.4 GBP 2014/05/21 Transfers from Europe Assets:DE:Checking -100 EUR Assets:US:Checking 133.33 USD @ 0.75 EUR This results in the following inventory in the Assets:US:Checking account: $ ledger -f l2.lgr bal --lots US:Checking 500.00 USD {0.4 GBP} [2014/05/15] 133.33 USD {0.75 EUR} [2014/05/21] 400.00 USD {1.25 CAD} [2014/05/01] Assets:US:Checking The currencies are treated like stock. But nobody thinks of their currency balances like this , one normally thinks of currencies held in an account as units in and of themselves, not in terms of their price related to some other currency. In my view, this is confusing. After these conversions, I just think of the balance of that account as 1033.33 USD. A possible rebuttal to this observation is that most of the time a user does not care about inventory lots for accounts with just currencies, so they just happen not print them out. Out of sight, out of mind. But there is no way to distinguish when the rendering routine should render lots or not. If instructed to produce a report, a balance routine should ideally only render lots only for some accounts (e.g., investing accounts, where the cost of units matters) and not for others (e.g. accounts with currencies that were deposited sometimes as a result of conversions). Moreover, when I render a balance sheet, for units held at cost we need to report the book values rather than the number of shares. But for currencies, the currency units themselves need to get reported, always. If you don\u2019t distinguish between the two cases, how do you know which to render? I think the answer is to select which view you want to render using the options. The problem is that neither provides a single unified view that does the correct thing for both types of commodities. The user should not have to specify options to view partially incorrect views in one style or the other, this should be automatic and depend on whether we care about the cost of those units. This gets handled correctly if you distinguish between the two kinds of conversions. But perhaps most importantly, this method does not particularly address the conversion problem : it is still possible to create money out of thin air by making conversions back and forth at different rates: 2014/05/01 Transfer to Canada Assets:US:Checking -40000 USD Assets:CA:Checking 50000 CAD @ 0.80 USD 2014/05/15 Transfer from Canada Assets:CA:Checking -50000 CAD @ 0.85 USD Assets:US:Checking 42500 USD This results in a trial balance with just 2500 USD: $ ledger -f l3.lgr bal 2500 USD Assets:US:Checking This is not a desirable outcome. We should require that the trial balance always sum to zero (except for virtual transactions). After all, this is the aggregate realization of the elegance of the double-entry method: because all transactions sum to zero, the total sum of any subgroup of transactions also sums to zero\u2026 except it doesn\u2019t. In my view, any balance sheet that gets rendered should comply with the accounting equation, precisely. In my explorations with Ledger, I was hoping that by virtue of its inventory tracking method it would somehow naturally deal with these conversions automatically and thus provide a good justification for keeping track of all units at cost, but I witness the same problem showing up. (The conversions issue has been a real bitch of a problem to figure out a solution for in Beancount, but it is working, and I think that the same solution could be applied to Ledger to adjust these sums, without changing its current booking method: automatically insert a special conversion entry at strategic points - when a balance sheet is rendered.) In the end, I have not found that tracking costs for every transaction would appear to have an advantage over forgetting the costs for price conversions, the conversions problem is independent of this. I\u2019d love to hear more support in favour of this design choice. Now, because all conversions maintain their cost, Ledger needs to be quite liberal about booking, because it would be inconvenient to force the user to specify the original rate of conversion for each currency, in the case of commonly occurring currency conversions. This has an unfortunate impact on those conversions which we do want to hold at cost: arbitrary lot reductions are allowed, that is, reductions against lots that do not exist. For example, the following trade does not trigger an error in Ledger: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of an impossible lot Assets:Investments:Stock -10 HOOL {505 USD} @ 510 USD Assets:Investments:Cash This results in an inventory with a long position of 10 HOOL and a short position of 10 HOOL (at a different cost): $ ledger -f l4.lgr bal --lots stock 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD505} Assets:Investments:Stock I think that the reduction of a 505 USD position of HOOL should not have been allowed; Beancount treats this as an error by choice. Ledger clearly appears to support booking against an existing inventory, so surely the intention was to be able to reduce against existing positions. The following transactions do result in an empty inventory, for instance: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot Assets:Investments:Stock -10 HOOL {500 USD} [2014/05/01] @ 510 USD Assets:Investments:Cash With output: $ ledger -f l5.lgr bal --lots USD100 Equity:Capital Gains As you see, the HOOL position has been eliminated. (Don\u2019t mind the auto-inserted equity entry in the output, I believe this will be fixed in a pending patch .) However, both the date and the cost must be specified for this to work. The following transactions results in a similar problematic long + short position inventory as mentioned above: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot? Assets:Investments:Stock -10 HOOL {500 USD} @ 510 USD Assets:Investments:Cash With output: $ ledger -f l6.lgr bal --lots 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} Assets:Investments:Stock USD100 Equity:Capital Gains -------------------- 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} USD100 This is a bit surprising, I expected the lots to book against each other. I suspect this may be an unreported bug, and not intended behaviour. Finally, the \u201c {} \u201d cost syntax is allowed be used on augmenting legs as well. The documentation points to these methods being equivalent . It results in an inventory lot that does not have a date associated with it, but the other leg is not converted to cost: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash With output: $ ledger -f l7.lgr bal --lots 0 Assets:Investments -10 HOOL {USD500} Cash 10 HOOL {USD500} Stock -------------------- 0 I probably just don\u2019t understand how this is meant to be used; in Beancount the automatically computed leg would have posted a -5000 USD amount to the Cash account, not shares. What I think is going on, is that Ledger (probably) accumulates all the lots without attempting to match them together to try to make reductions, and then at reporting time - and only then - it matches the lots together based on some reporting options: Group lots by both cost/price and date, using --lots Group lots by cost/price only, using --lot-prices Group lots by date only, using --lot-dates It is not entirely obvious from the documentation how that works, but the behavior is consistent with this. (If this is correct, I believe that to be a problem with its design: the mechanism by which an inventory reduction is booked to an existing set of lots should definitely not be a reporting feature. It needs to occur before processing reports, so that a single and definitive history of inventory bookings can be realized. If variants in bookings are supported - and I don\u2019t think this is a good idea - they should be supported before the reporting phase. In my view, altering inventory booking strategies from command-line options is a bad idea, the strategies in place should be fully defined by the language itself.) Shortcomings in Beancount \uf0c1 Beancount also has various shortcomings, though different ones. In contrast to Ledger, Beancount disambiguates between currency conversions and conversions \u201cheld at cost\u201d: 2014-05-01 * \"Convert some Loonies to Franklins\" Assets:CA:Checking -6000 CAD Assets:Investments:Cash 5000 USD @ 1.2 CAD ; Currency conversion 2014-05-01 * \"Buy some stock\" Assets:Investments:Stock 10 HOOL {500 USD} ; Held at cost Assets:Investments:Cash In the first transaction, the Assets:Investment:Cash account results in an inventory of 5000 USD, with no cost associated to it. However, after the second transaction the Assets:Investment:Stock account has an inventory of 10 HOOL with a cost of 500 USD associated to it. This is perhaps a little bit more complex, and requires more knowledge from the user: there are two kinds of conversions and he has to understand and distinguish between these two cases, and this is not obvious for newcomers to the system. Some user education is required (I\u2019ll admit it would help if I wrote more documentation). Beancount\u2019s method is also less liberal, it requires a strict application of lot reduction. That is, if you enter a transaction that reduces a lot that does not exist, it will output an error. The motivation for this is to make it difficult for the user to make a mistake in data entry. Any reduction of a position in an inventory has to match against exactly one lot. There are a few downsides that result from this choice: It requires the users to always find the cost of the lot to match against. This means that automating the import of transactions requires manual intervention from the user, as he has to go search in the ledger to insert the matching lot. (Note that theoretically the import code could load up the ledger contents to list its holdings, and if unambiguous, could look for the cost itself and insert it in the output. But this makes the import code dependent on the user\u2019s ledger, which most import codes aren't doing). This is an important step, as finding the correct cost is required in order to correctly compute capital gains, but a more flexible method is desired, one that allows the user to be a bit more lazy and not have to put the cost of the existing position when the choice is obvious, e.g., when there is a single lot of that unit to match against. I\u2019d like to relax this method somewhat. The strict requirement to reduce against an existing lot also means that both long and short positions of the same commodities \u201cheld at cost\u201d in the same account cannot exist. This is not much of a problem in practice, however, because short positions are quite rare\u2014few individuals engage in them\u2014and the user could always create separate accounts to hold short positions if needed. In fact, it is already customary to create a dedicated subaccount for each commodity in an investment account, as it naturally organizes trading activity nicely (reports a bit of a mess otherwise, it\u2019s nice to see the total position value grouped by stock, since they offer exposure to different market characteristics). Different accounts should work well, as long as we treat the signs correctly in reductions, i.e., buying stock against an existing short position is seen as a position reduction (the signs are just reversed). Another problem with the Beancount booking method is in how it matches against lots with dates. For example, if you were to try to disambiguate between two lots at the same price, the most obvious input would not work: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash This would report a booking error. This is confusing. In this case Beancount, in its desire to be strict, applies strict matching against the inventory lots, and attempting to match units of a lot of (HOOL, 500 USD, 2012-05-01) with units of (HOOL, 500 USD, None ) simply fails. Note that the lot-date syntax is accepted by Beancount on both augmenting and reducing legs, so the \u201csolution\u201d is that the user is forced to specifically provide the lot-date on acquiring the position. This would work, for instance: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash The inconvenience here is that when the user entered the first trade, he could not predict that he would enter another trade at the same price in the future and typically would not have inserted the lot-date. More likely than not, the user had to go back and revisit that transaction in order to disambiguate this. We can do better. This is a shortcoming of its implementation, which reflects the fact that position reduction was initially regarded as an exact matching problem rather than a fuzzy matching one\u2014it wasn\u2019t clear to me how to handle this safely at the time. This needs to be fixed after this proposal, and what I\u2019m doing with this document is very much a process of clarifying for myself what I want this fuzzy matching to mean, and to ensure that I come up with an unambiguous design that is easy to enter data for. Ledger always automatically attaches the transaction date to the inventory lot, and in my view this is the correct thing to do (below we propose attaching more information as well). Context \uf0c1 [Updated on Nov\u20192014] It is important to distinguish between two types of booking: Booking. The strict type of booking that occurs when we are considering the entire list of transactions. We will call this the \u201cbooking\u201d process, and it should occur just once. This process should be strict: the failure of correctly book a reduction to an existing lot should trigger a fatal error. Inventory Aggregation. When we are considering a subset of entries, it is possible that entries that add some lots are removed, and that other entries that reduce or remove those lots are kept. This creates a situation in which it is necessary to support aggregations over time of changes to an inventory that may not admit a matching lot. If we are to support arbitrary subsets of transactions being aggregated, we must take this into account. This aggregation process should never lead to an error. Let\u2019s take an example to justify why we need the non-booking summarization. Let\u2019s say we have a stock purchase and sale: 2013-11-03 * \"Buy\" Assets:Investments:VEA 10 AAPL {37.45 USD} ;; (A) Assets:Investments:Cash -370.45 USD 2014-08-09 * \"Sell\" Assets:Investments:VEA -5 AAPL {37.45 USD} Assets:Investments:Cash 194.40 USD Income:Investments:PnL If we filter \u201cby year\u201d and only want to view transactions that occurred in 2014, AND we don\u2019t \u201cclose\u201d the previous year, that is, we do not create opening balances entries that would deposit the lots in the account at the beginning of the year - this could happen if we filter by tag, or by some other combination of conditions - then the (A) lot is not present in the inventory to be debited from. We must somehow accommodate a -5 AAPL at that point. When reporting, the user could decide how to summarize and \u201cmatch up as best it can\u201d those legs, but converting them to units or cost, or convert them to a single lot at average-cost. The booking process, however, should admit no such laxity. It should be strict and trigger an error if a lot cannot be matched. This would only be applied on the total set of transactions, and only once, never on any subset. The purpose of the booking stage should be threefold: Match against existing lots and issue errors when that is impossible. Do this, using the method specified by the user. Replace all partially specified lots by a full lot specification, the lot that was matched during the booking process. Insert links on transactions that form a trade: a buy and corresponding sells should be linked together. This essentially identifies trades, and can then be used for reporting later on. (Trades at average cost will require special consideration.) We can view Ledger\u2019s method as having an aggregation method only, lacking a booking stage. The particular method for aggregation is chosen using --lots or --lot-dates or --lot-prices. This would indicate that a \u201cbooking\u201d stage could be tacked on to Ledger without changing much of its general structure: it could be inserted as a pre- or post- process, but it would require Ledger to sort the transactions by date, something it does not currently do (it runs a single pass over its data). Beancount has had both methods for a while, but the separate nature of these two operations has not been explicitly stated so far\u2014instead, the inventory supported an optional flag to raise an error when booking was impossible. Also, inventory aggregation has not been thought as requiring much customization, it was meant to be unified to the booking process. It is now clear that they are two distinct algorithms, and that a few different methods for aggregating can be relevant (though they do not function the same as Ledger\u2019s do. Note: you can view the inventory aggregation method as a GROUP BY within an Inventory object\u2019s lots: which columns do you group over? This requires use cases). In any case, fully specified lots should match against each other by default: we need to support this as the degenerate case to use for simple currencies (not held at cost)... we would not want to accumulate all changes in the same currency as separate lots in an inventory, they need to cross each other as soon as they are applied. We want to change this in order to make the code clearer: there should be a separate \u201cbooking\u201d stage, provided by a plugin , which resolves the partially specific lot reduction using the algorithm outlined in this document, and a separate method for inventory aggregation should not bother with any of those details. In fact, the inventory aggregation could potentially simply become an accumulated list of lots, and the summarization of them could take place a posteriori, with some conceptual similarly to when Ledger applies its aggregation. Just using a simple aggregation becomes more relevant once we begin entering more specific data about lots, such as always having an acquisition date, a label, and possibly even a link to the entry that created the lot. In order to trigger summarization sensibly, functions to convert and summarize accumulated inventories could be provided in the shell, such as \u201cUNITS(inventory)\u201d. Precision for Interpolation \uf0c1 The interpolation capabilities will be extended to cover eliding a single number, any number, in any subset of postings whose \u201cweights\u201d resolve to a particular currency (e.g., \u201call postings with weights in USD\u201d). The interpolation needs to occur at a particular precision. The interpolated number should be quantized automatically, and the number of fractional digits to quantize it to should be automatically inferred from the DisplayContext which is itself derived from the input file. Either the most common or the maximum number of digits witnessed in the file should be used. For a use case, see this thread : On Mon, Nov 24, 2014 at 12:33 PM, ELI wrote: Regarding the last piece on which there seems to be a misunderstanding between you and I, let me provide an standalone example, outside the context of the plugin. Since Vanguard only makes three decimal places of precision available me, I need to have the actual share count calculated from the price and transaction amount. For example, I have a transaction on my activities page with these details: Shares Transacted: 0.000 Share Price: 48.62 Amount: 0.02 The only way to enter this transaction and have it balance would be to manually calculate the Shares Transacted with four decimal places of precision. My preferred way of entering this would be to enter the Share Price and Amount and have Beancount calculate Shares Transacted to a precision associated with my Vanguard account. Additionally, as metadata, I'd record \"Shares Transacted: 0.000\" as history of \"what the statement said\". Maybe you can give me your thoughts on how such a case would be addressed with planned Beancount features or as a plugin I could right? What does the downloadable file contain? 0.000 or 0.0004 or even 0.00041? What this is, most likely, is the quarterly fee that they take, which they price in terms of shares. If this is your Vanguard account, and you sum all the similar such transactions around it, you should observe that it sums to 5$ per quarter. I would log it like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX -0.00041 VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD The \"*\" will be required to merge all the lots into a single one (average cost booking) because the 48.62 USD they declare is _not_ one of your lots, but rather just the price of the asset that happened to be there on the day they took the fee. In other words, they take their fee in terms of units of each asset, priced at the current price, deducting against the cost basis of all your lots together (it doesn't matter which because this is a pre-tax account, so no capital gains are reported). Now you're asking, how could I avoid calculating 0.00041 myself? My first answer would be: let's first see if the OFX download from Vanguard includes the precision. That would solve it. I believe it does (I checked my own history of downloads and I have some numbers in there with 4 fractional digits). My second answer would be that - if you don't have the number of shares from the file - after I implement the much needed new inventory booking proposal ( http://furius.ca/beancount/doc/proposal-booking ), the interpolation capabilities will be vastly extended, such that eliding the number of shares would even be possible, like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD Now this by itself still would not solve the problem: this would store 0.000411353353 which is limited at 12 fractional digits because of the default context. So that's incorrect. What would need to be done to deal with this is to infer the most common number of digits used on units of VIIPX and to quantize the result to that number of digits (I think this could be safe enough). The number of digits appearing in the file for each currency is already tracked in a DisplayContext object that is stored in the options_map from the parser. I'll have to take that into account in the inventory booking proposal. I'm adding this to the proposal. Requirements \uf0c1 The previous sections introduce the general problem of booking, and point out important shortcomings in both the Beancount and Ledger implementations. This section will present a set of desires for a new and improved booking mechanism for command-line bookkeeping software implementations. Here are reasonable requirements for a new method: Explicit inventory lot selection needs to be supported. The user should be able to indicate precisely which of the lots to reduce a position against, per-transaction. An account or commodity should not have to have a consistent method applied across all its trades. Explicit inventory lot selection should support partial matching , that is, in cases where the lots to match against are ambiguous, the user should be able to supply only minimal information to disambiguate the lots, e.g., the date, or a label, or the cost, or combinations of these. For instance, if only a single lot is available to match against, the user should be able to specify the cost as \u201c {} \u201d, telling the system to book at cost, but essentially saying: \u201cbook against any lot.\u201d This should trigger an error only if there are multiple lots. A variety of informations should be supported to specify an existing lot, and they should be combinable: The cost of acquisition of the position; The date the position was acquired at; A user-provided label. Cases where insufficient information is provided for explicit lot selection is available should admit for a default booking method to be invoked. The user should be able to specify globally and per-account what the default booking method should be. This paves the way for implicit lot selection . A degenerate method should be available that kicks off an error and requires the user to be explicit. Average cost booking needs to be supported, as it is quite common in retirement or tax-sheltered accounts. This is the default method used in Canadian investment accounts. Cost basis adjustments need to be supported as well. The problem should be able to be specified by providing a specific lot (or all of a commodity\u2019s lots at average cost) and a dollar amount to adjust the position by. Stock splits need to be able to maintain some of the original attributes of the position , specifically, the original trade date and the user-specified label, if one has been provided. This should allow a common syntax to specify an original trading lot when reducing a position after a split. Reducing a position needs to always book against an existing lot . I\u2019m preserving the Beancount behavior here, which I\u2019ve argued for previously, as it works quite well and is a great method to avoid data entry mistakes. This naturally defines an invariant for inventories: all positions of a particular commodity held at cost should be of the same sign in one inventory (this can be used as a sanity check). Bookings that change the sign of a number of units should raise an error , unless an explicit exception is requested (and I\u2019m not even convinced that we need it). Note again that bookings only occur for units held at cost, so currency conversions are unaffected by this requirement. Beancount has had this feature for a while, and it has proved useful to detect errors. For example, if an inventory has a position of 8 HOOL {500 USD} you attempt to post a change of -10 units to this lot, the resulting number of units is now negative: -2. This should indicate user error. The only use case I can think for allowing this is the trading of futures spreads and currencies, which would naturally be reported in the same account (a short position in currencies is not regarded as a different instrument in the same way that a short position would); this is the only reason to provide an exception, and I suspect that 99% of users will not need it.] Debugging Tools \uf0c1 Since this is a non-trivial but greatly important part of the process of entering trading data, we should provide tools that list in detail the running balance for an inventory, including all of the detail of its lots. Booking errors being reported should include sufficient context, that is: The transaction with offending posting The offending posting The current state of the inventory before the posting is applied, with all its lots The implicit booking method that is in effect to disambiguate between lots A detailed reason why the booking failed Explicit vs. Implicit Booking Reduction \uf0c1 Another question that may come up in the design of a new booking method is whether we require the user to be explicit about whether he thinks this is an addition to a position, or a reduction. This could be done, for instance, by requiring a slightly different syntax for the cost, for example \u201c {} \u201d would indicate an addition and \u201c [] \u201d a reduction. I\u2019m not convinced that it is necessary to make that distinction, maybe the extra burden on the user is not worth it, but it might be a way to cross-check an expectation against the actual calculations that occur. I\u2019ll drop the idea for now. Design Proposal \uf0c1 This section presents a concrete description of a design that fulfills the previously introduced requirements. We hope to keep this as simple as possible. Inventory \uf0c1 An inventory is simply a list of lot descriptors. Each inventory lot will be defined by: UNITS, (CURRENCY, COST-UNITS, COST-CURRENCY, DATE, LABEL) Fields: UNITS: the number of units of that lot that are held CURRENCY: the type of commodity held, a string COST-UNITS: the number in the price of that lot COST-CURRENCY: the pricing commodity of the cost DATE: the acquisition date of the lot LABEL: an arbitrary user-specified string used to identify this lot If this represents a lot held at cost, after this processing, only the LABEL field should be optionally with a NULL value. Anyone writing code against this inventory could expect that all other values are filled in with non-NULL values (or are otherwise all NULL). Input Syntax & Filtering \uf0c1 The input syntax should allow the specification of cost as any combination of the following informations: The cost per unit , as an amount, such as \u201c 500 USD \u201d A total cost , to be automatically divided by the number of units, like this: {... +9.95 USD} . You should be able to use either cost per unit, total cost, or even combine the two, like this: {500 + 9.95 USD} . This is useful to enter commissions. This syntax also replaces the previous {{ ... }} syntax. The lot-date , as a YYYY-MM-DD date, such as \u201c 2014-06-20 \u201d A label , which is any non-numerical identifier, such as first-apple or some random uuid like \u201c aa2ba9695cc7 \u201d A special marker \u201c * \u201d that indicates to book at the average cost of the inventory balance These following postings should all be valid syntax: ... Assets:Investments:Stock 10 HOOL {500 USD} ; cost Assets:Investments:Stock 10 HOOL {339999615d7a} ; label Assets:Investments:Stock 10 HOOL {2014-05-01} ; lot-date Assets:Investments:Stock 10 HOOL {} ; no information ... Combinations Assets:Investments:Stock 10 HOOL {500 USD, 2014-05-01} Assets:Investments:Stock 10 HOOL {2014-05-01, 339999615d7a} Algorithm \uf0c1 All postings should be processed in a separate step after parsing, in the order of their date, against a running inventory balance for each account. The cost amount should become fully determined at this stage, and if we fail to resolve a cost, an error should be raised and the transaction deleted from the flow of directives (after the program loudly complaining about it). When processing an entry, we should match and filter all inventory lots against all the filters that are provided by the user. In general, after filtering the lots with the user restrictions: If the set of lots is empty, an error should be raised; If there is a single lot, this lot should be selected (success case); If there are multiple lots, the default implicitly booking method for the corresponding account should be invoked. Implicit Booking Methods \uf0c1 If there are multiple matching lots to choose from during booking, the following implicit booking methods could be invoked: STRICT: Select the only lot of the inventory. If there are more than one lots, raise an error. FIFO: Select the lot with the earliest date. LIFO: Select the lot with the latest date. AVERAGE: Book this transaction at average cost. AVERAGE_ONLY: Like AVERAGE but also trigger an aggregation on an addition to a position. This can be used to enforce that the only booking method for all lots is at the average cost. NONE: Don\u2019t perform any inventory booking on this account. Allow a mix of lots for the same commodity or positive and negative numbers in the inventory. (This essentially devolves to the Ledger method of booking.) This method would only get invoked if disambiguation between multiple lots is required, after filtering the lots against the expression provided by the user. The STRICT method is basically the degenerate disambiguation which issues an error if there is any ambiguity and should be the default. There should be a default method that applies to all accounts. The default value should be overridable. In Beancount, we would add a new \u201coption\u201d to allow the user to change this: option \"booking_method\" \"FIFO\" The default value used in a particular account should be specifiable as well, because it is a common case that the method varies by account, and is fixed within the account. In Beancount, this would probably become part of the open directive, something like this: 2003-02-17 open Assets:Ameritrade:HOOL HOOL booking:FIFO (I\u2019m not sure about the syntax.) Resolving Same Date Ambiguity \uf0c1 If the automatic booking method gets invoked to resolve an ambiguous lot reduction, e.g. FIFO, if there are multiple lots at the same date, something needs to be done to resolve which of the lots is to be chosen. The line number at which the transaction appears should be selected. For example, in the following case, a WIDGET Of 8 GBP would be selected: 2014-10-15 * \"buy widgets\" Assets:Inventory 10 WIDGET {} ;; Price inferred to 8 GBP/widget Assets:Cash -80 GBP 2014-10-15 * \"buy another widget\" Assets:Inventory 1 WIDGET {} ;; Price inferred to 9 GBP/widget Assets:Cash -9 GBP 2014-10-16 * \"sell a widget\" Assets:Cash 11 GBP Assets:Inventory -1 WIDGET {} ;; Ambiguous lot Dates Inserted by Default \uf0c1 By default, if an explicit date is not specified in a cost, the date of the transaction that holds the posting should be attached to the trading lot automatically. This date is overridable so that stock splits may be implemented by a transformation to a transaction like this: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD, 2014-01-04} ; reducing Assets:Investments:Stock 10 HOOL {500.00 USD, 2014-01-04} ; augment Assets:Investments:Stock 10 HOOLL {500.00 USD, 2014-01-04} ; augment Matching with No Information \uf0c1 Supplying no information for the cost should be supported and is sometimes useful: in the case of augmenting a position, if all the other legs have values specified, we should be able to automatically infer the cost of the units being traded: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {} Assets:Investments:Cash -5009.95 USD Expenses:Commissions 9.95 USD In the case of reducing a position\u2014and we can figure that out whether that is the case by looking at the inventory at the point of applying the transaction to its account balance\u2014an empty specification should trigger the default booking method. If the method is STRICT, for instance, this would select a lot only if there is a single lot available (the choice is unambiguous), which is probably a common case if one trades infrequently. Other methods will apply as they are defined. Note that the user still has to specify a cost of \u201c {} \u201d in order to inform us that this posting has to be considered \u201cheld at cost.\u201d This is important to disambiguate from a price conversion with no associated cost. Reducing Multiple Lots \uf0c1 If a single trade needs to close multiple existing lots of an inventory, this can dealt with trivially by inserting one posting for each lot. I think this is a totally reasonable requirement. This could represent a single trade, for instance, if your brokerage allows it: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -10 HOOL {500 USD} Assets:Investments:Stock -12 HOOL {510 USD} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains Note: If the cost basis specification for the lot matches multiple lots of the inventory and the result is unambiguous, the lots will be correctly selected. For example, if the ante-inventory contains just those two lots (22 HOOL in total), you should be able to have a single reducing posting like this: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -22 HOOL {} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains and they will both be included. On the other hand, if the result is ambiguous (for example, if you have more than these two lots) the booking strategy for that account would be invoked. By default, this strategy will be \"STRICT\" which will generate an error, but if this account's strategy is set to \"FIFO\" (or is not set and the default global strategy is \"FIFO\"), the FIFO lots would be automatically selected for you, as per your wish. Lot Basis Modification \uf0c1 Another idea is to support the modification of a specific lot\u2019s cost basis in a single leg. The way this could work, is by specifying the number of units to modify, and the \u201c+ number currency\u201d syntax would be used to adjust the cost basis by a specific number, like this: 2012-05-01 * \"Adjust cost basis by 250 USD for 5 of the 500 USD units\" Assets:Investments:Stock ~5 HOOL {500 + 250 USD} If the number of units is smaller than the total number of units in the lot, split out the lot before applying the cost basis adjustment. I\u2019m not certain I like this. Not specifying the size could also adjust the entire position (I\u2019m not sure if this makes sense as it departs from the current syntax significantly): 2012-05-01 * \"Adjust cost basis by 250 USD for the 500 USD units\" Assets:Investments:Stock HOOL {500 + 250 USD} In any case, all the fields other than the total cost adjustment would be used to select which lot to adjust. Tag Reuse \uf0c1 We will have to be careful in the inventory booking to warn on reuse of lot labels. Labels should be unique. Examples \uf0c1 Nothing speaks more clearly than concrete examples. If otherwise unspecified, we are assuming that the booking method on the Assets:Investments:Stock account is STRICT . No Conflict \uf0c1 Given the following inventory lots: 21, (HOOL, 500, USD, 2012-05-01, null) 22, (AAPL, 380, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {} ... This booking should fail (because the amount is not one of the valid lots): 2013-05-01 * Assets:Investments:Stock -10 HOOL {520 USD} ... This one too should fail (no currency matching lot): 2013-05-01 * Assets:Investments:Stock -10 MSFT {80 USD} ... So should this one (invalid date when a date is specified): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2010-01-01} ... Explicit Selection By Cost \uf0c1 Given the following inventory: 21, (HOOL, 500, USD, 2012-05-01, null) 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 25, (HOOL, 510, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {510 USD} ... This booking should fail if the stock account\u2019s method is STRICT (ambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... This booking should succeed if the stock account\u2019s method is FIFO, booking against the lot at 2012-05-01 (earliest): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... Explicit Selection By Date \uf0c1 Given the same inventory as previously, this booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-05-01} ... This booking should fail if the method is STRICT (ambiguous) 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-06-01} ... Explicit Selection By Label \uf0c1 This booking should succeed, because there is a single lot with the \u201cabc\u201c label: 2013-05-01 * Assets:Investments:Stock -10 HOOL {abc} ... If multiple lots have the same label, ambiguous cases may occur; with this inventory, for example: 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 31, (HOOL, 510, USD, 2012-07-01, \u201cabc\u201d) The same booking should fail. Explicit Selection By Combination \uf0c1 With the initial inventory, this booking should succeed (unambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} ... There is only one lot at a cost of 500 USD and at an acquisition date of 2012-06-01. Not Enough Units \uf0c1 The following booking would be unambiguous, but would fail, because there aren\u2019t enough units of the lot in the inventory to subtract from: 2013-05-01 * Assets:Investments:Stock -33 HOOL {500 USD, 2012-06-01} \u2026 Redundant Selection of Same Lot \uf0c1 If two separate postings select the same inventory lot in one transaction, it should be able to work: 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -10 HOOL {abc} ... Of course, if there are not enough shares, it should fail: 2013-05-01 * Assets:Investments:Stock -20 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -20 HOOL {abc} ... Automatic Price Extrapolation \uf0c1 If all the postings of a transaction can have their balance computed, we allow a single price to be automatically calculated - this should work: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD And result in the equivalent of: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {534.51 USD} Income:US:Invest:Gains -340.51 USD Of course, preserving the original date of the lot should work too, so this should work as well: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {2014-02-04} Income:US:Invest:Gains -340.51 USD Average Cost Booking \uf0c1 Augmenting lots with the average cost syntax should fail: 2014-03-15 * \"Buying at average cost, what does this mean?\" Assets:US:Invest:Stock 10.00 HOOL {*} Income:US:Invest:Gains -5000.00 USD Reducing should work, this is the main use case: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {510.00 USD} Assets:US:Invest:Cash -5100.00 USD 2014-04-28 * \"Obtaining a dividend in stock\" Assets:US:Invest:Stock 1.00 HOOL {520.00 USD} Income:US:Invest:Dividends -520.00 USD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains ; Automatically calculated: -194.29 USD The effect would be to aggregate the 10 HOOL {500.00 USD} lot, the 10 HOOL {510.00 USD} lot, and the 1.00 HOOL {520.00 USD} lot, together into a single lot of 21 HOOL {505.714285 USD}, and to remove 8 HOOL from this lot, which is 8 HOOL x 505.714285 USD/HOOL = 4045.71 USD. We receive 4240.00 USD, which allows us to automatically compute the capital gain: 4240.00 - 4045.71 = 194.29 USD. After the reduction, a single lot of 13 HOOL {505.714285 USD} remains. It should also work if we have multiple currencies in the account, that is adding the following transaction to the above should not make it faile: 2014-04-15 * \"Buying another stock\" Assets:US:Invest:Stock 15.00 AAPL {300.00 USD} Assets:US:Invest:Cash -4500.00 USD However it should fail if we have the same currency priced in distinct cost currencies in the same account: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {623.00 CAD} Assets:US:Invest:Cash -62300.00 CAD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} ; Which HOOL, USD or CAD? Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains But of course, this never happens in practice, so I\u2019m not too concerned. We could potentially support specifying the cost-currency to resolve this case, that is, replacing the sell leg with this: 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {* USD} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains I\u2019m quite sure it wouldn\u2019t be useful, but we could go the extra mile and be as general as we can possibly be. Future Work \uf0c1 This proposal does not yet deal with cost basis re-adjustments! We need to way to be able to add or remove a fixed dollar amount to a position\u2019s cost basis, that is, from (1) a lot identifier and (2) a cost-currency amount, we should be able to update the cost of that lot. The transaction still has to balance. Here are some ideas what this might look like: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD + 230.00 USD} Income:US:Invest:Gains -230.00 USD The resulting inventory would be 10 HOOL at 523.00 USD per share, and the rest of the original lots, less 10 HOOL at 500.00 USD per share (there might be some of that remaining, that\u2019s fine). This is the equivalent of 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500.00 USD} Assets:US:Invest:Stock 10.00 HOOL {523.00 USD} Income:US:Invest:Gains -230.00 USD except you did not have to carry out the calculation. This should therefore be implemented as a transformation. However, this is not super useful, because this is already supported... adjustments are usually performed on lots at average cost, and this is where the problem lie: you\u2019d have to make the calculation yourself! Here\u2019s a relevant use case: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD This would first average all the lots of HOOL together and recompute the cost basis with the new amount. This should work: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 5.00 HOOL {500.00 USD} Assert:US:Invest:Cash -2500.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 5.00 HOOL {520.00 USD} Assets:US:Invest:Cash -2600.00 USD 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Notice how we still have to specify an amount of HOOL shares to be repriced. I like this, but I\u2019m wondering if we should allow specifying \u201call the units\u201d like this (this would be a more radical change to the syntax): 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock * HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Perhaps the best way to auto-compute cost basis adjustments would be via powerful interpolation, like this: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500 USD} ; reduce Assets:US:Invest:Stock 10.00 HOOL {} ; augment, interpolated Income:US:Invest:Gains -230.00 USD See the Smarter Elision document for more details on a proposal. Inter-Account Booking \uf0c1 Some tax laws require a user to book according to a specific method, and this might apply between all accounts. This means that some sort of transfer needs to be applied in order to handle this correctly. See the separate document for detail. TODO - complete this with more tests for average cost booking! Implementation Notes \uf0c1 Separate Parsing & Interpolation \uf0c1 In order to implement a syntax for reduction that does not specify the cost, we need to make changes to the parser. Because there may be no costs on the posting, it becomes impossible to perform balance checks at parsing time. Therefore, we will need to postpone balance checks to a stage after parsing. This is reasonable and in a way nice: it is best if the Beancount parser does not output many complex errors at that stage. A new \u201cpost-parse\u201d stage will be added, right before running the plugins. Conclusion \uf0c1 This document is work-in-progress. I\u2019d really love to get some feedback in order to improve the suggested method, which is why I put this down in words instead of just writing the code. I think a better semantic can be reached by iterating on this design to produce something that works better in all systems, and this can be used as documentation later on for developers to understand why it is designed this way. Your feedback is much appreciated.","title":"A Proposal for an Improvement on Inventory Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inventory-booking","text":"A Proposal for an Improvement on Command-Line Bookkeeping Martin Blais, June 2014 http://furius.ca/beancount/doc/proposal-booking Motivation Problem Description Lot Date Average Cost Basis Capital Gains Sans Commissions Cost Basis Adjustments Dealing with Stock Splits Previous Solutions Shortcomings in Ledger Shortcomings in Beancount Requirements Debugging Tools Explicit vs. Implicit Booking Reduction Design Proposal Inventory Input Syntax & Filtering Algorithm Implicit Booking Methods Dates Inserted by Default Matching with No Information Reducing Multiple Lots Examples No Conflict Explicit Selection By Cost Explicit Selection By Date Explicit Selection By Label Explicit Selection By Combination Not Enough Units Redundant Selection of Same Lot Automatic Price Extrapolation Average Cost Booking Future Work Implementation Note Conclusion","title":"Inventory Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#motivation","text":"The problem of \u201cinventory booking,\u201d that is, selecting which of an inventory\u2019s trade lots to reduce when closing a position, is a tricky one. So far, in the command-line accounting community, relatively little progress has been made in supporting the flexibility to deal with many common real-life cases. This document offers a discussion of the current state of affairs, describes the common use cases we would like to be able to solve, identifies a set of requirements for a better booking method, and proposes a syntax and implementation design to support these requirements, along with clearly defined semantics for the booking method.","title":"Motivation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#problem-description","text":"The problem under consideration is the problem of deciding, for a double-entry transaction that intends to reduce the size of a position at a particular point in time in a particular account, which of the account inventory lots contained at that point should be reduced. This should be specified using a simple data entry syntax that minimizes the burden on the user. For example, one could enter a position in HOOL stock by buying two lots of it at different points in time: 2014-02-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade\" Assets:Investments:Stock 8 HOOL {510 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD We will call the two sets of shares \u201ctrade lots\u201d and assume that the underlying system that counts them is keeping track of each of their cost and date of acquisition separately. The question here is, if we were to sell some of this stock, which of the shares should we select? Those from the first trade, or those from the second? This is an important decision, because it has an impact on capital gains, and thus on taxes. (The account of capital gains is described in more detail in the \u201cStock Trading\u201d section of the Beancount cookbook if you need an explanation of this.) Depending on our financial situation, this year\u2019s trading history, and the unrealized gains that a trade would realize, sometimes we may want to select one lot over another. By now, most discount brokers even let you select your specific lot when you place a trade that reduces or closes a position. It is important to emphasize here the two directions of inventory booking: Augmenting a position. This is the process of creating a new trading lot, or adding to an existing trading lot (if the cost and other attributes are identical). This is easy, and amounts to adding a record to some sort of mapping that describes an account\u2019s balance (I call this an \"inventory\"). Reducing a position. This is generally where booking complications take place, and the problem is essentially to figure out which lot of an existing position to remove units from. Most of this document is dedicated to the second direction.","title":"Problem Description"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#lot-date","text":"Even if the cost of each lot is identical, the acquisition date of the position you\u2019re intending to close matters, because different taxation rules may apply, for example, in the US, positions held for more than 12 months are subject to a significantly lower tax rate (\u201cthe long-term capital gains rate\u201d). For example, if you entered your position like this: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD 2014-02-15 * \"Second trade at same price\" Assets:Investments:Stock 8 HOOL {300 USD} Assets:Investments:Cash Expenses:Commissions 9.95 USD and we are booking a trade for 2014-03-01, selecting the first lot would result in a long-term (low) gain, because two years have passed since acquisition (position held from 2012-05-01 to 2014-03-01) while booking against the second would result in a short-term (high) capital gains tax. So it\u2019s not just a matter of the cost of the lots. Our systems don't aim to file taxes automatically at this point but we would like to enter our data in a way that eventually allows this kind of reporting to take place. Note that this discussion assumes that you were able to decide which of the lots you could trade against. There are several taxation rules that may apply, and in some cases, depending on which country you live in, you may not have a choice, the government may dictate how you are meant to report your gains. In some cases you may even need to be able to apply multiple booking methods to the same account (e.g., if an account is subject to a taxation claim from multiple countries). We will ignore this exotic case in this document, as it is quite rare.","title":"Lot Date"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#average-cost-basis","text":"Things get more complicated for tax-sheltered accounts. Because there are no tax consequences to closing positions in these accounts, brokers will normally choose to account for the book value of your positions as if they were a single lot. That is, the cost of each share is computed using the weighted average of the cost of the position you are holding. This can equivalently be calculated by summing the total cost of each position and then dividing by the total number of shares. To continue with our first example: 10 HOOL x 500 USD/HOOL = 5000 USD 8 HOOL x 510 USD/HOOL = 4080 USD Cost basis = 5000 USD + 4080 USD = 9080 USD Total number of shares = 10 HOOL + 8 HOOL = 18 HOOL Cost basis per share = 9080 USD / 18 HOOL = 504.44 USD/HOOL So if you were to close some of your position and sell some shares, you would do so at a cost of 504.44 USD/HOOL. The gain you would realize would be relative to this cost; for example if you sold 5 shares at 520 USD/HOOL, your gain would be calculated like this: (520 USD/HOOL - 504.44 USD/HOOL) x 5 HOOL = 77.78 USD This type of booking method is made evident by two kinds of financial events that you might witness occur in these types of accounts: Fees may be withdrawn by selling some shares reported at their current market price. You will see these if you hold a retirement account at Vanguard in the USA. The broker takes a fixed amount of dollars per quarter (5$, with my employer's plan) that is backed out in terms of a small, fractional number of shares of each fund to sell. That transaction is oblivious to capital gains: they simply figure out how many shares to sell to cover their fee and don\u2019t report a gain to you. You have to assume it doesn\u2019t matter. Most people don't track their gains in non-taxable accounts but freaks like us may want to compute the return nonetheless. Cost basis readjustments may occur spontaneously. This is rare, but I have seen this once or twice in the case of mutual funds in a Canadian tax-deferred account: based on some undisclosed details of their trading activity, the fund management has had to issue an adjustment to the cost basis, which you are meant to apply to your positions. You get an email telling you how much the adjustment was for. 99% of people probably don\u2019t blink, don't understand and ignore this message\u2026 perhaps rightly so, as it has no impact on their taxes, but if you want to account for your trading returns in that account, you do need to count it. So we need to be able to add or remove to the cost basis of existing positions. Note that the average cost of your positions changes on every trade and needs to get recalculated every time that you add to an existing position. A problem with this is that if you track the cost per share, these multiple recalculations may result in some errors if you store the amounts using a fixed numerical representation (typically decimal numbers). An alternative method to track the cost in our data structures, one that is more stable numerically, is to simply keep track of the total cost instead of the cost-per-share. Also note that using fractional numbers does not provide a sufficient answer to this problem: in practice, the broker may report a specific cost, one that is rounded to specific number of decimals, and we may want to be able to book our reducing trade using that reported number rather than maintain the idealized amount that a fractional representation would. Both using a fixed decimal or a fractional representation pose problems. We will largely ignore those problems here. In summary: we need to be able to support book against lots at their average cost, and we need to be able to adjust the cost basis of an existing position. I\u2019ve been thinking about a solution to implement this that would not force an account to be marked with a special state. I think we can do this using only inventory manipulations. All we need is a small change to the syntax that allows the user to indicate to the system that it should book against the average cost of all positions. Using the first example above, acquiring the shares would take the same form as previously, that is after buying two lots of 10 and 8 HOOL units, we end up with an inventory that holds two lots, one of 10 HOOL {500 USD} and one of 8 HOOL {510 USD}. However, when it\u2019s time to sell, the following syntax would be used (this is an old idea I\u2019ve been meaning to implement for a while): 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 HOOL {*} Assets:Investments:Cash 2759.95 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains When the \u201c * \u201d is encountered in lieu of the cost, like this, it would: Merge all the lots together and recalculate the average price per share (504.44 USD) Book against this merged inventory, reducing the resulting lot. After the transaction, we would end up with a single position of 13 HOOL {504.44 USD). Adding to this position at a different price would create a new lot, and those would get merged again the next time a reduction occurs at average cost. We do not need to merge lots until there is a reduction. Apart from concerns of accumulating rounding error, this solution is correct mathematically, and maintains the property that accounts aren\u2019t coerced into any specific booking method\u2014a mix of methods could be used on every reduction. This is a nice property, even if not strictly necessary, and even if we want to be able to just specify a \u201cdefault\u201d booking method to use per account and just stick with it throughout. It\u2019s always nice to have the flexibility to support exceptions, because in real life, they sometimes occur.","title":"Average Cost Basis"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#capital-gains-sans-commissions","text":"We would like to be able to support the automatic calculation of capital gains without the commissions. This problem is described in much detail in the Commissions section of the trading documentation and in a thread on the mailing-list . The essence of the complication that occurs is that one cannot simply subtract the commissions incurred during the reporting period from the gains that include commissions, because the commissions that were incurred to acquire the position must be pro-rated to the shares of the position that are sold. The simplest and most common way to implement this is to include the costs of acquiring the position into the cost basis of the position itself, and deduct the selling costs from the market value when a position is reduced. Whatever new design we come up with must allow us to count these adjusted gains as this is essential to various individuals' situations. In Beancount, I have figured out a solution for this problem, which luckily enough involves only an automated transformation of a transaction flagged by the presence of a special flag on its postings... if and only if I can find a way to specify which lot to book against without having to specify its cost, because once the cost of commissions gets folded into the cost of the position, the adjusted cost does not show up anywhere in the input file, the user would have to calculate it manually, which is unreasonable. In the solution I\u2019m proposing, the following transactions would be transformed automatically, triggered by the presence of the \u201cC\u201d flag: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD C Expenses:Commissions 9.95 USD Assets:US:Invest:HOOL 10.00 HOOL {500 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains 2014-05-10 * \"Sell #2\" Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} C Expenses:Commissions 9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains They would be automatically transformed by a plugin into the following and replace the original transactions above: 2014-02-10 * \"Buy\" Assets:US:Invest:Cash -5009.95 USD X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD X Assets:US:Invest:HOOL 10.00 HOOL {500.995 USD / aa2ba9695cc7} 2014-04-10 * \"Sell #1\" X Assets:US:Invest:HOOL -4.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 2110.05 USD Income:US:Invest:Gains ; Should be (530-500)*4 - 9.95*(4/10) - 9.95 = ; 106.07 USD 2014-05-10 * \"Sell #2\" X Assets:US:Invest:HOOL -6.00 HOOL {aa2ba9695cc7} X Expenses:Commissions 9.95 USD X Income:US:Invest:Rebates -9.95 USD Assets:US:Invest:Cash 3230.05 USD Income:US:Invest:Gains ; Should be (540-500)*6 - 9.95*(6/10) - 9.95 = ; 224.08 USD The \u201cX\u201d flag would just mark the postings that have been transformed, so maybe they can be rendered with a special color or attribute later on. The rebates account is included as a separate counter just to make the transaction balance. The reason I need to relax the disambiguation of trading lots is that the user needs to be able to specify the matching leg without having to specify the cost of the position, because at the point where the position is reduced (2014-04-10), there is no way to figure out what the cost of the original lot was. Just to be clear, in the example above, this means that if all the information have is 4 HOOL and 500 USD, there is no way to back out a cost of 500.995 USD that could specify a match with the opening trade lot, because that happened with 10 HOOLs. So we need to be more explicit about booking. In the example above, I\u2019m selecting the matching lot \u201cby label,\u201d that is, the user has the option to provide a unique lot identifier in the cost specification, and that can later on be used to disambiguate which lot we want to book a reduction/sale against. The example above uses the string \u201c aa2ba9695cc7 \u201d in this way. (An alternative solution to this problem would involve keep track of both the original cost (without commissions), and the actual cost (with commissions), and then finding the lot against the former, but using the latter to balance the transaction. This idea is to allow the user to keep using the cost of a position to select the lot, but I\u2019m not even sure if that is possible, in the presence of various changes to the inventory. More thought is required on this matter.)","title":"Capital Gains Sans Commissions"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#cost-basis-adjustments","text":"In order to adjust the cost basis, one can replace the contents of an account explicitly like this: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {510 USD} Income:US:Invest:Gains This method works well and lets the system automatically compute the gains. But it would be nice to support making adjustments in price units against the total cost of the position, for example, \u201cadd 340.51 USD to the cost basis of this position\u201d. The problem is that the adjusted cost per share now needs to be computed by the user\u2026 it\u2019s inconvenient. Here\u2019s one way we could support this well, however: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD If all the legs are fully specified - they have a calculatable balance - we allow a single price to be elided. This solves this problem well.","title":"Cost Basis Adjustments"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#dealing-with-stock-splits","text":"Accounting for stock splits creates some complications in terms of booking. In general, the problem is that we need to deal with changes in the meaning of a commodity over time. For example, you could do this in Beancount right now and it works: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD} Assets:Investments:Stock 10 HOOL {500.00 USD} Assets:Investments:Stock 10 HOOLL {500.00 USD} One problem with splitting lots this way is that the price and meaning of a HOOL unit before and after the split differs, but let\u2019s just assume we\u2019re okay with that for now (this can be solved by relabeling the commodity by using another name, ideally in a way that is not visible to the user - we have a pending discussion elsewhere ). The issue that concerns booking is, when you sell that position, which cost do you use? The inventory will contain positions at 500 USD, so that\u2019s what you should be using, but is it obvious to the user? We can assume this can be learned with a recipe . Now, what if we automatically attach the transaction date? Does it get reset on the split and now you would have to use 2014-04-17? If so, we could not automatically inspect the list of trades to determine whether this is a long-term vs. short-term trade. We need to somehow preserve some of the attributes of the original event that augmented this position, which includes the original trade date (not the split date) and the original user-specified label on the position, if one was provided.","title":"Dealing with Stock Splits"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#forcing-a-single-method-per-account","text":"For accounts that will use the average booking method, it may be useful to allow specifying that an account should only use this booking method . The idea is to avoid data entry errors when we know that an account is only able to use this method. One way to ensure this is to automatically aggregate inventory lots when augmenting a position. This ensures that at any point in time, there is only a single lot for each commodity. If we associated an inventory booking type to each account, we could define a special one that also triggers aggregation upon the addition of a position. Another approach would be to not enforce these aggregations, but to provide a plugin that would check that for those accounts that are marked as using the average cost inventory booking method by default, that only such bookings actually take place.","title":"Forcing a Single Method per Account"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#previous-solutions","text":"This section reviews existing implementations of booking methods in command-line bookkeeping systems and highlights specific shortcomings that motivate why we need to define a new method.","title":"Previous Solutions"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#shortcomings-in-ledger","text":"(Please note: I\u2019m not deeply familiar with the finer details of the inner workings of Ledger; I inferred its behavior from building test examples and reading the docs. If I got any of this wrong, please do let me know by leaving a comment.) Ledger\u2019s approach to booking is quite liberal. Internally, Ledger does not distinguish between conversions held at cost and regular price conversions : all conversions are assumed held at cost, and the only place trading lots appear is at reporting time (using its --lots option). Its \u201c {} \u201d cost syntax is meant to be used to disambiguate lots on a position reduction , not to be used when acquiring a position. The cost of acquiring a position is specified using the \u201c @ \u201d price notation: 2014/05/01 * Buy some stock (Ledger syntax) Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash This will result in an inventory of 10 HOOL {500 USD}, that is, the cost is always stored in the inventory that holds the position: $ ledger -f l1.lgr bal --lots 10 HOOL {USD500} [2014/05/01] USD-5000 Assets:Investments USD-5000 Cash 10 HOOL {USD500} [2014/05/01] Stock -------------------- 10 HOOL {USD500} [2014/05/01] USD-5000 There is no distinction between conversions at cost and without cost\u2014all conversions are tracked as if \u201cat cost.\u201d As we will see in the next section, this behaviour is distinct from Beancount\u2019s semantics, which requires a conversion held at cost to be specified using the \u201c {} \u201d notation for both its augmenting and reducing postings, and distinguishes between positions held at cost and price conversions. The advantage of the Ledger method is a simpler input mechanism, but it leads to confusing outcomes if you accumulate many conversions of many types of currencies in the same account. An inventory can easily become fragmented in its lot composition. Consider what happens, for instance, if you convert in various directions between 5 different currencies\u2026 you may easily end up with USD held in CAD, JPY, EUR and GBP and vice-versa\u2026 any possible combinations of those are possible. For instance, the following currency conversions will maintain their original cost against their converted currencies: 2014/05/01 Transfer from Canada Assets:CA:Checking -500 CAD Assets:US:Checking 400 USD @ 1.25 CAD 2014/05/15 Transfers from Europe Assets:UK:Checking -200 GBP Assets:US:Checking 500 USD @ 0.4 GBP 2014/05/21 Transfers from Europe Assets:DE:Checking -100 EUR Assets:US:Checking 133.33 USD @ 0.75 EUR This results in the following inventory in the Assets:US:Checking account: $ ledger -f l2.lgr bal --lots US:Checking 500.00 USD {0.4 GBP} [2014/05/15] 133.33 USD {0.75 EUR} [2014/05/21] 400.00 USD {1.25 CAD} [2014/05/01] Assets:US:Checking The currencies are treated like stock. But nobody thinks of their currency balances like this , one normally thinks of currencies held in an account as units in and of themselves, not in terms of their price related to some other currency. In my view, this is confusing. After these conversions, I just think of the balance of that account as 1033.33 USD. A possible rebuttal to this observation is that most of the time a user does not care about inventory lots for accounts with just currencies, so they just happen not print them out. Out of sight, out of mind. But there is no way to distinguish when the rendering routine should render lots or not. If instructed to produce a report, a balance routine should ideally only render lots only for some accounts (e.g., investing accounts, where the cost of units matters) and not for others (e.g. accounts with currencies that were deposited sometimes as a result of conversions). Moreover, when I render a balance sheet, for units held at cost we need to report the book values rather than the number of shares. But for currencies, the currency units themselves need to get reported, always. If you don\u2019t distinguish between the two cases, how do you know which to render? I think the answer is to select which view you want to render using the options. The problem is that neither provides a single unified view that does the correct thing for both types of commodities. The user should not have to specify options to view partially incorrect views in one style or the other, this should be automatic and depend on whether we care about the cost of those units. This gets handled correctly if you distinguish between the two kinds of conversions. But perhaps most importantly, this method does not particularly address the conversion problem : it is still possible to create money out of thin air by making conversions back and forth at different rates: 2014/05/01 Transfer to Canada Assets:US:Checking -40000 USD Assets:CA:Checking 50000 CAD @ 0.80 USD 2014/05/15 Transfer from Canada Assets:CA:Checking -50000 CAD @ 0.85 USD Assets:US:Checking 42500 USD This results in a trial balance with just 2500 USD: $ ledger -f l3.lgr bal 2500 USD Assets:US:Checking This is not a desirable outcome. We should require that the trial balance always sum to zero (except for virtual transactions). After all, this is the aggregate realization of the elegance of the double-entry method: because all transactions sum to zero, the total sum of any subgroup of transactions also sums to zero\u2026 except it doesn\u2019t. In my view, any balance sheet that gets rendered should comply with the accounting equation, precisely. In my explorations with Ledger, I was hoping that by virtue of its inventory tracking method it would somehow naturally deal with these conversions automatically and thus provide a good justification for keeping track of all units at cost, but I witness the same problem showing up. (The conversions issue has been a real bitch of a problem to figure out a solution for in Beancount, but it is working, and I think that the same solution could be applied to Ledger to adjust these sums, without changing its current booking method: automatically insert a special conversion entry at strategic points - when a balance sheet is rendered.) In the end, I have not found that tracking costs for every transaction would appear to have an advantage over forgetting the costs for price conversions, the conversions problem is independent of this. I\u2019d love to hear more support in favour of this design choice. Now, because all conversions maintain their cost, Ledger needs to be quite liberal about booking, because it would be inconvenient to force the user to specify the original rate of conversion for each currency, in the case of commonly occurring currency conversions. This has an unfortunate impact on those conversions which we do want to hold at cost: arbitrary lot reductions are allowed, that is, reductions against lots that do not exist. For example, the following trade does not trigger an error in Ledger: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of an impossible lot Assets:Investments:Stock -10 HOOL {505 USD} @ 510 USD Assets:Investments:Cash This results in an inventory with a long position of 10 HOOL and a short position of 10 HOOL (at a different cost): $ ledger -f l4.lgr bal --lots stock 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD505} Assets:Investments:Stock I think that the reduction of a 505 USD position of HOOL should not have been allowed; Beancount treats this as an error by choice. Ledger clearly appears to support booking against an existing inventory, so surely the intention was to be able to reduce against existing positions. The following transactions do result in an empty inventory, for instance: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot Assets:Investments:Stock -10 HOOL {500 USD} [2014/05/01] @ 510 USD Assets:Investments:Cash With output: $ ledger -f l5.lgr bal --lots USD100 Equity:Capital Gains As you see, the HOOL position has been eliminated. (Don\u2019t mind the auto-inserted equity entry in the output, I believe this will be fixed in a pending patch .) However, both the date and the cost must be specified for this to work. The following transactions results in a similar problematic long + short position inventory as mentioned above: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL @ 500 USD Assets:Investments:Cash 2014/05/15 Sale of matching lot? Assets:Investments:Stock -10 HOOL {500 USD} @ 510 USD Assets:Investments:Cash With output: $ ledger -f l6.lgr bal --lots 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} Assets:Investments:Stock USD100 Equity:Capital Gains -------------------- 10 HOOL {USD500} [2014/05/01] -10 HOOL {USD500} USD100 This is a bit surprising, I expected the lots to book against each other. I suspect this may be an unreported bug, and not intended behaviour. Finally, the \u201c {} \u201d cost syntax is allowed be used on augmenting legs as well. The documentation points to these methods being equivalent . It results in an inventory lot that does not have a date associated with it, but the other leg is not converted to cost: 2014/05/01 Enter a position Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash With output: $ ledger -f l7.lgr bal --lots 0 Assets:Investments -10 HOOL {USD500} Cash 10 HOOL {USD500} Stock -------------------- 0 I probably just don\u2019t understand how this is meant to be used; in Beancount the automatically computed leg would have posted a -5000 USD amount to the Cash account, not shares. What I think is going on, is that Ledger (probably) accumulates all the lots without attempting to match them together to try to make reductions, and then at reporting time - and only then - it matches the lots together based on some reporting options: Group lots by both cost/price and date, using --lots Group lots by cost/price only, using --lot-prices Group lots by date only, using --lot-dates It is not entirely obvious from the documentation how that works, but the behavior is consistent with this. (If this is correct, I believe that to be a problem with its design: the mechanism by which an inventory reduction is booked to an existing set of lots should definitely not be a reporting feature. It needs to occur before processing reports, so that a single and definitive history of inventory bookings can be realized. If variants in bookings are supported - and I don\u2019t think this is a good idea - they should be supported before the reporting phase. In my view, altering inventory booking strategies from command-line options is a bad idea, the strategies in place should be fully defined by the language itself.)","title":"Shortcomings in Ledger"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#shortcomings-in-beancount","text":"Beancount also has various shortcomings, though different ones. In contrast to Ledger, Beancount disambiguates between currency conversions and conversions \u201cheld at cost\u201d: 2014-05-01 * \"Convert some Loonies to Franklins\" Assets:CA:Checking -6000 CAD Assets:Investments:Cash 5000 USD @ 1.2 CAD ; Currency conversion 2014-05-01 * \"Buy some stock\" Assets:Investments:Stock 10 HOOL {500 USD} ; Held at cost Assets:Investments:Cash In the first transaction, the Assets:Investment:Cash account results in an inventory of 5000 USD, with no cost associated to it. However, after the second transaction the Assets:Investment:Stock account has an inventory of 10 HOOL with a cost of 500 USD associated to it. This is perhaps a little bit more complex, and requires more knowledge from the user: there are two kinds of conversions and he has to understand and distinguish between these two cases, and this is not obvious for newcomers to the system. Some user education is required (I\u2019ll admit it would help if I wrote more documentation). Beancount\u2019s method is also less liberal, it requires a strict application of lot reduction. That is, if you enter a transaction that reduces a lot that does not exist, it will output an error. The motivation for this is to make it difficult for the user to make a mistake in data entry. Any reduction of a position in an inventory has to match against exactly one lot. There are a few downsides that result from this choice: It requires the users to always find the cost of the lot to match against. This means that automating the import of transactions requires manual intervention from the user, as he has to go search in the ledger to insert the matching lot. (Note that theoretically the import code could load up the ledger contents to list its holdings, and if unambiguous, could look for the cost itself and insert it in the output. But this makes the import code dependent on the user\u2019s ledger, which most import codes aren't doing). This is an important step, as finding the correct cost is required in order to correctly compute capital gains, but a more flexible method is desired, one that allows the user to be a bit more lazy and not have to put the cost of the existing position when the choice is obvious, e.g., when there is a single lot of that unit to match against. I\u2019d like to relax this method somewhat. The strict requirement to reduce against an existing lot also means that both long and short positions of the same commodities \u201cheld at cost\u201d in the same account cannot exist. This is not much of a problem in practice, however, because short positions are quite rare\u2014few individuals engage in them\u2014and the user could always create separate accounts to hold short positions if needed. In fact, it is already customary to create a dedicated subaccount for each commodity in an investment account, as it naturally organizes trading activity nicely (reports a bit of a mess otherwise, it\u2019s nice to see the total position value grouped by stock, since they offer exposure to different market characteristics). Different accounts should work well, as long as we treat the signs correctly in reductions, i.e., buying stock against an existing short position is seen as a position reduction (the signs are just reversed). Another problem with the Beancount booking method is in how it matches against lots with dates. For example, if you were to try to disambiguate between two lots at the same price, the most obvious input would not work: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash This would report a booking error. This is confusing. In this case Beancount, in its desire to be strict, applies strict matching against the inventory lots, and attempting to match units of a lot of (HOOL, 500 USD, 2012-05-01) with units of (HOOL, 500 USD, None ) simply fails. Note that the lot-date syntax is accepted by Beancount on both augmenting and reducing legs, so the \u201csolution\u201d is that the user is forced to specifically provide the lot-date on acquiring the position. This would work, for instance: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash 2014-02-15 * \"Second trade at very same price\" Assets:Investments:Stock 8 HOOL {500 USD} Assets:Investments:Cash 2014-06-30 * \"Trying to sell\" Assets:Investments:Stock -5 HOOL {500 USD / 2012-05-01} Assets:Investments:Cash The inconvenience here is that when the user entered the first trade, he could not predict that he would enter another trade at the same price in the future and typically would not have inserted the lot-date. More likely than not, the user had to go back and revisit that transaction in order to disambiguate this. We can do better. This is a shortcoming of its implementation, which reflects the fact that position reduction was initially regarded as an exact matching problem rather than a fuzzy matching one\u2014it wasn\u2019t clear to me how to handle this safely at the time. This needs to be fixed after this proposal, and what I\u2019m doing with this document is very much a process of clarifying for myself what I want this fuzzy matching to mean, and to ensure that I come up with an unambiguous design that is easy to enter data for. Ledger always automatically attaches the transaction date to the inventory lot, and in my view this is the correct thing to do (below we propose attaching more information as well).","title":"Shortcomings in Beancount"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#context","text":"[Updated on Nov\u20192014] It is important to distinguish between two types of booking: Booking. The strict type of booking that occurs when we are considering the entire list of transactions. We will call this the \u201cbooking\u201d process, and it should occur just once. This process should be strict: the failure of correctly book a reduction to an existing lot should trigger a fatal error. Inventory Aggregation. When we are considering a subset of entries, it is possible that entries that add some lots are removed, and that other entries that reduce or remove those lots are kept. This creates a situation in which it is necessary to support aggregations over time of changes to an inventory that may not admit a matching lot. If we are to support arbitrary subsets of transactions being aggregated, we must take this into account. This aggregation process should never lead to an error. Let\u2019s take an example to justify why we need the non-booking summarization. Let\u2019s say we have a stock purchase and sale: 2013-11-03 * \"Buy\" Assets:Investments:VEA 10 AAPL {37.45 USD} ;; (A) Assets:Investments:Cash -370.45 USD 2014-08-09 * \"Sell\" Assets:Investments:VEA -5 AAPL {37.45 USD} Assets:Investments:Cash 194.40 USD Income:Investments:PnL If we filter \u201cby year\u201d and only want to view transactions that occurred in 2014, AND we don\u2019t \u201cclose\u201d the previous year, that is, we do not create opening balances entries that would deposit the lots in the account at the beginning of the year - this could happen if we filter by tag, or by some other combination of conditions - then the (A) lot is not present in the inventory to be debited from. We must somehow accommodate a -5 AAPL at that point. When reporting, the user could decide how to summarize and \u201cmatch up as best it can\u201d those legs, but converting them to units or cost, or convert them to a single lot at average-cost. The booking process, however, should admit no such laxity. It should be strict and trigger an error if a lot cannot be matched. This would only be applied on the total set of transactions, and only once, never on any subset. The purpose of the booking stage should be threefold: Match against existing lots and issue errors when that is impossible. Do this, using the method specified by the user. Replace all partially specified lots by a full lot specification, the lot that was matched during the booking process. Insert links on transactions that form a trade: a buy and corresponding sells should be linked together. This essentially identifies trades, and can then be used for reporting later on. (Trades at average cost will require special consideration.) We can view Ledger\u2019s method as having an aggregation method only, lacking a booking stage. The particular method for aggregation is chosen using --lots or --lot-dates or --lot-prices. This would indicate that a \u201cbooking\u201d stage could be tacked on to Ledger without changing much of its general structure: it could be inserted as a pre- or post- process, but it would require Ledger to sort the transactions by date, something it does not currently do (it runs a single pass over its data). Beancount has had both methods for a while, but the separate nature of these two operations has not been explicitly stated so far\u2014instead, the inventory supported an optional flag to raise an error when booking was impossible. Also, inventory aggregation has not been thought as requiring much customization, it was meant to be unified to the booking process. It is now clear that they are two distinct algorithms, and that a few different methods for aggregating can be relevant (though they do not function the same as Ledger\u2019s do. Note: you can view the inventory aggregation method as a GROUP BY within an Inventory object\u2019s lots: which columns do you group over? This requires use cases). In any case, fully specified lots should match against each other by default: we need to support this as the degenerate case to use for simple currencies (not held at cost)... we would not want to accumulate all changes in the same currency as separate lots in an inventory, they need to cross each other as soon as they are applied. We want to change this in order to make the code clearer: there should be a separate \u201cbooking\u201d stage, provided by a plugin , which resolves the partially specific lot reduction using the algorithm outlined in this document, and a separate method for inventory aggregation should not bother with any of those details. In fact, the inventory aggregation could potentially simply become an accumulated list of lots, and the summarization of them could take place a posteriori, with some conceptual similarly to when Ledger applies its aggregation. Just using a simple aggregation becomes more relevant once we begin entering more specific data about lots, such as always having an acquisition date, a label, and possibly even a link to the entry that created the lot. In order to trigger summarization sensibly, functions to convert and summarize accumulated inventories could be provided in the shell, such as \u201cUNITS(inventory)\u201d.","title":"Context"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#precision-for-interpolation","text":"The interpolation capabilities will be extended to cover eliding a single number, any number, in any subset of postings whose \u201cweights\u201d resolve to a particular currency (e.g., \u201call postings with weights in USD\u201d). The interpolation needs to occur at a particular precision. The interpolated number should be quantized automatically, and the number of fractional digits to quantize it to should be automatically inferred from the DisplayContext which is itself derived from the input file. Either the most common or the maximum number of digits witnessed in the file should be used. For a use case, see this thread : On Mon, Nov 24, 2014 at 12:33 PM, ELI wrote: Regarding the last piece on which there seems to be a misunderstanding between you and I, let me provide an standalone example, outside the context of the plugin. Since Vanguard only makes three decimal places of precision available me, I need to have the actual share count calculated from the price and transaction amount. For example, I have a transaction on my activities page with these details: Shares Transacted: 0.000 Share Price: 48.62 Amount: 0.02 The only way to enter this transaction and have it balance would be to manually calculate the Shares Transacted with four decimal places of precision. My preferred way of entering this would be to enter the Share Price and Amount and have Beancount calculate Shares Transacted to a precision associated with my Vanguard account. Additionally, as metadata, I'd record \"Shares Transacted: 0.000\" as history of \"what the statement said\". Maybe you can give me your thoughts on how such a case would be addressed with planned Beancount features or as a plugin I could right? What does the downloadable file contain? 0.000 or 0.0004 or even 0.00041? What this is, most likely, is the quarterly fee that they take, which they price in terms of shares. If this is your Vanguard account, and you sum all the similar such transactions around it, you should observe that it sums to 5$ per quarter. I would log it like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX -0.00041 VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD The \"*\" will be required to merge all the lots into a single one (average cost booking) because the 48.62 USD they declare is _not_ one of your lots, but rather just the price of the asset that happened to be there on the day they took the fee. In other words, they take their fee in terms of units of each asset, priced at the current price, deducting against the cost basis of all your lots together (it doesn't matter which because this is a pre-tax account, so no capital gains are reported). Now you're asking, how could I avoid calculating 0.00041 myself? My first answer would be: let's first see if the OFX download from Vanguard includes the precision. That would solve it. I believe it does (I checked my own history of downloads and I have some numbers in there with 4 fractional digits). My second answer would be that - if you don't have the number of shares from the file - after I implement the much needed new inventory booking proposal ( http://furius.ca/beancount/doc/proposal-booking ), the interpolation capabilities will be vastly extended, such that eliding the number of shares would even be possible, like this: 2014-11-23 * \"Quarterly fee\" Assets:US:Vanguard:VIIPX VIIPX {* 48.62 USD} Expenses:Financial:Fees 0.02 USD Now this by itself still would not solve the problem: this would store 0.000411353353 which is limited at 12 fractional digits because of the default context. So that's incorrect. What would need to be done to deal with this is to infer the most common number of digits used on units of VIIPX and to quantize the result to that number of digits (I think this could be safe enough). The number of digits appearing in the file for each currency is already tracked in a DisplayContext object that is stored in the options_map from the parser. I'll have to take that into account in the inventory booking proposal. I'm adding this to the proposal.","title":"Precision for Interpolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#requirements","text":"The previous sections introduce the general problem of booking, and point out important shortcomings in both the Beancount and Ledger implementations. This section will present a set of desires for a new and improved booking mechanism for command-line bookkeeping software implementations. Here are reasonable requirements for a new method: Explicit inventory lot selection needs to be supported. The user should be able to indicate precisely which of the lots to reduce a position against, per-transaction. An account or commodity should not have to have a consistent method applied across all its trades. Explicit inventory lot selection should support partial matching , that is, in cases where the lots to match against are ambiguous, the user should be able to supply only minimal information to disambiguate the lots, e.g., the date, or a label, or the cost, or combinations of these. For instance, if only a single lot is available to match against, the user should be able to specify the cost as \u201c {} \u201d, telling the system to book at cost, but essentially saying: \u201cbook against any lot.\u201d This should trigger an error only if there are multiple lots. A variety of informations should be supported to specify an existing lot, and they should be combinable: The cost of acquisition of the position; The date the position was acquired at; A user-provided label. Cases where insufficient information is provided for explicit lot selection is available should admit for a default booking method to be invoked. The user should be able to specify globally and per-account what the default booking method should be. This paves the way for implicit lot selection . A degenerate method should be available that kicks off an error and requires the user to be explicit. Average cost booking needs to be supported, as it is quite common in retirement or tax-sheltered accounts. This is the default method used in Canadian investment accounts. Cost basis adjustments need to be supported as well. The problem should be able to be specified by providing a specific lot (or all of a commodity\u2019s lots at average cost) and a dollar amount to adjust the position by. Stock splits need to be able to maintain some of the original attributes of the position , specifically, the original trade date and the user-specified label, if one has been provided. This should allow a common syntax to specify an original trading lot when reducing a position after a split. Reducing a position needs to always book against an existing lot . I\u2019m preserving the Beancount behavior here, which I\u2019ve argued for previously, as it works quite well and is a great method to avoid data entry mistakes. This naturally defines an invariant for inventories: all positions of a particular commodity held at cost should be of the same sign in one inventory (this can be used as a sanity check). Bookings that change the sign of a number of units should raise an error , unless an explicit exception is requested (and I\u2019m not even convinced that we need it). Note again that bookings only occur for units held at cost, so currency conversions are unaffected by this requirement. Beancount has had this feature for a while, and it has proved useful to detect errors. For example, if an inventory has a position of 8 HOOL {500 USD} you attempt to post a change of -10 units to this lot, the resulting number of units is now negative: -2. This should indicate user error. The only use case I can think for allowing this is the trading of futures spreads and currencies, which would naturally be reported in the same account (a short position in currencies is not regarded as a different instrument in the same way that a short position would); this is the only reason to provide an exception, and I suspect that 99% of users will not need it.]","title":"Requirements"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#debugging-tools","text":"Since this is a non-trivial but greatly important part of the process of entering trading data, we should provide tools that list in detail the running balance for an inventory, including all of the detail of its lots. Booking errors being reported should include sufficient context, that is: The transaction with offending posting The offending posting The current state of the inventory before the posting is applied, with all its lots The implicit booking method that is in effect to disambiguate between lots A detailed reason why the booking failed","title":"Debugging Tools"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-vs-implicit-booking-reduction","text":"Another question that may come up in the design of a new booking method is whether we require the user to be explicit about whether he thinks this is an addition to a position, or a reduction. This could be done, for instance, by requiring a slightly different syntax for the cost, for example \u201c {} \u201d would indicate an addition and \u201c [] \u201d a reduction. I\u2019m not convinced that it is necessary to make that distinction, maybe the extra burden on the user is not worth it, but it might be a way to cross-check an expectation against the actual calculations that occur. I\u2019ll drop the idea for now.","title":"Explicit vs. Implicit Booking Reduction"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#design-proposal","text":"This section presents a concrete description of a design that fulfills the previously introduced requirements. We hope to keep this as simple as possible.","title":"Design Proposal"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inventory","text":"An inventory is simply a list of lot descriptors. Each inventory lot will be defined by: UNITS, (CURRENCY, COST-UNITS, COST-CURRENCY, DATE, LABEL) Fields: UNITS: the number of units of that lot that are held CURRENCY: the type of commodity held, a string COST-UNITS: the number in the price of that lot COST-CURRENCY: the pricing commodity of the cost DATE: the acquisition date of the lot LABEL: an arbitrary user-specified string used to identify this lot If this represents a lot held at cost, after this processing, only the LABEL field should be optionally with a NULL value. Anyone writing code against this inventory could expect that all other values are filled in with non-NULL values (or are otherwise all NULL).","title":"Inventory"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#input-syntax-filtering","text":"The input syntax should allow the specification of cost as any combination of the following informations: The cost per unit , as an amount, such as \u201c 500 USD \u201d A total cost , to be automatically divided by the number of units, like this: {... +9.95 USD} . You should be able to use either cost per unit, total cost, or even combine the two, like this: {500 + 9.95 USD} . This is useful to enter commissions. This syntax also replaces the previous {{ ... }} syntax. The lot-date , as a YYYY-MM-DD date, such as \u201c 2014-06-20 \u201d A label , which is any non-numerical identifier, such as first-apple or some random uuid like \u201c aa2ba9695cc7 \u201d A special marker \u201c * \u201d that indicates to book at the average cost of the inventory balance These following postings should all be valid syntax: ... Assets:Investments:Stock 10 HOOL {500 USD} ; cost Assets:Investments:Stock 10 HOOL {339999615d7a} ; label Assets:Investments:Stock 10 HOOL {2014-05-01} ; lot-date Assets:Investments:Stock 10 HOOL {} ; no information ... Combinations Assets:Investments:Stock 10 HOOL {500 USD, 2014-05-01} Assets:Investments:Stock 10 HOOL {2014-05-01, 339999615d7a}","title":"Input Syntax & Filtering"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#algorithm","text":"All postings should be processed in a separate step after parsing, in the order of their date, against a running inventory balance for each account. The cost amount should become fully determined at this stage, and if we fail to resolve a cost, an error should be raised and the transaction deleted from the flow of directives (after the program loudly complaining about it). When processing an entry, we should match and filter all inventory lots against all the filters that are provided by the user. In general, after filtering the lots with the user restrictions: If the set of lots is empty, an error should be raised; If there is a single lot, this lot should be selected (success case); If there are multiple lots, the default implicitly booking method for the corresponding account should be invoked.","title":"Algorithm"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#implicit-booking-methods","text":"If there are multiple matching lots to choose from during booking, the following implicit booking methods could be invoked: STRICT: Select the only lot of the inventory. If there are more than one lots, raise an error. FIFO: Select the lot with the earliest date. LIFO: Select the lot with the latest date. AVERAGE: Book this transaction at average cost. AVERAGE_ONLY: Like AVERAGE but also trigger an aggregation on an addition to a position. This can be used to enforce that the only booking method for all lots is at the average cost. NONE: Don\u2019t perform any inventory booking on this account. Allow a mix of lots for the same commodity or positive and negative numbers in the inventory. (This essentially devolves to the Ledger method of booking.) This method would only get invoked if disambiguation between multiple lots is required, after filtering the lots against the expression provided by the user. The STRICT method is basically the degenerate disambiguation which issues an error if there is any ambiguity and should be the default. There should be a default method that applies to all accounts. The default value should be overridable. In Beancount, we would add a new \u201coption\u201d to allow the user to change this: option \"booking_method\" \"FIFO\" The default value used in a particular account should be specifiable as well, because it is a common case that the method varies by account, and is fixed within the account. In Beancount, this would probably become part of the open directive, something like this: 2003-02-17 open Assets:Ameritrade:HOOL HOOL booking:FIFO (I\u2019m not sure about the syntax.)","title":"Implicit Booking Methods"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#resolving-same-date-ambiguity","text":"If the automatic booking method gets invoked to resolve an ambiguous lot reduction, e.g. FIFO, if there are multiple lots at the same date, something needs to be done to resolve which of the lots is to be chosen. The line number at which the transaction appears should be selected. For example, in the following case, a WIDGET Of 8 GBP would be selected: 2014-10-15 * \"buy widgets\" Assets:Inventory 10 WIDGET {} ;; Price inferred to 8 GBP/widget Assets:Cash -80 GBP 2014-10-15 * \"buy another widget\" Assets:Inventory 1 WIDGET {} ;; Price inferred to 9 GBP/widget Assets:Cash -9 GBP 2014-10-16 * \"sell a widget\" Assets:Cash 11 GBP Assets:Inventory -1 WIDGET {} ;; Ambiguous lot","title":"Resolving Same Date Ambiguity"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#dates-inserted-by-default","text":"By default, if an explicit date is not specified in a cost, the date of the transaction that holds the posting should be attached to the trading lot automatically. This date is overridable so that stock splits may be implemented by a transformation to a transaction like this: 2014-01-04 * \"Buy some HOOL\" Assets:Investments:Stock 10 HOOL {1000.00 USD} Assets:Investments:Cash 2014-04-17 * \"2:1 split into Class A and Class B shares\" Assets:Investments:Stock -10 HOOL {1000.00 USD, 2014-01-04} ; reducing Assets:Investments:Stock 10 HOOL {500.00 USD, 2014-01-04} ; augment Assets:Investments:Stock 10 HOOLL {500.00 USD, 2014-01-04} ; augment","title":"Dates Inserted by Default"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#matching-with-no-information","text":"Supplying no information for the cost should be supported and is sometimes useful: in the case of augmenting a position, if all the other legs have values specified, we should be able to automatically infer the cost of the units being traded: 2012-05-01 * \"First trade\" Assets:Investments:Stock 10 HOOL {} Assets:Investments:Cash -5009.95 USD Expenses:Commissions 9.95 USD In the case of reducing a position\u2014and we can figure that out whether that is the case by looking at the inventory at the point of applying the transaction to its account balance\u2014an empty specification should trigger the default booking method. If the method is STRICT, for instance, this would select a lot only if there is a single lot available (the choice is unambiguous), which is probably a common case if one trades infrequently. Other methods will apply as they are defined. Note that the user still has to specify a cost of \u201c {} \u201d in order to inform us that this posting has to be considered \u201cheld at cost.\u201d This is important to disambiguate from a price conversion with no associated cost.","title":"Matching with No Information"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#reducing-multiple-lots","text":"If a single trade needs to close multiple existing lots of an inventory, this can dealt with trivially by inserting one posting for each lot. I think this is a totally reasonable requirement. This could represent a single trade, for instance, if your brokerage allows it: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -10 HOOL {500 USD} Assets:Investments:Stock -12 HOOL {510 USD} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains Note: If the cost basis specification for the lot matches multiple lots of the inventory and the result is unambiguous, the lots will be correctly selected. For example, if the ante-inventory contains just those two lots (22 HOOL in total), you should be able to have a single reducing posting like this: 2012-05-01 * \"Closing my position\" Assets:Investments:Stock -22 HOOL {} Assets:Investments:Cash 12000.00 USD Income:Investments:Gains and they will both be included. On the other hand, if the result is ambiguous (for example, if you have more than these two lots) the booking strategy for that account would be invoked. By default, this strategy will be \"STRICT\" which will generate an error, but if this account's strategy is set to \"FIFO\" (or is not set and the default global strategy is \"FIFO\"), the FIFO lots would be automatically selected for you, as per your wish.","title":"Reducing Multiple Lots"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#lot-basis-modification","text":"Another idea is to support the modification of a specific lot\u2019s cost basis in a single leg. The way this could work, is by specifying the number of units to modify, and the \u201c+ number currency\u201d syntax would be used to adjust the cost basis by a specific number, like this: 2012-05-01 * \"Adjust cost basis by 250 USD for 5 of the 500 USD units\" Assets:Investments:Stock ~5 HOOL {500 + 250 USD} If the number of units is smaller than the total number of units in the lot, split out the lot before applying the cost basis adjustment. I\u2019m not certain I like this. Not specifying the size could also adjust the entire position (I\u2019m not sure if this makes sense as it departs from the current syntax significantly): 2012-05-01 * \"Adjust cost basis by 250 USD for the 500 USD units\" Assets:Investments:Stock HOOL {500 + 250 USD} In any case, all the fields other than the total cost adjustment would be used to select which lot to adjust.","title":"Lot Basis Modification"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#tag-reuse","text":"We will have to be careful in the inventory booking to warn on reuse of lot labels. Labels should be unique.","title":"Tag Reuse"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#examples","text":"Nothing speaks more clearly than concrete examples. If otherwise unspecified, we are assuming that the booking method on the Assets:Investments:Stock account is STRICT .","title":"Examples"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#no-conflict","text":"Given the following inventory lots: 21, (HOOL, 500, USD, 2012-05-01, null) 22, (AAPL, 380, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {} ... This booking should fail (because the amount is not one of the valid lots): 2013-05-01 * Assets:Investments:Stock -10 HOOL {520 USD} ... This one too should fail (no currency matching lot): 2013-05-01 * Assets:Investments:Stock -10 MSFT {80 USD} ... So should this one (invalid date when a date is specified): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2010-01-01} ...","title":"No Conflict"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-cost","text":"Given the following inventory: 21, (HOOL, 500, USD, 2012-05-01, null) 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 25, (HOOL, 510, USD, 2012-06-01, null) This booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {510 USD} ... This booking should fail if the stock account\u2019s method is STRICT (ambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ... This booking should succeed if the stock account\u2019s method is FIFO, booking against the lot at 2012-05-01 (earliest): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD} ...","title":"Explicit Selection By Cost"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-date","text":"Given the same inventory as previously, this booking should succeed: 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-05-01} ... This booking should fail if the method is STRICT (ambiguous) 2013-05-01 * Assets:Investments:Stock -10 HOOL {2012-06-01} ...","title":"Explicit Selection By Date"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-label","text":"This booking should succeed, because there is a single lot with the \u201cabc\u201c label: 2013-05-01 * Assets:Investments:Stock -10 HOOL {abc} ... If multiple lots have the same label, ambiguous cases may occur; with this inventory, for example: 32, (HOOL, 500, USD, 2012-06-01, \u201cabc\u201d) 31, (HOOL, 510, USD, 2012-07-01, \u201cabc\u201d) The same booking should fail.","title":"Explicit Selection By Label"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#explicit-selection-by-combination","text":"With the initial inventory, this booking should succeed (unambiguous): 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} ... There is only one lot at a cost of 500 USD and at an acquisition date of 2012-06-01.","title":"Explicit Selection By Combination"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#not-enough-units","text":"The following booking would be unambiguous, but would fail, because there aren\u2019t enough units of the lot in the inventory to subtract from: 2013-05-01 * Assets:Investments:Stock -33 HOOL {500 USD, 2012-06-01} \u2026","title":"Not Enough Units"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#redundant-selection-of-same-lot","text":"If two separate postings select the same inventory lot in one transaction, it should be able to work: 2013-05-01 * Assets:Investments:Stock -10 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -10 HOOL {abc} ... Of course, if there are not enough shares, it should fail: 2013-05-01 * Assets:Investments:Stock -20 HOOL {500 USD, 2012-06-01} Assets:Investments:Stock -20 HOOL {abc} ...","title":"Redundant Selection of Same Lot"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#automatic-price-extrapolation","text":"If all the postings of a transaction can have their balance computed, we allow a single price to be automatically calculated - this should work: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {} Income:US:Invest:Gains -340.51 USD And result in the equivalent of: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {534.51 USD} Income:US:Invest:Gains -340.51 USD Of course, preserving the original date of the lot should work too, so this should work as well: 2014-03-15 * \"Adjust cost basis from 500 USD to 510 USD\" Assets:US:Invest:HOOL -10.00 HOOL {500.00 USD} Assets:US:Invest:HOOL 10.00 HOOL {2014-02-04} Income:US:Invest:Gains -340.51 USD","title":"Automatic Price Extrapolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#average-cost-booking","text":"Augmenting lots with the average cost syntax should fail: 2014-03-15 * \"Buying at average cost, what does this mean?\" Assets:US:Invest:Stock 10.00 HOOL {*} Income:US:Invest:Gains -5000.00 USD Reducing should work, this is the main use case: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {510.00 USD} Assets:US:Invest:Cash -5100.00 USD 2014-04-28 * \"Obtaining a dividend in stock\" Assets:US:Invest:Stock 1.00 HOOL {520.00 USD} Income:US:Invest:Dividends -520.00 USD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains ; Automatically calculated: -194.29 USD The effect would be to aggregate the 10 HOOL {500.00 USD} lot, the 10 HOOL {510.00 USD} lot, and the 1.00 HOOL {520.00 USD} lot, together into a single lot of 21 HOOL {505.714285 USD}, and to remove 8 HOOL from this lot, which is 8 HOOL x 505.714285 USD/HOOL = 4045.71 USD. We receive 4240.00 USD, which allows us to automatically compute the capital gain: 4240.00 - 4045.71 = 194.29 USD. After the reduction, a single lot of 13 HOOL {505.714285 USD} remains. It should also work if we have multiple currencies in the account, that is adding the following transaction to the above should not make it faile: 2014-04-15 * \"Buying another stock\" Assets:US:Invest:Stock 15.00 AAPL {300.00 USD} Assets:US:Invest:Cash -4500.00 USD However it should fail if we have the same currency priced in distinct cost currencies in the same account: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD} Assert:US:Invest:Cash -5000.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 10.00 HOOL {623.00 CAD} Assets:US:Invest:Cash -62300.00 CAD 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {*} ; Which HOOL, USD or CAD? Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains But of course, this never happens in practice, so I\u2019m not too concerned. We could potentially support specifying the cost-currency to resolve this case, that is, replacing the sell leg with this: 2014-05-20 * \"Sell some stock at average cost\" Assets:US:Invest:Stock -8.00 HOOL {* USD} Assets:US:Invest:Cash 4240.00 USD Income:US:Invest:Gains I\u2019m quite sure it wouldn\u2019t be useful, but we could go the extra mile and be as general as we can possibly be.","title":"Average Cost Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#future-work","text":"This proposal does not yet deal with cost basis re-adjustments! We need to way to be able to add or remove a fixed dollar amount to a position\u2019s cost basis, that is, from (1) a lot identifier and (2) a cost-currency amount, we should be able to update the cost of that lot. The transaction still has to balance. Here are some ideas what this might look like: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {500.00 USD + 230.00 USD} Income:US:Invest:Gains -230.00 USD The resulting inventory would be 10 HOOL at 523.00 USD per share, and the rest of the original lots, less 10 HOOL at 500.00 USD per share (there might be some of that remaining, that\u2019s fine). This is the equivalent of 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500.00 USD} Assets:US:Invest:Stock 10.00 HOOL {523.00 USD} Income:US:Invest:Gains -230.00 USD except you did not have to carry out the calculation. This should therefore be implemented as a transformation. However, this is not super useful, because this is already supported... adjustments are usually performed on lots at average cost, and this is where the problem lie: you\u2019d have to make the calculation yourself! Here\u2019s a relevant use case: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD This would first average all the lots of HOOL together and recompute the cost basis with the new amount. This should work: 2014-03-15 * \"Buying a first lot\" Assets:US:Invest:Stock 5.00 HOOL {500.00 USD} Assert:US:Invest:Cash -2500.00 USD 2014-04-15 * \"Buying a second lot\" Assets:US:Invest:Stock 5.00 HOOL {520.00 USD} Assets:US:Invest:Cash -2600.00 USD 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock 10.00 HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Notice how we still have to specify an amount of HOOL shares to be repriced. I like this, but I\u2019m wondering if we should allow specifying \u201call the units\u201d like this (this would be a more radical change to the syntax): 2014-05-20 * \"Adjust cost basis of the Hooli units\" Assets:US:Invest:Stock * HOOL {* + 230.00 USD} Income:US:Invest:Gains -230.00 USD Perhaps the best way to auto-compute cost basis adjustments would be via powerful interpolation, like this: 2014-05-20 * \"Adjust cost basis of a specific lot\" Assets:US:Invest:Stock -10.00 HOOL {500 USD} ; reduce Assets:US:Invest:Stock 10.00 HOOL {} ; augment, interpolated Income:US:Invest:Gains -230.00 USD See the Smarter Elision document for more details on a proposal.","title":"Future Work"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#inter-account-booking","text":"Some tax laws require a user to book according to a specific method, and this might apply between all accounts. This means that some sort of transfer needs to be applied in order to handle this correctly. See the separate document for detail. TODO - complete this with more tests for average cost booking!","title":"Inter-Account Booking"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#implementation-notes","text":"","title":"Implementation Notes"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#separate-parsing-interpolation","text":"In order to implement a syntax for reduction that does not specify the cost, we need to make changes to the parser. Because there may be no costs on the posting, it becomes impossible to perform balance checks at parsing time. Therefore, we will need to postpone balance checks to a stage after parsing. This is reasonable and in a way nice: it is best if the Beancount parser does not output many complex errors at that stage. A new \u201cpost-parse\u201d stage will be added, right before running the plugins.","title":"Separate Parsing & Interpolation"},{"location":"a_proposal_for_an_improvement_on_inventory_booking.html#conclusion","text":"This document is work-in-progress. I\u2019d really love to get some feedback in order to improve the suggested method, which is why I put this down in words instead of just writing the code. I think a better semantic can be reached by iterating on this design to produce something that works better in all systems, and this can be used as documentation later on for developers to understand why it is designed this way. Your feedback is much appreciated.","title":"Conclusion"},{"location":"balance_assertions_in_beancount.html","text":"Balance Assertions in Beancount \uf0c1 Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-balance This document summarizes the different kinds of semantics for balance assertions in all command-line accounting systems and proposes new syntax for total and file-order running assertions in Beancount. Motivation Partial vs. Complete Assertions File vs. Date Assertions Ordering & Ambiguity Intra-Day Assertions Beginning vs. End of Day Status Proposal File Assertions Complete Assertions Motivation \uf0c1 Both Beancount and Ledger implement balance assertions. These provide the system with checkpoints it can use to verify the integrity of the data entry 1 . Traditional accounting principles state that a user may never change the past\u2014correcting the past involves inserting new entries to undo past mistakes 2 \u2014but we in the command-line accounting community take issue with that: we want to remain able to reconstruct the past and more importantly, to correct past mistakes at the site of their original data entry. Yes, we are dilettantes but we are bent on simplifying the process of bookkeeping by challenging existing concepts and think that this is perfectly reasonable, as long as it does not break known balances. These known balances are what we provide via balance assertions. Another way to look at these balance assertions, is that they are simply the bottom line amounts reported on various account statements, as exemplified in the figure below. Following this thread , we established that there were differing interpretations of balance assertions in the current versions of Ledger (3.0) and Beancount (2.0b). Partial vs. Complete Assertions \uf0c1 An assertion in Beancount currently looks like this: 2012-02-04 balance Assets:CA:Bank:Checking 417.61 CAD The meaning of this directive is: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains exactly 417.61 units of CAD at the end of February 3, 2012\u201d (so it\u2019s dated on the 4th because in Beancount balance assertions are specified at the beginning of the date). It says nothing about other commodities in the account\u2019s inventory. For example, if the account contains units of \u201cUSD\u201d, those are unchecked. We will call this interpretation a partial balance assertion or single-commodity assertion . An alternative assertion would be exhaustive: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains only 417.61 units of CAD at the end of February 3, 2012.\u201d We call this a complete balance assertion . In order to work, this second type of assertion would have to support the specification of the full contents of an inventory. This is currently not supported. Further note that we are not asserting the cost basis of an inventory, just the number of units. File vs. Date Assertions \uf0c1 There are two differing interpretations and implementations of the running balances for assertions: Beancount first sorts all the directives and verifies the balance at the beginning of the date of the directive. In the previous example, that is \u201c the balance before any transactions on 2012-02-04 are applied.\u201d We will call this date assertions or date-based assertions . Ledger keeps a running balance of each account\u2019s inventory during its parsing phase and performs the check at the site of the assertion in the file. We will call this file assertions or file-order, or file-based assertions . This kind of assertion has no date associated with it (this is slightly misleading in Ledger because of the way assertions are specified, as attached to a transaction\u2019s posting, which appears to imply that they occur on the transaction date, but they don\u2019t, they strictly apply to the file location). Ordering & Ambiguity \uf0c1 An important difference between those two types of assertions is that file-based assertions are not order-independent. For example, take the following input file: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 If you move the credit card payment up to the credit card account section, the same set of transactions fails: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary This subtle problem could be difficult for beginners to understand. Moving the file assertion to its own, undated directive might be more indicative syntax of its semantics, something that would look like this: balance Assets:Checking = $3008.67 The absence of a date indicates that the check is not applied at a particular point in time. Intra-Day Assertions \uf0c1 On the other hand, date-based assertions, because of their order-independence, preclude intra-day assertions, that is, a balance assertion that occurs between two postings on the same account during the same day. Beancount does not support intra-day assertions at the moment. Note despite this shortcoming, this has not been much of a problem, because it is often possible to fudge the date where necessary by adding or removing a day, or even just skipping an assertion where it would be impossible (skipping assertions is not a big deal, as they are optional and their purpose is just to provide certainty. As long as you have one that appears some date after the skipped one, it isn\u2019t much of an issue). It would be nice to find a solution to intra-day assertions for date-based assertions, however. One interesting idea would be to extend the semantics to apply the balance in file order within the set of all transactions directly before and after the balance check that occur on the same date as the balance check, for example, this would balance: 2013-05-05 balance Assets:Checking 100 USD 2013-05-20 * \"Interest payment\" Assets:Checking 12.01 USD Income:Interest 2013-05-20 balance Assets:Checking 112.01 USD 2013-05-20 * \"Check deposit\" Assets:Checking 731.73 USD Assets:Receivable The spell would be broken as soon as a directive would appear at a different date. Another idea would be to always sort the balance assertions in file-order as the second sort key (after the date) and apply them as such. I\u2019m not sure this would be easy to understand though. Beginning vs. End of Day \uf0c1 Finally, just for completeness, it is worth mentioning that date assertions have to have well-defined semantics regarding when they apply during the day. In Beancount, they currently apply at the beginning of the day. It might be worthwhile to provide an alternate version of date-based assertions that applies at the end of the day, e.g. \u201cbalance_end\u201d. Beancount v1 used to have this (\u201ccheck_end\u201d) but it was removed in the v2 rewrite, as it wasn\u2019t clear it would be really needed. The simplicity of a single meaning for balance assertions is nice too. Status \uf0c1 Ledger 3.0 currently supports only partial file-order assertions, on transactions. Beancount 2.0 currently supports only partial date-based assertions at the beginning of the day. Proposal \uf0c1 I propose the following improvements to Beancount\u2019s balance assertions. File Assertions \uf0c1 File assertions should be provided as a plugin. They would look like this: 2012-02-03 file_balance Assets:CA:Bank:Checking 417.61 CAD Ideally, in order to make it clear that they apply strictly in file order, they would not have a date, something like this: file_balance Assets:CA:Bank:Checking 417.61 CAD But this breaks the regularity of the syntax for all other directives. It also complicates an otherwise very regular and simple parser just that much more\u2026 all other directives begin with a date and a word, and all other lines are pretty much ignored. It would be a bit of a departure from this. Finally, it would still be nice to have a date just to insert those directives somewhere in the rendered journal. So I\u2019m considering keeping a date for it. If you decide to use those odd assertions, you should know what they mean. I also don\u2019t like the idea of attaching assertions to transactions; transaction syntax is already busy enough, it is calling to remain simple. This should be a standalone directive that few people use. In order to implement this, the plugin would simply resort all the directives according to their file location only (using the fileloc attribute of entries), disregarding the date, and recompute the running balances top to bottom while applying the checks. This can entirely be done via post-processing, like date-based assertions, without disturbing any of the other processing. Moreover, another advantage of doing this in a plugin is that people who don\u2019t use this directive won\u2019t have to pay the cost of calculating these inventories. Complete Assertions \uf0c1 Complete assertions should be supported in Beancount by the current balance assertion directive. They aren\u2019t very important but are potentially useful. Possible syntax ideas: 2012-02-03 balance* Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking = 417.61 CAD, 162 USD 2012-02-03 balance full Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking 417.61 CAD Assets:CA:Bank:Checking 162 USD I\u2019m still undecided which is best. So far it seems a matter of taste. As far as we know, the notion of inputting an explicit expected amount is unique to command-line accounting systems. Other systems \u201creconcile\u201d by freezing changes in the past. \u21a9 There are multiple reasons for this. First, in pre-computer times, accounting was done using books, and recomputing running balances manually would have involved making multiple annoying corrections to a book. This must have been incredibly inconvenient, and inserting correcting entries at the current time is a lot easier. Secondly, if your accounting balances are used to file taxes, changing some of the balances retroactively makes it difficult to go back and check the detail of reported amounts in case of an audit. This problem also applies to our context, but whether a past correction should be allowed is a choice that depends on the context and the particular account, and we leave it up to the user to decide whether it should be allowed. \u21a9","title":"Balance Assertions in Beancount"},{"location":"balance_assertions_in_beancount.html#balance-assertions-in-beancount","text":"Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-balance This document summarizes the different kinds of semantics for balance assertions in all command-line accounting systems and proposes new syntax for total and file-order running assertions in Beancount. Motivation Partial vs. Complete Assertions File vs. Date Assertions Ordering & Ambiguity Intra-Day Assertions Beginning vs. End of Day Status Proposal File Assertions Complete Assertions","title":"Balance Assertions in Beancount"},{"location":"balance_assertions_in_beancount.html#motivation","text":"Both Beancount and Ledger implement balance assertions. These provide the system with checkpoints it can use to verify the integrity of the data entry 1 . Traditional accounting principles state that a user may never change the past\u2014correcting the past involves inserting new entries to undo past mistakes 2 \u2014but we in the command-line accounting community take issue with that: we want to remain able to reconstruct the past and more importantly, to correct past mistakes at the site of their original data entry. Yes, we are dilettantes but we are bent on simplifying the process of bookkeeping by challenging existing concepts and think that this is perfectly reasonable, as long as it does not break known balances. These known balances are what we provide via balance assertions. Another way to look at these balance assertions, is that they are simply the bottom line amounts reported on various account statements, as exemplified in the figure below. Following this thread , we established that there were differing interpretations of balance assertions in the current versions of Ledger (3.0) and Beancount (2.0b).","title":"Motivation"},{"location":"balance_assertions_in_beancount.html#partial-vs-complete-assertions","text":"An assertion in Beancount currently looks like this: 2012-02-04 balance Assets:CA:Bank:Checking 417.61 CAD The meaning of this directive is: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains exactly 417.61 units of CAD at the end of February 3, 2012\u201d (so it\u2019s dated on the 4th because in Beancount balance assertions are specified at the beginning of the date). It says nothing about other commodities in the account\u2019s inventory. For example, if the account contains units of \u201cUSD\u201d, those are unchecked. We will call this interpretation a partial balance assertion or single-commodity assertion . An alternative assertion would be exhaustive: \u201cplease assert that the inventory of account \u2018Assets:CA:Bank:Checking\u2019 contains only 417.61 units of CAD at the end of February 3, 2012.\u201d We call this a complete balance assertion . In order to work, this second type of assertion would have to support the specification of the full contents of an inventory. This is currently not supported. Further note that we are not asserting the cost basis of an inventory, just the number of units.","title":"Partial vs. Complete Assertions"},{"location":"balance_assertions_in_beancount.html#file-vs-date-assertions","text":"There are two differing interpretations and implementations of the running balances for assertions: Beancount first sorts all the directives and verifies the balance at the beginning of the date of the directive. In the previous example, that is \u201c the balance before any transactions on 2012-02-04 are applied.\u201d We will call this date assertions or date-based assertions . Ledger keeps a running balance of each account\u2019s inventory during its parsing phase and performs the check at the site of the assertion in the file. We will call this file assertions or file-order, or file-based assertions . This kind of assertion has no date associated with it (this is slightly misleading in Ledger because of the way assertions are specified, as attached to a transaction\u2019s posting, which appears to imply that they occur on the transaction date, but they don\u2019t, they strictly apply to the file location).","title":"File vs. Date Assertions"},{"location":"balance_assertions_in_beancount.html#ordering-ambiguity","text":"An important difference between those two types of assertions is that file-based assertions are not order-independent. For example, take the following input file: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 If you move the credit card payment up to the credit card account section, the same set of transactions fails: ;; Credit card account 2014/05/01 opening Liabilities:CreditCard $-1000.00 Expenses:Opening-Balances 2014/05/12 dinner Liabilities:CreditCard $-74.20 Expenses:Restaurant 2014/06/05 cc payment Assets:Checking $-1074.20 = $3008.67 Liabilities:CreditCard = $0 ;; Checking account 2014/06/05 salary Assets:Checking $4082.87 Income:Salary This subtle problem could be difficult for beginners to understand. Moving the file assertion to its own, undated directive might be more indicative syntax of its semantics, something that would look like this: balance Assets:Checking = $3008.67 The absence of a date indicates that the check is not applied at a particular point in time.","title":"Ordering & Ambiguity"},{"location":"balance_assertions_in_beancount.html#intra-day-assertions","text":"On the other hand, date-based assertions, because of their order-independence, preclude intra-day assertions, that is, a balance assertion that occurs between two postings on the same account during the same day. Beancount does not support intra-day assertions at the moment. Note despite this shortcoming, this has not been much of a problem, because it is often possible to fudge the date where necessary by adding or removing a day, or even just skipping an assertion where it would be impossible (skipping assertions is not a big deal, as they are optional and their purpose is just to provide certainty. As long as you have one that appears some date after the skipped one, it isn\u2019t much of an issue). It would be nice to find a solution to intra-day assertions for date-based assertions, however. One interesting idea would be to extend the semantics to apply the balance in file order within the set of all transactions directly before and after the balance check that occur on the same date as the balance check, for example, this would balance: 2013-05-05 balance Assets:Checking 100 USD 2013-05-20 * \"Interest payment\" Assets:Checking 12.01 USD Income:Interest 2013-05-20 balance Assets:Checking 112.01 USD 2013-05-20 * \"Check deposit\" Assets:Checking 731.73 USD Assets:Receivable The spell would be broken as soon as a directive would appear at a different date. Another idea would be to always sort the balance assertions in file-order as the second sort key (after the date) and apply them as such. I\u2019m not sure this would be easy to understand though.","title":"Intra-Day Assertions"},{"location":"balance_assertions_in_beancount.html#beginning-vs-end-of-day","text":"Finally, just for completeness, it is worth mentioning that date assertions have to have well-defined semantics regarding when they apply during the day. In Beancount, they currently apply at the beginning of the day. It might be worthwhile to provide an alternate version of date-based assertions that applies at the end of the day, e.g. \u201cbalance_end\u201d. Beancount v1 used to have this (\u201ccheck_end\u201d) but it was removed in the v2 rewrite, as it wasn\u2019t clear it would be really needed. The simplicity of a single meaning for balance assertions is nice too.","title":"Beginning vs. End of Day"},{"location":"balance_assertions_in_beancount.html#status","text":"Ledger 3.0 currently supports only partial file-order assertions, on transactions. Beancount 2.0 currently supports only partial date-based assertions at the beginning of the day.","title":"Status"},{"location":"balance_assertions_in_beancount.html#proposal","text":"I propose the following improvements to Beancount\u2019s balance assertions.","title":"Proposal"},{"location":"balance_assertions_in_beancount.html#file-assertions","text":"File assertions should be provided as a plugin. They would look like this: 2012-02-03 file_balance Assets:CA:Bank:Checking 417.61 CAD Ideally, in order to make it clear that they apply strictly in file order, they would not have a date, something like this: file_balance Assets:CA:Bank:Checking 417.61 CAD But this breaks the regularity of the syntax for all other directives. It also complicates an otherwise very regular and simple parser just that much more\u2026 all other directives begin with a date and a word, and all other lines are pretty much ignored. It would be a bit of a departure from this. Finally, it would still be nice to have a date just to insert those directives somewhere in the rendered journal. So I\u2019m considering keeping a date for it. If you decide to use those odd assertions, you should know what they mean. I also don\u2019t like the idea of attaching assertions to transactions; transaction syntax is already busy enough, it is calling to remain simple. This should be a standalone directive that few people use. In order to implement this, the plugin would simply resort all the directives according to their file location only (using the fileloc attribute of entries), disregarding the date, and recompute the running balances top to bottom while applying the checks. This can entirely be done via post-processing, like date-based assertions, without disturbing any of the other processing. Moreover, another advantage of doing this in a plugin is that people who don\u2019t use this directive won\u2019t have to pay the cost of calculating these inventories.","title":"File Assertions"},{"location":"balance_assertions_in_beancount.html#complete-assertions","text":"Complete assertions should be supported in Beancount by the current balance assertion directive. They aren\u2019t very important but are potentially useful. Possible syntax ideas: 2012-02-03 balance* Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking = 417.61 CAD, 162 USD 2012-02-03 balance full Assets:CA:Bank:Checking 417.61 CAD, 162 USD 2012-02-03 balance Assets:CA:Bank:Checking 417.61 CAD Assets:CA:Bank:Checking 162 USD I\u2019m still undecided which is best. So far it seems a matter of taste. As far as we know, the notion of inputting an explicit expected amount is unique to command-line accounting systems. Other systems \u201creconcile\u201d by freezing changes in the past. \u21a9 There are multiple reasons for this. First, in pre-computer times, accounting was done using books, and recomputing running balances manually would have involved making multiple annoying corrections to a book. This must have been incredibly inconvenient, and inserting correcting entries at the current time is a lot easier. Secondly, if your accounting balances are used to file taxes, changing some of the balances retroactively makes it difficult to go back and check the detail of reported amounts in case of an audit. This problem also applies to our context, but whether a past correction should be allowed is a choice that depends on the context and the particular account, and we leave it up to the user to decide whether it should be allowed. \u21a9","title":"Complete Assertions"},{"location":"beancount_cheat_sheet.html","text":"Beancount Syntax Cheat Sheet \uf0c1 Example Account Name: Assets:US:BofA:Checking Account Types Assets Liabilities Income Expenses Equity + - - + - Commodities All in CAPS: USD, EUR, CAD, AUD GOOG, AAPL, RBF1005 HOME_MAYST, AIRMILES HOURS Directives General syntax: YYYY-MM-DD Opening & Closing Accounts 2001-05-29 open Expenses:Restaurant 2001-05-29 open Assets:Checking USD,EUR ; Currency constraints 2015-04-23 close Assets:Checking Declaring Commodities This is optional; use this only if you want to attach metadata by currency. 1998-07-22 commodity AAPL name: \"Apple Computer Inc.\" Prices Use many times to fill historical price database: 2015-04-30 price AAPL 125.15 USD 2015-05-30 price AAPL 130.28 USD Notes 2013-03-20 note Assets:Checking \"Called to ask about rebate\" Documents 2013-03-20 document Assets:Checking \"path/to/statement.pdf\" Transactions 2015-05-30 * \"Some narration about this transaction\" Liabilities:CreditCard -101.23 USD Expenses:Restaurant 101.23 USD 2015-05-30 ! \"Cable Co\" \"Phone Bill\" #tag \u02c6link id: \"TW378743437\" ; Meta-data Expenses:Home:Phone 87.45 USD Assets:Checking ; You may leave one amount out Postings ... 123.45 USD Simple ... 10 GOOG {502.12 USD} With per-unit cost ... 10 GOOG {{5021.20 USD}} With total cost ... 10 GOOG {502.12 # 9.95 USD} With both costs ... 1000.00 USD @ 1.10 CAD With per-unit price ... 10 GOOG {502.12 USD} @ 1.10 CAD With cost & price ... 10 GOOG {502.12 USD, 2014-05-12 } With date ! ... 123.45 USD ... With flag Balance Assertions and Padding Asserts the amount for only the given currency: 2015-06-01 balance Liabilities:CreditCard -634.30 USD Automatic insertion of transaction to fulfill the following assertion: YYYY-MM-DD pad Assets:Checking Equity:Opening-Balances Events YYYY-MM-DD event \"location\" \"New York, USA\" YYYY-MM-DD event \"address\" \"123 May Street\" Options option \"title\" \"My Personal Ledger\" See this doc for the full list of supported options. Other pushtag #trip-to-peru ... poptag #trip-to-peru ; Comments begin with a semi-colon","title":"Beancount Cheat Sheet"},{"location":"beancount_cheat_sheet.html#beancount-syntax-cheat-sheet","text":"Example Account Name: Assets:US:BofA:Checking","title":"Beancount Syntax Cheat Sheet"},{"location":"beancount_design_doc.html","text":"Beancount Design Doc \uf0c1 Martin Blais ( blais@furius.ca ) http://furius.ca/beancount/doc/design-doc A guide for developers to understand Beancount\u2019s internals. I hope for this document to provide a map of the main objects used in the source code to make it easy to write scripts and plugins on top of Beancount or even extend it. Introduction Invariants Isolation of Inputs Order-Independence All Transactions Must Balance Account Have Types Account Lifetimes & Open Directives Supports Dates Only (and No Time) Metadata is for User Data Overview of the Codebase Core Data Structures Number Commodity Amount Lot Position Inventory Account Flag Posting About Tuples & Mutability Summary Directives Common Properties Transactions Flag Payee & Narration Tags Links Postings Balancing Postings Elision of Amounts Stream Processing Stream Invariants Loader & Processing Order Loader Output Parser Implementation Two Stages of Parsing: Incomplete Entries The Printer Uniqueness & Hashing Display Context Realization The Web Interface Reports vs. Web Client-Side JavaScript The Query Interface Design Principles Minimize Configurability Favor Code over DSLs File Format or Input Language? Grammar via Parser Generator Future Work Tagged Strings Errors Cleanup Types Conclusion Introduction \uf0c1 This document describes the principles behind the design of Beancount and a high-level overview of its codebase, data structures, algorithms, implementation and methodology. This is not a user's manual; if you are interested in just using Beancount, see the associated User's Manual and all the other documents available here . However, if you just want to understand more deeply how Beancount works this document should be very helpful. This should also be of interest to developers. This is a place where I wrote about a lot of the ideas behind Beancount that did not find any other venue. Expect some random thoughts that aren\u2019t important for users. Normally one writes a design document before writing the software. I did not do that. But I figured it would still be worthwhile to spell out some of the ideas that led to this design here. I hope someone finds this useful. Invariants \uf0c1 Isolation of Inputs \uf0c1 Beancount should accept input only from the text file you provide to its tools. In particular, it should not contact any external networked service or open any \u201cglobal\u201d files, even just a cache of historical prices or otherwise. This isolation is by design. Isolating the input to a single source makes it easier to debug, reproduce and isolate problems when they occur. This is a nice property for the tool. In addition, fetching and converting external data is very messy. External data formats can be notoriously bad, and they are too numerous to handle all of them (handling just the most common subset would beg the question of where to include the implementation of new converters). Most importantly, the problems of representing the data vs. that of fetching and converting it segment from each other very well naturally: Beancount provides a core which allows you to ingest all the transactional data and derive reports from it, and its syntax is the hinge that connects it to these external repositories of transactions or prices. It isolates itself from the ugly details of external sources of data in this way. Order-Independence \uf0c1 Beancount offers a guarantee that the ordering of its directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input text and reorder any declaration as is most convenient for you, without having to worry about how the software will make its calculations. This also makes it trivial to implement inclusions of multiple files: you can just concatenate the files if you want, and you can process them in any order. Not even directives that declare accounts or commodities are required to appear before these accounts get used in the file. All directives are parsed and then basically sorted before any calculation or validation occurs (a minor detail is that the line number is used as a secondary key in the sorting, so that the re-ordering is stable). Correspondingly, all the non-directive grammar that is in the language is limited to either setting values with global impact (\u201c option \u201d, \u201c plugin \u201d) or syntax-related conveniences (\u201c pushtag \u201d, \u201c poptag \u201d). There is an exception: Options may have effects during the processing of the file and should appear at the beginning. At the moment, Beancount does not enforce this, e.g., by performing multiple passes of parsing, but it probably should. What\u2019s more, options in included files are currently ignored. You should put all your options in the top-level ledger file. All Transactions Must Balance \uf0c1 All transactions must balance by \u201cweight.\u201d There is no exception. Beancount transactions are required to have balanced, period. In particular, there is no allowance for exceptions such as the \u201cvirtual postings\u201d that can be seen in Ledger. The first implementation of Beancount allowed such postings. As a result, I often used them to \u201cresolve\u201d issues that I did not know how to model well otherwise. When I rewrote Beancount, I set out to convert my entire input file to avoid using these, and over time I succeeded in doing this and learned a lot of new things in the process. I have since become convinced that virtual postings are wholly unnecessary, and that making them available only provides a crutch upon which a lot of users will latch instead of learning to model transfers using balanced transactions. I have yet to come across a sufficiently convincing case for them 1 . This provides the property that any subsets of transactions will sum up to zero. This is a nice property to have, it means we can generate balance sheets at any point in time. Even when we eventually support settlement dates or per-posting dates , we will split up transactions in a way that does not break this invariant. Accounts Have Types \uf0c1 Beancount accounts should be constrained to be of one of five types: Assets, Liabilities, Income, Expenses and Equity. The rationale behind this is to realize that all matters of counting can be characterized by being either permanent (Assets, Liabilities) or transitory (Income, Expenses), and that the great majority of accounts have a usual balance sign: positive (Assets, Expenses) or negative (Liabilities, Income). Given a quantity to accumulate, we can always select one of these four types of labels for it. Enforcing this makes it possible to implement reports of accumulated values for permanent accounts (balance sheet) and reports of changes of periods of time (income statement). Although it is technically possible to explicitly book postings against Equity accounts the usual purpose of items in that category is to account for past changes on the balance sheet (net/retained income or opening balances). These concepts of time and sign generalize beyond that of traditional accounting. Not having them raises difficult and unnecessary questions about how to handle calculating these types of reports. We simply require that you label your accounts into this model. I\u2019ll admit that this requires a bit of practice and forethought, but the end result is a structure that easily allows us to build commonly expected outputs. Account Lifetimes & Open Directives \uf0c1 Accounts have lifetimes; an account opens at a particular date and optionally closes at another. This is directed by the Open and Close directives. All accounts are required to have a corresponding Open directive in the stream. By default, this is enforced by spitting out an error when posting to an account whose Open directive hasn\u2019t been seen in the stream. You can automate the generation of these directives by using the \u201cauto_accounts\u201d plugin, but in any case, the stream of entries will always contain the Open directives. This is an assumption that one should be able to rely upon when writing scripts. (Eventually a similar constraint will be applied for Commodity directives so that the stream always includes one before it is used; and they should be auto-generated as well. This is not the case right now [June 2015].) Supports Dates Only (and No Time) \uf0c1 Beancount does not represent time, only dates. The minimal time interval is one day. This is for simplicity\u2019s sake. The situations where time would be necessary to disambiguate account balances exist, but in practice they are rare enough that their presence does not justify the added complexity of handling time values and providing syntax to deal with it. If you have a use case whereby times are required, you may use metadata to add it in, or more likely, you should probably write custom software for it. This is outside the scope of Beancount by choice. Metadata is for User Data \uf0c1 By default, Beancount will not make assumptions about metadata fields and values. Metadata is reserved for private usage by the user and for plugins. The Beancount core does not read metadata and change its processing based on it. However, plugins may define special metadata values that affect what they produce. That being said, there are a few special metadata fields produced by the Beancount processing: filename: A string, the name of the file the transaction was read from (or the plugin that created it) lineno: An integer, the line number from the file where the transaction was read from. tolerances : A dict of currency to Decimal, the tolerance values used in balancing the transaction. residual (on postings): A boolean, true if the posting has been added to automatically absorb rounding error. There may be a few more produced in the future but in any case, the core should not read any metadata and affect its behavior as a consequence. (I should probably create a central registry or location where all the special values can be found in one place.) Overview of the Codebase \uf0c1 All source code lives under a \u201c beancount \u201d Python package. It consists of several packages with well-defined roles, the dependencies between which are enforced strictly. Beancount source code packages and approximate dependencies between them. At the bottom, we have a beancount.core package which contains the data structures used to represent transactions and other directives in memory. This contains code for accounts, amounts, lots, positions, and inventories. It also contains commonly used routines for processing streams of directives. One level up, the beancount.parser package contains code to parse the Beancount language and a printer that can produce the corresponding text given a stream of directives. It should be possible to convert between text and data structure and round-trip this process without loss. At the root of the main package is a beancount.loader module that coordinates everything that has to be done to load a file in memory. This is the main entry point for anyone writing a script for Beancount, it always starts with loading a stream of entries. This calls the parser, runs some processing code, imports the plugins and runs them in order to produce the final stream of directives which is used to produce reports. The beancount.plugins package contains implementations of various plugins. Each of these plugin modules are independent from each other. Consult the docstrings in the source code to figure out what each of these does. Some of these are prototypes of ideas. There is a beancount.ops package which contains some high-level processing code and some of the default code that always runs by default that is implemented as plugins as well (like padding using the Pad directive). This package needs to be shuffled a bit in order to clarify its role. On top of this, reporting code calls modules from those packages. There are four packages which contain reporting code, corresponding to the Beancount reporting tools: beancount.reports : This package contains code specialized to produce different kinds of outputs (invoked by bean-report). In general I\u2019d like to avoid defining custom routines to produce desired outputs and use the SQL query tool to express grouping & aggregation instead, but I think there will always be a need for at least some of these. The reports are defined in a class hierarchy with a common interface and you should be able to extend them. Each report supports some subset of a number of output formats. beancount.query : This package contains the implementation of a query language and interactive shell (invoked by bean-query) that allows you to group and aggregate positions using an SQL-like DSL. This essentially implements processing over an in-memory table of Posting objects, with functions defined over Amount, Position and Inventory objects. beancount.web : This package contains all the source code for the default web interface (invoked by bean-web). This is a simple Bottle application that serves many of the reports from beancount.report to HTML format, running on your machine locally. The web interface provides simple views and access to your data. (It stands to be improved greatly, it\u2019s in no way perfect.) beancount.projects : This package contains various custom scripts for particular applications that I\u2019ve wanted to share and distribute. Wherever possible, I aim to fold these into reports. There are no scripts to invoke these; you should use \u201c python3 -m beancount.projects. \u201d to run them. There is a beancount.scripts package that contains the \u201cmain\u201d programs for all the scripts under the bin directory . Those scripts are simple launchers that import the corresponding file under beancount.scripts. This allows us to keep all the source code under a single directory and that makes it easier to run linters and other code health checkers on the code\u2014it\u2019s all in one place. Finally, there is a beancount.utils package which is there to contain generic reusable random bits of utility code. And there is a relatively unimportant beancount.docs package that contains code used by the author just to produce and maintain this documentation (code that connects to Google Drive). Enforcing the dependency relationships between those packages is done by a custom script . Core Data Structures \uf0c1 This section describes the basic data structures that are used as building blocks to represent directives. Where possible I will describe the data structure in conceptual terms. (Note for Ledger users : This section introduces some terminology for Beancount; look here if you\u2019re interested to contrast it with concepts and terminology found in Ledger.) Number \uf0c1 Numbers are represented using decimal objects, which are perfectly suited for this. The great majority of numbers entered in accounting systems are naturally decimal numbers and binary representations involve representational errors which cause many problems for display and in precision. Rational numbers avoid this problem, but they do not carry the limited amount of precision that the user intended in the input. We must deal with tolerances explicitly. Therefore, all numbers should use Python\u2019s \u201c decimal.Decimal \u201d objects. Conveniently, Python v3.x supports a C implementation of decimal types natively (in its standard library; this used to be an external \u201ccdecimal\u201d package to install but it has been integrated in the C/Python interpreter). The default constructor for decimal objects does not support some of the input syntax we want to allow, such as commas in the integer part of numbers (e.g., \u201c278 , 401.35 USD\u201d) or initialization from an empty string. These are important cases to handle. So I provide a special constructor to accommodate these inputs: beancount.core.number.D() . This is the only method that should be used to create decimal objects: from beancount.core.number import D \u2026 number = D(\"2,402.21\") I like to import the \u201c D \u201d symbol by itself (and not use number.D ). All the number-related code is located under beancount.core.number . Some number constants have been defined as well: ZERO , ONE , and HALF . Use those instead of explicitly constructed numbers (such as D(\"1\") ) as it makes it easier to grep for such initializations. Commodity \uf0c1 A commodity , or currency (I use both names interchangeably in the code and documentation) is a string that represents a kind of \u201cthing\u201d that can be stored in accounts. In the implementation, it is represented as a Python str object (there is no module with currency manipulation code). These strings may only contain capital letters and numbers and some special characters (see the lexer code). Beancount does not predefine any currency names or categories\u2014all currencies are created equal. Really. It genuinely does not know anything special about dollars or euros or yen or anything else. The only place in the source code where there is a reference to those is in the tests. There is no support for syntax using \u201c$\u201d or other currency symbols; I understand that some users may want this syntax, but in the name of keeping the parser very simple and consistent I choose not to provide that option. Moreover, Beancount does not make a distinction between commodities which represent \"money\" and others that do not (such as shares, hours, etc.). These are treated the same throughout the system. It also does not have the concept of a \"home\" currency 2 ; it's a genuinely multi-currency system. Currencies need not be defined explicitly in the input file format; you can just start using them in the file and they will be recognized as such by their unique syntax (the lexer recognizes and emits currency tokens). However, you may declare some using a Commodity directive. This is only provided so that a per-commodity entity exists upon which the user can attach some metadata, and some reports and plugins are able to find and use that metadata. Account \uf0c1 An account is basically just the name of a bucket associated with a posting and is represented as a simple string (a Python str object). Accounts are detected and tokenized by the lexer and have names with at least two words separated by a colon (\":\"). Accounts don\u2019t have a corresponding object type; we just refer to them by their unique name string (like filenames in Python). When per-account attributes are needed, we can extract the Open directives from the stream of entries and find the one corresponding to the particular account we\u2019re looking for. Similar to Python\u2019s os.path module, there are some routines to manipulate account names in beancount.core.account and the functions are named similarly to those of the os.path module. The first component of an account\u2019s name is limited to one of five types: Assets Liabilities Equity Income Expenses The names of the account types as read in the input syntax may be customized with some \"option\" directives, so that you can change those names to another language, or even just rename \"Income\" to \"Revenue\" if you prefer that. The beancount.core.account_types module contains some helpers to deal with these. Note that the set of account names forms an implicit hierarchy. For example, the names: Assets:US:TDBank:Checking Assets:US:TDBank:Savings implicitly defines a tree of nodes with parent nodes \"Assets\", \"US\", \"TDBank\" with two leaf nodes \"Checking\" and \"Savings\". This implicit tree is never realized during processing, but there is a Python module that allows one to do this easily (see beancount.core.realization ) and create linearized renderings of the tree. Flag \uf0c1 A \u201cflag\u201d is a single-character string that may be associated with Transactions and Postings to indicate whether they are assumed to be correct (\"reconciled\") or flagged as suspicious. The typical value used on transaction instances is the character \u201c*\u201d. On Postings, it is usually left absent (and set to a None). Amount \uf0c1 An Amount is the combination of a number and an associated currency, conceptually: Amount = (Number, Currency) Amount instances are used to represent a quantity of a particular currency (the \u201cunits\u201d) and the price on a posting. A class is defined in beancount.core.amount as a simple tuple-like object. Functions exist to perform simple math operations directly on instances of Amount. You can also create Amount instance using amount.from_string() , for example: value = amount.from_string(\"201.32 USD\") Cost \uf0c1 A Cost object represents the cost basis for a particular lot of a commodity. Conceptually, it is Cost = (Number, Currency, Date, Label) The number and currency is that of the cost itself, not of the commodity. For example, if you bought 40 shares of AAPL stock at 56.78 USD, the Number is a \u201c56.78\u201d decimal and the Currency is \u201cUSD.\u201d For example: Cost(Decimal(\"56.78\"), \"USD\", date(2012, 3, 5), \"lot15\") The Date is the acquisition date of the corresponding lot (a datetime.date object). This is automatically attached to the Cost object when a posting augments an inventory\u2014the Transaction\u2019s date is automatically attached to the Cost object\u2014or if the input syntax provides an explicit date override. The Label can be any string. It is provided as a convenience for a user to refer to a particular lot or disambiguate similar lots. On a Cost object, the number, currency and date attributes are always set. If the label is unset, it has a value of \u201cNone.\u201d CostSpec \uf0c1 In the input syntax, we allow users to provide as little information as necessary in order to disambiguate between the lots contained in the inventory prior to posting. The data provided filters the set of matching lots to an unambiguous choice, or to a subset from which an automated booking algorithm will apply (e.g., \u201cFIFO\u201d). In addition, we allow the user to provide either the per-unit cost and/or the total-cost. This convenience is useful to let Beancount automatically compute the per-unit cost from a total of proceeds. CostSpec = (Number-per-unit, Number-total, Currency, Date, Label, Merge) Since any of the input elements may be omitted, any of the attributes of a CostSpec may be left to None. If a number is missing and necessary to be filled in, the special value \u201cMISSING\u201d will be set on it. The Merge attribute it used to record a user request to merge all the input lots before applying the transaction and to merge them after. This is the method used for all transactions posted to an account with the \u201cAVERAGE\u201d booking method. Position \uf0c1 A position represents some units of a particular commodity held at cost. It consists simply in Position = (Units, Cost) Units is an instance of Amount , and Cost is an instance of Cost , or a null value if the commodity is not held at cost. Inventories contain lists of Position instances. See its definition in beancount.core.position . Posting \uf0c1 Each Transaction directive is composed of multiple Postings (I often informally refer to these in the code and on the mailing-list as the \u201clegs\u201d of a transaction). Each of these postings is associated with an account, a position and an optional price and flag: Posting = (Account, Units, Cost-or-CostSpec, Price, Flag, Metadata) As you can see, a Posting embeds its Position instance 3 . The Units is an Amount , and the \u2018cost\u2019 attribute refers to either a Cost or a CostSpec instance (the parser outputs Posting instances with an CostSpec attribute which is resolved to a Cost instance by the booking process; see How Inventories Work for details). The Price is an instance of Amount or a null value. It is used to declare a currency conversion for balancing the transaction, or the current price of a position held at cost. It is the Amount that appears next to a \u201c@\u201d in the input. Flags on postings are relatively rare; users will normally find it sufficient to flag an entire transaction instead of a specific posting. The flag is usually left to None; if set, it is a single-character string. The Posting type is defined in beancount.core.data , along with all the directive types. Inventory \uf0c1 An Inventory is a container for an account\u2019s balance in various lots of commodities. It is essentially a list of Position instances with suitable operations defined on it. Conceptually you may think of it as a mapping with unique keys: Inventory = [Position1, Position2, Position3, \u2026 , PositionN] Generally, the combination of a position\u2019s ( Units.Currency, Cost) is kept unique in the list, like the key of a mapping. Positions for equal values of currency and cost are merged together by summing up their Units.Number and keeping a single position for it. And simple positions are mixed in the list with positions held at cost. The Inventory is one of the most important and oft-used object in Beancount\u2019s implementation, because it is used to sum the balance of one or more accounts over time. It is also the place where the inventory reduction algorithms get applied to, and traces of that mechanism can be found there. The \u201c How Inventories Work \u201d document provides the full detail of that process. For testing, you can create initialized instances of Inventory using inventory.from_string() . All the inventory code is written in beancount.core.inventory . About Tuples & Mutability \uf0c1 Despite the program being written in a language which does not make mutability \u201cdifficult by default\u201d, I designed the software to avoid mutability in most places. Python provides a \u201c collections.namedtuple \u201d factory that makes up new record types whose attributes cannot be overridden. Well\u2026 this is only partially true: mutable attributes of immutable tuples can be modified. Python does not provide very strong mechanisms to enforce this property. Regardless, functional programming is not so much an attribute of the language used to implement our programs than of the guarantees we build into its structure. A language that supports strong guarantees helps to enforce this. But if, even by just using a set of conventions, we manage to mostly avoid mutability, we have a mostly functional program that avoids most of the pitfalls that occur from unpredictable mutations and our code is that much easier to maintain. Programs with no particular regard for where mutation occurs are most difficult to maintain. By avoiding most of the mutation in a functional approach, we avoid most of those problems. Most of the data structures used in Beancount are namedtuples , which make the modification of their attributes inconvenient on purpose. Most of the code will attempt to avoid mutation, except for local state (within a function) or in a narrow scope that we can easily wrap our head around. Where mutation is possible, by convention I try to avoid it by default. When we do mutation, I try to document the effects. I avoid the creation of classes which mutate their internal state, except for a few cases (e.g. the web application). I prefer functions to objects and where I define classes I mostly avoid inheritance. These properties are especially true for all the small objects: Amount, Lot, Position, Posting objects, and all the directives types from beancount.core.data . On the other hand, the Inventory object is nearly always used as an accumulator and does allow the modification of its internal state (it would require a special, persistent data structure to avoid this). You have to be careful how you share access to Inventory objects\u2026 and modify them, if you ever do. Finally, the loader produces lists of directives which are all simple namedtuple objects. These lists form the main application state. I\u2019ve avoided placing these inside some special container and instead pass them around explicitly, on purpose. Instead of having some sort of big \u201capplication\u201d object, I\u2019ve trimmed down all the fat and all your state is represented in two things: A dated and sorted list of directives which can be the subject of stream processing and a list of constant read-only option values. I think this is simpler. I credit my ability to make wide-ranging changes to a mid-size Python codebase to the adoption of these principles. I would love to have types in order to safeguard against another class of potential errors, and I plan to experiment with Python 3.5\u2019s upcoming typing module . Summary \uf0c1 The following diagram explains how these objects relate to each other, starting from a Posting. For example, to access the number of units of a postings you could use posting.units.number For the cost currency: posting.cost.currency You can print out the tuples in Python to figure out their structure. Previous Design \uf0c1 For the sake of preservation, if you go back in time in the repository, the structure of postings was deeper and more complex. The new design reflects a flatter and simpler version of it. Here is what the old design used to look like: Directives \uf0c1 The main production from loading a Beancount input file is a list of directives . I also call these entries interchangeably in the codebase and in the documents. There are directives of various types: Transaction Balance & Pad Open & Close Commodity Note Event Price Document In a typical Beancount input file, the great majority of entries will be Transactions and some Balance assertions. There will also be Open & perhaps some Close entries. Everything else is optional. Since these combined with a map of option values form the entire application state, you should be able to feed those entries to functions that will produce reports. The system is built around this idea of processing a stream of directives to extract all contents and produce reports, which are essentially different flavors of filtering and aggregations of values attached to this stream of directives. Common Properties \uf0c1 Some properties are in common to all the directives: Date. A datetime.date object. This is useful and consistent. For a Transaction, that\u2019s the date at which it occurs. For an Open or Close directive, that\u2019s the date at which the account was opened or closed. For a Pad directive, that\u2019s the date at which to insert a transaction. For a Note or Document, it is the date at which to insert the note in the stream of postings of that account. For an Event, it is the date at which it occurs. For a Commodity directive which essentially provides a per-commodity hanging point for commodity-related metadata, the date isn\u2019t as meaningful; I choose to date those on the day the commodity was created. Meta . All the directives have metadata attribute (a Python dict object). The purpose of metadata is to allow the user to hang any kind of ancillary data they want and then use this in scripts or queries. Posting instances also have a metadata attribute. Transactions \uf0c1 The Transaction directive is the subject of Beancount and is by far the most common directive found in input files and is worth of special attention. The function of a bookkeeping system is to organize the Postings attached to Transactions in various ways. All the other types of entries occupy supporting roles in our system. A Transaction has the following additional fields. Flag \uf0c1 The single-character flag is usually there to replace the \u201ctxn\u201d keyword (Transaction is the only directive which allows being entered without entering its keyword). I\u2019ve been considering changing the syntax definition somewhat to allow not entering the flag nor the keyword, because I\u2019d like to eventually support that. Right now, either the flag or keyword is required. The flag attribute may be set to None. Payee & Narration \uf0c1 The narration field is a user-provided description of the transaction, such as \"Dinner with Mary-Ann.\" You can put any information in this string. It shows up in the journal report. Oftentimes it is used to enrich the transaction with context that cannot be imported automatically, such as \"transfer from savings account to pay for car repairs.\" The payee name is optional, and exists to describe the entity with which the transaction is conducted, such as \"Whole Foods Market\" or \"Shell.\" Note that I want to be able to produce reports for all transactions associated with a particular payee, so it would be nice to enter consistent payee names. The problem with this is that the effort to do this right is sometimes too great. Either better tools or looser matching over names is required in order to make this work. The input syntax also allows only a single string to be provided. By default this becomes the narration, but I\u2019ve found that in practice it can be useful to have just the payee. It\u2019s just a matter of convenience. At the moment, if you want to enter just the payee string you need to append an empty narration string. This should be revisited at some point. Tags \uf0c1 Tags are sets of strings that can be used to group sets of transactions (or set to None if there are no tags). A view of this subset of transactions can then be generated, including all the usual reports (balance sheet, income statement, journals, etc.). You can tag transactions for a variety of purposes; here are some examples: All transactions during a particular travel might be tagged to later summarize your trip expenses. Those transactions will usually occur around the same date and therefore there is convenient syntax used to automatically tag many transactions that are declared together in a file. Transactions related to a particular project to be accomplished. I took some classes in an online program once, and tagged all related expenses to it. I use this to track all my immigration-related expenses for a particular stage (e.g. green card). Declaring a group of expenses to be paid back by an employer (or your own company) later on. Expenses related to moving between locations. Typical tag names that I use for tags look like #trip-israel-2012 , #conference-siggraph , and #course-cfa . In general, tags are useful where adding a sub-accounts won't do. This is often the case where a group of related expenses are of differing types, and so they would not belong to a single account. Given the right support from the query tools, they could eventually be subsumed by metadata\u2014I have been considering converting tags into metadata keys with a boolean value of True, in order to remove unnecessary complexity. Links \uf0c1 Links are a unique set of strings or None , and in practice will be usually empty for most transactions. They differ from tags only in purpose. Links are there to chain together a list of related transactions and there are tools used to list, navigate and balance a subset of transactions linked together. They are a way for a transaction to refer to other transactions. They are not meant to be used for summarizing. Examples include: transaction-ids from trading accounts (these often provide an id to a \"related\" or \"other\" transaction); account entries associated with related corrections, for example a reversed fee following a phone call could be linked to the original invalid fee from several weeks before; the purchase and sale of a home, and related acquisition expenses. In contrast to tags, their strings are most often unique numbers produced by the importers. No views are produced for links; only a journal of a particular links transactions can be produced and a rendered transaction should be accompanied by an actual \"link\" icon you can click on to view all the other related transactions. Postings \uf0c1 A list of Postings are attached to the Transaction object. Technically this list object is mutable but in practice we try not to modify it. A Posting can ever only be part of a single Transaction. Sometimes different postings of the same transaction will settle at different dates in their respective accounts, so eventually we may allow a posting to have its own date to override the transaction's date, to be used as documentation; in the simplest version, we enforce all transactions to occur punctually, which is simple to understand and was not found to be a significant problem in practice. Eventually we might implement this by implicitly converting Transactions with multiple dates into multiple Transactions and using some sort of transfer account to hold the pending balance in-between dates. See the associated proposal for details. Balancing Postings \uf0c1 The fundamental principle of double-entry book-keeping is enforced in each of the Transaction entries: the sum of all postings must be zero. This section describes the specific way in which we do this, and is the key piece of logic that allow the entire system to balance nicely. This is also one of the few places in this system where calculations go beyond simple filtering and aggregation. As we saw earlier, postings are associated with A position, which is a number of units of a lot (which itself may or may not have a cost) An optional conversion price Given this, how do we balance a transaction? Some terminology is in order: for this example posting: Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD Units. The number of units is the number of the position, or \u201c50\u201d. Currency. The commodity of the lot, that is, \u201cHOOL\u201d. Cost. The cost of this position is the number of units times the per-unit cost in the cost currency, that is 50 x 700 = 35000 USD. (Total) Price. The price of a unit is the attached price amount, 920 USD. The total price of a position is its number of units times the conversion price, in this example 50 x 920 = 46000 USD. Weight. The amount used to balance each posting in the transaction: If the posting has an associated cost, we calculate the cost of the position (regardless of whether a price is present or not); If the posting has no associated cost but has a conversion price, we convert the position into its total price; Finally, if the posting has no associated cost nor conversion price, the number of units of the lot are used directly. Balancing is really simple: each of the Posting's positions are first converted into their weight. These amounts are then grouped together by currency, and the final sum for each currency is asserted to be close to zero, that is, within a small amount of tolerance (as inferred by a combination of by the numbers in the input and the options ). The following example is one of case (2), with a price conversion and a regular leg with neither a cost nor a price (case 3): 2013-07-22 * \"Wired money to foreign account\" Assets:Investment:HOOL -35350 CAD @ 1.01 USD ;; -35000 USD (2) Assets:Investment:Cash 35000 USD ;; 35000 USD (3) ;;------------------ ;; 0 USD In the next example, the posting for the first leg has a cost, the second posting has neither: 2013-07-22 * \"Bought some investment\" Assets:Investment:HOOL 50 HOOL {700 USD} ;; 35000 USD (1) Assets:Investment:Cash -35000 USD ;; -35000 USD (3) ;;------------------ ;; 0 USD Here's a more complex example with both a price and a cost; the price here is ignored for the purpose of balance and is used only to enter a data-point in the internal price database: 2013-07-22 * \"Sold some investment\" Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD ;; -35000 USD (1) Assets:Investment:Cash 46000 USD ;; 46000 USD (3) Income:CapitalGains -11000 USD ;; -11000 USD (3) ;;------------------ ;; 0 USD Finally, here's an example with multiple commodities: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking 4585.38 USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib 540.00 IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation -4.62 VACHR Here there are three groups of weights to balance: USD: (4485.38) + (-25.38) + (-5000) + (540) ~= 0 USD IRAUSD: (540.00) + (-540.00) ~= 0 IRAUSD VACHR: (4.62) + (-4.62) ~= 0 VACHR Elision of Amounts \uf0c1 Transactions allow for at most one posting to be elided and automatically set; if an amount was elided, the final balance of all the other postings is attributed to the balance. With the inventory booking proposal , it should be extended to be able to handle more cases of elision. An interesting idea would be to allow the elided postings to at least specify the currency they're using. This would allow the user to elide multiple postings, like this: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation VACHR Stream Processing \uf0c1 An important by-product of representing all state using a single stream of directives is that most of the operations in Beancount can be implemented by simple functions that accept the list of directives as input and output a modified list of directives. For example, The \u201cPad\u201d directive is implemented by processing the stream of transactions, accumulating balances, inserting a new padding transaction after the Pad directive when a balance assertion fails. Summarization operations (open books, close books, clear net income to equity) are all implemented this way, as functional operators on the list of streams. For example, opening the books at a particular date is implemented by summing all balances before a certain date and replacing those transactions by new transactions that pull the final balance from an Equity account. Closing the books only involves moving the balances of income statement account to Equity and truncation the list of transactions to that date. This is very elegant\u2014the reporting code can be oblivious to the fact that summarization has occurred. Prices on postings held with a cost are normally ignored. The \u201cimplicit prices\u201d option is implemented using a plugin which works its magic by processing the stream of operations and inserting otherwise banal Price directives automatically when such a posting is found. Many types of verifications are also implemented this way; the sellgains plugin verifies that non-income balances check against the converted price of postings disregarding the cost. This one does not insert any new directives, but may generate new errors. These are just some examples. I\u2019m aiming to make most operations work this way. This design has proved to be elegant, but also surprisingly flexible and it has given birth to the plugins system available in Beancount; you might be surprised to learn that I had not originally thought to provide a plugins system... it just emerged over time teasing abstractions and trying to avoid state in my program. I am considering experimenting with weaving the errors within the stream of directives by providing a new \u201cError\u201d directive that could be inserted by stream processing functions. I\u2019m not sure whether this would make anything simpler yet, it\u2019s just an idea at this point [July 2015]. Stream Invariants \uf0c1 The stream of directives comes with a few guarantees you can rest upon: All the directives are ordered by date. Because many dates have multiple directives, in order to have a stable sorting order the line number in the file is used as a secondary sort key. All uses of an account is preceded in the stream by a corresponding Open entry. If the input did not provide one, an Open directive is automatically synthesized. All Balance directives precede any other directive on a particular day. This is enforced in order to make the processing of balance assertions straightforward and to emphasize that their semantics is to occur at the beginning of the day. Loader & Processing Order \uf0c1 The process of loading a list of entries from an input file is the heart of the project. It is important to understand it in order to understand how Beancount works. Refer to the diagram below. It consists of A parsing step that reads in all the input files and produces a stream of potentially incomplete directives. The processing of this stream of entries by built-in and user-specified plugins. This step potentially produces new error objects. A final validation step that ensures the invariants have not been broken by the plugins. The beancount.loader module orchestrates this processing. In the code and documents, I\u2019m careful to distinguish between the terms \u201cparsing\u201d and \u201cloading\u201d carefully. These two concepts are distinct. \u201cParsing\u201d is only a part of \u201cloading.\u201d The user-specified plugins are run in the order they are provided in the input files. Raw mode. Some users have expressed a desire for more control over which built-in plugins are run and in which order they are to be run, and so enabling the \u201craw\u201d option disables the automatic loading of all built-in plugins (you have to replace those with explicit \u201cplugin\u201d directives if you want to do that). I\u2019m not sure if this is going to be useful yet, but it allows for tighter control over the loading process for experimentation. Loader Output \uf0c1 The parser and the loader produce three lists: entries: A list of directive tuples from beancount.core.data. This is the stream of data which should consists mostly of Transaction and Balance instances. As much as possible, all data transformations are performed by inserting or removing entries from this list. Throughout the code and documents, I refer to these as entries or directives interchangeably. errors: A list of error objects. In Beancount I don\u2019t use exceptions to report errors; rather, all the functions produce error objects and they are displayed at the most appropriate time. (These deserve a base common type but for now the convention they respect is that they all provide a source (filename, line-no), message and entry attributes.) options_map: A dict of the options provided in the file and derived during parsing. Though this is a mutable object, we never modify it once produced by the parser. Parser Implementation \uf0c1 The Beancount parser is a mix of C and Python 3 code. This is the reason you need to compile anything in the first place. The parser is implemented in C using a flex tokenizer and a Bison parser generator, mainly for performance reasons. I chose the C language and these crufty old GNU tools because they are portable and will work everywhere. There are better parser generators out there, but they would either require my users to install more compiler tools or exotic languages. The C language is a great common denominator. I want Beancount to work everywhere and to be easy to install. I want this to make it easy on others, but I also want this for myself, because at some point I\u2019ll be working on other projects and the less dependencies I have the lighter it will be to maintain aging software. Just looking ahead. (That\u2019s also why I chose to use Python instead of some of the other languages I\u2019m more naturally attracted to these days (Clojure, Go, ML); I need Beancount to work... when I\u2019m counting the beans I don\u2019t have time to debug problems. Python is careful about its changes and it will be around for a long long time as is.) There is a lexer file lexer.l written in flex and a Bison grammar in grammar.y. These tools are used to generate the corresponding C source code (lexer.h/.c and grammar.h/.c). These, along with some hand-written C code that defines some interface functions for the module (parser.h/.c), are compiled into an extension module (_parser.so). Eventually we could consider creating a small dependency rule in setup.py to invoke flex and Bison automatically but at the moment, in order to minimize the installation burden, I check the generated source code in the repository (lexer.h/c and grammar.h/c). The interaction between the Python and C code works like this: You import beancount.parser.parser and invoke either parse_file() or parse_string(). This uses code in grammar.py to create a Builder object which essentially provides callbacks in Python to process grammar rules. parser.py calls a C function in the extension module with the Builder object. That C code sets up flex to be able to read the input file or string then hands over control to the parser by calling the generated C function \u201cyyparse()\u201d. yyparse() is the parser and will fetch input tokens from the lexer C code using successive calls to \u201cyylex()\u201d and attempt to reduce rules. When a rule or token is successfully reduced, the C code invokes callback methods on the Builder object you provided. The parser\u2019s rules communicate between each other by passing around \u201cPyObject*\u201d instances, so that code has to be a little careful about correctly handling reference counting. This allows me to run Python code on rule reduction, which makes it really easy to customize parser behaviour yet maintain the performance of the grammar rule processing in C. Note that the grammar.py Builder derives from a similar Builder class defined in lexer.py, so that lexer tokens recognized calls methods defined in the lexer.py file to create them and parser rule correspondingly calls methods from grammar.py\u2019s Builder. This isolation is not only clean, it also makes it possible to just use the lexer to tokenize a file from Python (without parsing) for testing the tokenizer; see the tests where this is used if you\u2019re curious how this works. I just came up with this; I haven\u2019t seen this done anywhere else. I tried it and the faster parser made a huge difference compared to using PLY so I stuck with that. I quite like the pattern, it\u2019s a good compromise that offers the flexibility of a parser generator yet allows me to handle the rules using Python. Eventually I\u2019d like to move just some of the most important callbacks to C code in order to make this a great deal faster (I haven\u2019t done any real performance optimizations yet). This has worked well so far but for one thing: There are several limitations inherent in the flex tokenizer that have proved problematic. In particular, in order to recognize transaction postings using indent I have had to tokenize the whitespace that occurs at the beginning of a line. Also, single-characters in the input should be parsed as flags and at the moment this is limited to a small subset of characters. I\u2019d like to eventually write a custom lexer with some lookahead capabilities to better deal with these problems (this is easy to do). Two Stages of Parsing: Incomplete Entries \uf0c1 At the moment, the parser produces transactions which may or may not balance. The validation stage which runs after the plugins carries out the balance assertions. This degree of freedom is allowed to provide the opportunity for users to enter incomplete lists of postings and for corresponding plugins to automate some data entry and insert completing postings. However, the postings on the transactions are always complete objects, with all their expected attributes set. For example, a posting which has its amount elided in the input syntax will have a filled in position by the time it comes out of the parser. We will have to loosen this property when we implement the inventory booking proposal , because we want to support the elision of numbers whose values depend on the accumulated state of previous transactions. Here is an obvious example. A posting like this 2015-04-03 * \"Sell stock\" Assets:Investments:AAPL -10 AAPL {} ... should succeed if at the beginning of April 3rd the account contains exactly 10 units of AAPL, in either a single or multiple lots. There is no ambiguity: \u201csell all the AAPL,\u201d this says. However, the fact of its unambiguity assumes that we\u2019ve computed the inventory balance of that account up until April 3rd\u2026 So the parser will need to be split into two phases: A simple parsing step that produces potentially incomplete entries with missing amounts A separate step for the interpolation which will have available the inventory balances of each account as inputs. This second step is where the booking algorithms (e.g., FIFO) will be invoked from. See the diagram above for reference. Once implemented, everything else should be the same. The Printer \uf0c1 In the same package as the parser lives a printer. This isolates all the functionality that deals with the Beancount \u201clanguage\u201d in the beancount.parser package: the parser converts from input syntax to data structure and the printer does the reverse. No code outside this package should concern itself with the Beancount syntax. At some point I decided to make sure that the printer was able to round-trip through the parser, that is, given a stream of entries produced by the loader, you should be able to convert those to text input and parse them back in and the resulting set of entries should be the same (outputting the re-read input back to text should produce the same text), e.g., Note that the reverse isn\u2019t necessarily true: reading an input file and processing it through the loader potentially synthesizes a lot of entries (thanks to the plugins), so printing it back may not produce the same input file (even ignoring reordering and white space concerns). This is a nice property to have. Among other things, it allows us to use the parser to easily create tests with expected outputs. While there is a test that attempts to protect this property, some of the recent changes break it partially: Metadata round-tripping hasn\u2019t been tested super well. In particular, some of the metadata fields need to be ignored (e.g., filename and lineno). Some directives contain derived data, e.g. the Balance directive has a \u201cdiff_amount\u201d field that contains the difference when there is a failure in asserting a balance. This is used to report errors more easily. I probably need to remove these exceptions at some point because it is the only one of its kind (I could replace it with an inserted \u201cError\u201d directive). This probably needs to get refined a bit at some point with a more complete test (it\u2019s not far as it is). Uniqueness & Hashing \uf0c1 In order to make it possible to compare directives quickly, we support unique hashing of all directives, that is, from each directive we should be able to produce a short and unique id. We can then use these ids to perform set inclusion/exclusion/comparison tests for our unit tests. We provide a base test case class with assertion methods that use this capability . This feature is used liberally throughout our test suites. This is also used to detect and remove duplicates. This feature is optional and enabled by the beancount.plugins.noduplicates plugin. Note that the hashing of directives currently does not include user meta-data . Display Context \uf0c1 Number are input with different numbers of fractional digits: 500 -> 0 fractional digits 520.23 -> 2 fractional digits 1.2357 -> 4 fractional digits 31.462 -> 3 fractional digits Often, a number used for the same currency is input with a different number of digits. Given this variation in the input, a question that arises is how to render the numbers consistently in reports? Consider that: We would like that the user not to have to specify the number of fractional digits to use for rendering by default. Rendering numbers may differ depending on the context: most of the time we want to render numbers using their most commonly seen number of digits, rounding if necessary, but when we are rendering conversion rates we would like to render numbers using the maximum number of digits ever seen. The number of digits used tends to vary with the commodity they represent. For example, US dollars will usually render with two digits of precision; the number of units of a mutual fund such as RGAGX might be recorded by your broker with 3 digits of precision. Japanese Yen will usually be specified in integer units. We want to render text report which need to align numbers with varying number of fractional digits together, aligning them by their period or rightmost digits, and possibly rendering a currency thereafter. In order to deal with this thorny problem, I built a kind of accumulator which is used to record all numbers seen from some input and tally statistics about the precisions witnessed for each currency. I call this a DisplayContext . From this object one can request to build a DisplayFormatter object which can be used to render numbers in a particular way. I refer to those objects using the variable names dcontext and dformat throughout the code. The parser automatically creates a DisplayContext object and feeds it all the numbers it sees during parsing. The object is available in the options_map produced by the loader. Realization \uf0c1 It occurs often that one needs to compute the final balances of a list of filtered transactions, and to report on them in a hierarchical account structure. See diagram below. \uf0c1 For the purpose of creating hierarchical renderings, I provide a process called a \u201c realization .\u201d A realization is a tree of nodes, each of which contains An account name, A list of postings and other entries to that account, and The final balance for that account, A mapping of sub-account names to child nodes. The nodes are of type \u201cRealAccount,\u201d which is also a dict of its children. All variables of type RealAccount in the codebase are by convention prefixed by \u201creal_*\u201d. The realization module provides functions to iterate and produce hierarchical reports on these trees of balances, and the reporting routines all use these. For example, here is a bit of code that will dump a tree of balances of your accounts to the console: import sys from beancount import loader from beancount.core import realization entries, errors, options_map = loader.load_file(\"filename.beancount\") real_root = realization.realize(entries) realization.dump_balances(real_root, file=sys.stdout) The Web Interface \uf0c1 Before the appearance of the SQL syntax to filter and aggregate postings, the only report produced by Beancount was the web interface provided by bean-web. As such, bean-web evolved to be good enough for most usage and general reports. Reports vs. Web \uf0c1 One of the important characteristics of bean-web is that it should just be a thin dispatching shell that serves reports generated from the beancount.reports layer. It used to contain the report rendering code itself, but at some point I began to factor out all the reporting code to a separate package in order to produce reports to other formats, such as text reports and output to CSV. This is mostly finished, but at this time [July 2015] some reports remain that only support HTML output. This is why. Client-Side JavaScript \uf0c1 I would like to eventually include a lot more client-side scripting in the web interface. However, I don\u2019t think I\u2019ll be able to work on this for a while, at least not until all the proposals for changes to the core are complete (e.g., inventory booking improvements, settlement splitting and merging, etc.). If you\u2019d like to contribute to Beancount, improving bean-web or creating your own visualizations would be a great venue. The Query Interface \uf0c1 The current query interface is the result of a prototype . It has not yet been subjected to the amount of testing and refinement as the rest of the codebase. I\u2019ve been experimenting with it and have a lot of ideas for improving the SQL language and the kinds of outputs that can be produced from it. I think of it as \u201c70% done\u201d. However, it works, and though some of the reports can be a little clunky to specify, it produces useful results. It will be the subject of a complete rewrite at some point and when I do, I will keep the current implementation for a little while so that existing scripts don\u2019t just break; I\u2019ll implement a v2 of the shell. Design Principles \uf0c1 Minimize Configurability \uf0c1 First, Beancount should have as few options as possible. Second, command-line programs should have no options that pertain to the processing and semantic of the input file (options that affect the output or that pertain to the script itself are fine). The goal is to avoid feature creep. Just a few options opens the possibility of them interacting in complex ways and raises a lot of questions. In this project, instead of providing options to customize every possible thing, I choose to bias the design towards providing the best behavior by default, even if that means less behavior. This is the Apple approach to design. I don\u2019t usually favor this approach for my projects, but I chose it for this particular one. What I\u2019ve learned in the process is how far you can get and still provide much of the functionality you originally set out for. Too many command-line options makes a program difficult to use. I think Ledger suffers from this, for instance. I can never remember options that I don\u2019t use regularly, so I prefer to just design without them. Options that affect semantics should live in the file itself (and should be minimized as well). Options provided when running a process should be only to affect this process. Having less options also makes the software much easier to refactor. The main obstacle to evolving a library of software is the number of complex interactions between the codes. Guaranteeing a well-defined set of invariants makes it much easier to later on split up functionality or group it differently. So.. by default I will resist making changes that aren't generic or that would not work for most users. On the other hand, large-scale changes that would generalize well and that require little configurability are more likely to see implementation. Favor Code over DSLs \uf0c1 Beancount provides a simple syntax, a parser and printer for it, and a library of very simple data record types to allow a user to easily write their scripts to process their data, taking advantage of the multitude of libraries available from the Python environment. Should the built-in querying tools fail to suffice, I want it to be trivially easy for someone to build what they need on top of Beancount. This also means that the tools provided by Beancount don\u2019t have to support all the features everyone might ever possibly want. Beancount\u2019s strength should be representational coherence and simplicity rather than the richness of its reporting features. Creating new kinds of reports should be easy; changing the internals should be hard. (That said, it does provide an evolving palette of querying utilities that should be sufficient for most users.) In contrast, the implementation of the Ledger system provides high-level operations that are invoked as \"expressions\" defined in a domain-specific language, expressions which are either provided on the command-line or in the input file itself. For the user, this expression language is yet another thing to learn, and it\u2019s yet another thing that might involve limitations requiring expansion and work\u2026 I prefer to avoid DSLs if possible and use Python\u2019s well understood semantics instead, though this is not always sensible. File Format or Input Language? \uf0c1 One may wonder whether Beancount\u2019s input syntax should be a computer language or just a data format. The problem we're trying to solve is essentially that of making it easy for a human being to create a transactions-to-postings-to-accounts-&-positions data structure. What is the difference between a data file format and a simple declarative computer language? One distinction is the intended writer of the file. Is it a human? Or is it a computer? The input files are meant to be manicured by a human, at the very least briefly eyeballed. While we are trying to automate as much of this process as possible via importing code\u2014well, the unpleasant bits, anyway\u2014we do want to ensure that we review all the new transactions that we're adding to the ledger in order to ensure correctness. If the intended writer is a human one could file the input under the computer language rubric (most data formats are designed to be written by computers). We can also judge it by the power of its semantics. Beancount\u2019s language is not one designed to be used to perform computation, that is, you cannot define new \u201cBeancount functions\u201d in it. But it has data types. In this regard, it is more like a file format. Just something to think about, especially in the context of adding new semantics. Grammar via Parser Generator \uf0c1 The grammar of its language should be compatible with the use of commonly used parser generator tools. It should be possible to parse the language with a simple lexer and some grammar input file. Beancount\u2019s original syntax was modeled after the syntax of Ledger. However, in its attempt to make the amount of markup minimal, that syntax is difficult to parse with regular tools. By making simple changes to the grammar, e.g. adding tokens for strings and accounts and commodities, the Beancount v2 reimplementation made it possible to use a flex/bison parser generator. This has important advantages: It makes it really easy to make incremental changes to the grammar. Using a custom parser without a well-defined grammar can make it quite difficult to prototype syntax changes. It makes it much easier to implement a parser in another computer language. I\u2019d like to eventually be able to parse Beancount input files in other languages. The data structures are simple enough that it should be easy to reimplement the core in a different language. It\u2019s just a lot less code to write and maintain. (Eventually I\u2019d like to create a file format to generate parsers in multiple languages from a single input. That will be done later, once all the major features are implemented.) Future Work \uf0c1 Tagged Strings \uf0c1 At the moment, account names, tags, links and commodities are represented as simple Python strings. I\u2019ve tried to keep things simple. At some point I\u2019d like to refine this a bit and create a specialization of strings for each of these types. I\u2019d need to assess the performance impact of doing that beforehand. I\u2019m not entirely sure how it could benefit functionality yet. Errors Cleanup \uf0c1 I\u2019ve been thinking about removing the separate \u201cerrors\u201d list and integrating it into the stream of entries as automatically produced \u201cError\u201d entries. This has the advantage that the errors could be rendered as part of the rest of the stream, but it means we have to process the whole list of entries any time we need to print and find errors (probably not a big deal, given how rarely we output errors). Conclusion \uf0c1 This document is bound to evolve. If you have questions about the internals, please post them to the mailing-list . References \uf0c1 Nothing in Beancount is inspired from the following docs, but you may find them interesting as well: Accounting for Computer Scientists I have been considering the addition of a very constrained form of non-balancing postings that would preserve the property of transactions otherwise being balanced, whereby the extra postings would not be able to interact (or sum with) regular balancing postings. \u21a9 There is an option called operating_currency but it is only used to provide good defaults for building reports, never in the processing of transactions. It is used to tell the reporting code which commodities should be broken out to have their own columns of balances, for example. \u21a9 In previous version of Beancount this was not true, the Posting used to have a \u2018position\u2019 attribute and composed it. I prefer the flattened design, as many of the functions apply to either a Position or a Posting. Think of a Posting as deriving from a Position, though, technically it does not. \u21a9","title":"Beancount Design Doc"},{"location":"beancount_design_doc.html#beancount-design-doc","text":"Martin Blais ( blais@furius.ca ) http://furius.ca/beancount/doc/design-doc A guide for developers to understand Beancount\u2019s internals. I hope for this document to provide a map of the main objects used in the source code to make it easy to write scripts and plugins on top of Beancount or even extend it. Introduction Invariants Isolation of Inputs Order-Independence All Transactions Must Balance Account Have Types Account Lifetimes & Open Directives Supports Dates Only (and No Time) Metadata is for User Data Overview of the Codebase Core Data Structures Number Commodity Amount Lot Position Inventory Account Flag Posting About Tuples & Mutability Summary Directives Common Properties Transactions Flag Payee & Narration Tags Links Postings Balancing Postings Elision of Amounts Stream Processing Stream Invariants Loader & Processing Order Loader Output Parser Implementation Two Stages of Parsing: Incomplete Entries The Printer Uniqueness & Hashing Display Context Realization The Web Interface Reports vs. Web Client-Side JavaScript The Query Interface Design Principles Minimize Configurability Favor Code over DSLs File Format or Input Language? Grammar via Parser Generator Future Work Tagged Strings Errors Cleanup Types Conclusion","title":"Beancount Design Doc"},{"location":"beancount_design_doc.html#introduction","text":"This document describes the principles behind the design of Beancount and a high-level overview of its codebase, data structures, algorithms, implementation and methodology. This is not a user's manual; if you are interested in just using Beancount, see the associated User's Manual and all the other documents available here . However, if you just want to understand more deeply how Beancount works this document should be very helpful. This should also be of interest to developers. This is a place where I wrote about a lot of the ideas behind Beancount that did not find any other venue. Expect some random thoughts that aren\u2019t important for users. Normally one writes a design document before writing the software. I did not do that. But I figured it would still be worthwhile to spell out some of the ideas that led to this design here. I hope someone finds this useful.","title":"Introduction"},{"location":"beancount_design_doc.html#invariants","text":"","title":"Invariants"},{"location":"beancount_design_doc.html#isolation-of-inputs","text":"Beancount should accept input only from the text file you provide to its tools. In particular, it should not contact any external networked service or open any \u201cglobal\u201d files, even just a cache of historical prices or otherwise. This isolation is by design. Isolating the input to a single source makes it easier to debug, reproduce and isolate problems when they occur. This is a nice property for the tool. In addition, fetching and converting external data is very messy. External data formats can be notoriously bad, and they are too numerous to handle all of them (handling just the most common subset would beg the question of where to include the implementation of new converters). Most importantly, the problems of representing the data vs. that of fetching and converting it segment from each other very well naturally: Beancount provides a core which allows you to ingest all the transactional data and derive reports from it, and its syntax is the hinge that connects it to these external repositories of transactions or prices. It isolates itself from the ugly details of external sources of data in this way.","title":"Isolation of Inputs"},{"location":"beancount_design_doc.html#order-independence","text":"Beancount offers a guarantee that the ordering of its directives in an input file is irrelevant to the outcome of its computations. You should be able to organize your input text and reorder any declaration as is most convenient for you, without having to worry about how the software will make its calculations. This also makes it trivial to implement inclusions of multiple files: you can just concatenate the files if you want, and you can process them in any order. Not even directives that declare accounts or commodities are required to appear before these accounts get used in the file. All directives are parsed and then basically sorted before any calculation or validation occurs (a minor detail is that the line number is used as a secondary key in the sorting, so that the re-ordering is stable). Correspondingly, all the non-directive grammar that is in the language is limited to either setting values with global impact (\u201c option \u201d, \u201c plugin \u201d) or syntax-related conveniences (\u201c pushtag \u201d, \u201c poptag \u201d). There is an exception: Options may have effects during the processing of the file and should appear at the beginning. At the moment, Beancount does not enforce this, e.g., by performing multiple passes of parsing, but it probably should. What\u2019s more, options in included files are currently ignored. You should put all your options in the top-level ledger file.","title":"Order-Independence"},{"location":"beancount_design_doc.html#all-transactions-must-balance","text":"All transactions must balance by \u201cweight.\u201d There is no exception. Beancount transactions are required to have balanced, period. In particular, there is no allowance for exceptions such as the \u201cvirtual postings\u201d that can be seen in Ledger. The first implementation of Beancount allowed such postings. As a result, I often used them to \u201cresolve\u201d issues that I did not know how to model well otherwise. When I rewrote Beancount, I set out to convert my entire input file to avoid using these, and over time I succeeded in doing this and learned a lot of new things in the process. I have since become convinced that virtual postings are wholly unnecessary, and that making them available only provides a crutch upon which a lot of users will latch instead of learning to model transfers using balanced transactions. I have yet to come across a sufficiently convincing case for them 1 . This provides the property that any subsets of transactions will sum up to zero. This is a nice property to have, it means we can generate balance sheets at any point in time. Even when we eventually support settlement dates or per-posting dates , we will split up transactions in a way that does not break this invariant.","title":"All Transactions Must Balance"},{"location":"beancount_design_doc.html#accounts-have-types","text":"Beancount accounts should be constrained to be of one of five types: Assets, Liabilities, Income, Expenses and Equity. The rationale behind this is to realize that all matters of counting can be characterized by being either permanent (Assets, Liabilities) or transitory (Income, Expenses), and that the great majority of accounts have a usual balance sign: positive (Assets, Expenses) or negative (Liabilities, Income). Given a quantity to accumulate, we can always select one of these four types of labels for it. Enforcing this makes it possible to implement reports of accumulated values for permanent accounts (balance sheet) and reports of changes of periods of time (income statement). Although it is technically possible to explicitly book postings against Equity accounts the usual purpose of items in that category is to account for past changes on the balance sheet (net/retained income or opening balances). These concepts of time and sign generalize beyond that of traditional accounting. Not having them raises difficult and unnecessary questions about how to handle calculating these types of reports. We simply require that you label your accounts into this model. I\u2019ll admit that this requires a bit of practice and forethought, but the end result is a structure that easily allows us to build commonly expected outputs.","title":"Accounts Have Types"},{"location":"beancount_design_doc.html#account-lifetimes-open-directives","text":"Accounts have lifetimes; an account opens at a particular date and optionally closes at another. This is directed by the Open and Close directives. All accounts are required to have a corresponding Open directive in the stream. By default, this is enforced by spitting out an error when posting to an account whose Open directive hasn\u2019t been seen in the stream. You can automate the generation of these directives by using the \u201cauto_accounts\u201d plugin, but in any case, the stream of entries will always contain the Open directives. This is an assumption that one should be able to rely upon when writing scripts. (Eventually a similar constraint will be applied for Commodity directives so that the stream always includes one before it is used; and they should be auto-generated as well. This is not the case right now [June 2015].)","title":"Account Lifetimes & Open Directives"},{"location":"beancount_design_doc.html#supports-dates-only-and-no-time","text":"Beancount does not represent time, only dates. The minimal time interval is one day. This is for simplicity\u2019s sake. The situations where time would be necessary to disambiguate account balances exist, but in practice they are rare enough that their presence does not justify the added complexity of handling time values and providing syntax to deal with it. If you have a use case whereby times are required, you may use metadata to add it in, or more likely, you should probably write custom software for it. This is outside the scope of Beancount by choice.","title":"Supports Dates Only (and No Time)"},{"location":"beancount_design_doc.html#metadata-is-for-user-data","text":"By default, Beancount will not make assumptions about metadata fields and values. Metadata is reserved for private usage by the user and for plugins. The Beancount core does not read metadata and change its processing based on it. However, plugins may define special metadata values that affect what they produce. That being said, there are a few special metadata fields produced by the Beancount processing: filename: A string, the name of the file the transaction was read from (or the plugin that created it) lineno: An integer, the line number from the file where the transaction was read from. tolerances : A dict of currency to Decimal, the tolerance values used in balancing the transaction. residual (on postings): A boolean, true if the posting has been added to automatically absorb rounding error. There may be a few more produced in the future but in any case, the core should not read any metadata and affect its behavior as a consequence. (I should probably create a central registry or location where all the special values can be found in one place.)","title":"Metadata is for User Data"},{"location":"beancount_design_doc.html#overview-of-the-codebase","text":"All source code lives under a \u201c beancount \u201d Python package. It consists of several packages with well-defined roles, the dependencies between which are enforced strictly. Beancount source code packages and approximate dependencies between them. At the bottom, we have a beancount.core package which contains the data structures used to represent transactions and other directives in memory. This contains code for accounts, amounts, lots, positions, and inventories. It also contains commonly used routines for processing streams of directives. One level up, the beancount.parser package contains code to parse the Beancount language and a printer that can produce the corresponding text given a stream of directives. It should be possible to convert between text and data structure and round-trip this process without loss. At the root of the main package is a beancount.loader module that coordinates everything that has to be done to load a file in memory. This is the main entry point for anyone writing a script for Beancount, it always starts with loading a stream of entries. This calls the parser, runs some processing code, imports the plugins and runs them in order to produce the final stream of directives which is used to produce reports. The beancount.plugins package contains implementations of various plugins. Each of these plugin modules are independent from each other. Consult the docstrings in the source code to figure out what each of these does. Some of these are prototypes of ideas. There is a beancount.ops package which contains some high-level processing code and some of the default code that always runs by default that is implemented as plugins as well (like padding using the Pad directive). This package needs to be shuffled a bit in order to clarify its role. On top of this, reporting code calls modules from those packages. There are four packages which contain reporting code, corresponding to the Beancount reporting tools: beancount.reports : This package contains code specialized to produce different kinds of outputs (invoked by bean-report). In general I\u2019d like to avoid defining custom routines to produce desired outputs and use the SQL query tool to express grouping & aggregation instead, but I think there will always be a need for at least some of these. The reports are defined in a class hierarchy with a common interface and you should be able to extend them. Each report supports some subset of a number of output formats. beancount.query : This package contains the implementation of a query language and interactive shell (invoked by bean-query) that allows you to group and aggregate positions using an SQL-like DSL. This essentially implements processing over an in-memory table of Posting objects, with functions defined over Amount, Position and Inventory objects. beancount.web : This package contains all the source code for the default web interface (invoked by bean-web). This is a simple Bottle application that serves many of the reports from beancount.report to HTML format, running on your machine locally. The web interface provides simple views and access to your data. (It stands to be improved greatly, it\u2019s in no way perfect.) beancount.projects : This package contains various custom scripts for particular applications that I\u2019ve wanted to share and distribute. Wherever possible, I aim to fold these into reports. There are no scripts to invoke these; you should use \u201c python3 -m beancount.projects. \u201d to run them. There is a beancount.scripts package that contains the \u201cmain\u201d programs for all the scripts under the bin directory . Those scripts are simple launchers that import the corresponding file under beancount.scripts. This allows us to keep all the source code under a single directory and that makes it easier to run linters and other code health checkers on the code\u2014it\u2019s all in one place. Finally, there is a beancount.utils package which is there to contain generic reusable random bits of utility code. And there is a relatively unimportant beancount.docs package that contains code used by the author just to produce and maintain this documentation (code that connects to Google Drive). Enforcing the dependency relationships between those packages is done by a custom script .","title":"Overview of the Codebase"},{"location":"beancount_design_doc.html#core-data-structures","text":"This section describes the basic data structures that are used as building blocks to represent directives. Where possible I will describe the data structure in conceptual terms. (Note for Ledger users : This section introduces some terminology for Beancount; look here if you\u2019re interested to contrast it with concepts and terminology found in Ledger.)","title":"Core Data Structures"},{"location":"beancount_design_doc.html#number","text":"Numbers are represented using decimal objects, which are perfectly suited for this. The great majority of numbers entered in accounting systems are naturally decimal numbers and binary representations involve representational errors which cause many problems for display and in precision. Rational numbers avoid this problem, but they do not carry the limited amount of precision that the user intended in the input. We must deal with tolerances explicitly. Therefore, all numbers should use Python\u2019s \u201c decimal.Decimal \u201d objects. Conveniently, Python v3.x supports a C implementation of decimal types natively (in its standard library; this used to be an external \u201ccdecimal\u201d package to install but it has been integrated in the C/Python interpreter). The default constructor for decimal objects does not support some of the input syntax we want to allow, such as commas in the integer part of numbers (e.g., \u201c278 , 401.35 USD\u201d) or initialization from an empty string. These are important cases to handle. So I provide a special constructor to accommodate these inputs: beancount.core.number.D() . This is the only method that should be used to create decimal objects: from beancount.core.number import D \u2026 number = D(\"2,402.21\") I like to import the \u201c D \u201d symbol by itself (and not use number.D ). All the number-related code is located under beancount.core.number . Some number constants have been defined as well: ZERO , ONE , and HALF . Use those instead of explicitly constructed numbers (such as D(\"1\") ) as it makes it easier to grep for such initializations.","title":"Number"},{"location":"beancount_design_doc.html#commodity","text":"A commodity , or currency (I use both names interchangeably in the code and documentation) is a string that represents a kind of \u201cthing\u201d that can be stored in accounts. In the implementation, it is represented as a Python str object (there is no module with currency manipulation code). These strings may only contain capital letters and numbers and some special characters (see the lexer code). Beancount does not predefine any currency names or categories\u2014all currencies are created equal. Really. It genuinely does not know anything special about dollars or euros or yen or anything else. The only place in the source code where there is a reference to those is in the tests. There is no support for syntax using \u201c$\u201d or other currency symbols; I understand that some users may want this syntax, but in the name of keeping the parser very simple and consistent I choose not to provide that option. Moreover, Beancount does not make a distinction between commodities which represent \"money\" and others that do not (such as shares, hours, etc.). These are treated the same throughout the system. It also does not have the concept of a \"home\" currency 2 ; it's a genuinely multi-currency system. Currencies need not be defined explicitly in the input file format; you can just start using them in the file and they will be recognized as such by their unique syntax (the lexer recognizes and emits currency tokens). However, you may declare some using a Commodity directive. This is only provided so that a per-commodity entity exists upon which the user can attach some metadata, and some reports and plugins are able to find and use that metadata.","title":"Commodity"},{"location":"beancount_design_doc.html#account","text":"An account is basically just the name of a bucket associated with a posting and is represented as a simple string (a Python str object). Accounts are detected and tokenized by the lexer and have names with at least two words separated by a colon (\":\"). Accounts don\u2019t have a corresponding object type; we just refer to them by their unique name string (like filenames in Python). When per-account attributes are needed, we can extract the Open directives from the stream of entries and find the one corresponding to the particular account we\u2019re looking for. Similar to Python\u2019s os.path module, there are some routines to manipulate account names in beancount.core.account and the functions are named similarly to those of the os.path module. The first component of an account\u2019s name is limited to one of five types: Assets Liabilities Equity Income Expenses The names of the account types as read in the input syntax may be customized with some \"option\" directives, so that you can change those names to another language, or even just rename \"Income\" to \"Revenue\" if you prefer that. The beancount.core.account_types module contains some helpers to deal with these. Note that the set of account names forms an implicit hierarchy. For example, the names: Assets:US:TDBank:Checking Assets:US:TDBank:Savings implicitly defines a tree of nodes with parent nodes \"Assets\", \"US\", \"TDBank\" with two leaf nodes \"Checking\" and \"Savings\". This implicit tree is never realized during processing, but there is a Python module that allows one to do this easily (see beancount.core.realization ) and create linearized renderings of the tree.","title":"Account"},{"location":"beancount_design_doc.html#flag","text":"A \u201cflag\u201d is a single-character string that may be associated with Transactions and Postings to indicate whether they are assumed to be correct (\"reconciled\") or flagged as suspicious. The typical value used on transaction instances is the character \u201c*\u201d. On Postings, it is usually left absent (and set to a None).","title":"Flag"},{"location":"beancount_design_doc.html#amount","text":"An Amount is the combination of a number and an associated currency, conceptually: Amount = (Number, Currency) Amount instances are used to represent a quantity of a particular currency (the \u201cunits\u201d) and the price on a posting. A class is defined in beancount.core.amount as a simple tuple-like object. Functions exist to perform simple math operations directly on instances of Amount. You can also create Amount instance using amount.from_string() , for example: value = amount.from_string(\"201.32 USD\")","title":"Amount"},{"location":"beancount_design_doc.html#cost","text":"A Cost object represents the cost basis for a particular lot of a commodity. Conceptually, it is Cost = (Number, Currency, Date, Label) The number and currency is that of the cost itself, not of the commodity. For example, if you bought 40 shares of AAPL stock at 56.78 USD, the Number is a \u201c56.78\u201d decimal and the Currency is \u201cUSD.\u201d For example: Cost(Decimal(\"56.78\"), \"USD\", date(2012, 3, 5), \"lot15\") The Date is the acquisition date of the corresponding lot (a datetime.date object). This is automatically attached to the Cost object when a posting augments an inventory\u2014the Transaction\u2019s date is automatically attached to the Cost object\u2014or if the input syntax provides an explicit date override. The Label can be any string. It is provided as a convenience for a user to refer to a particular lot or disambiguate similar lots. On a Cost object, the number, currency and date attributes are always set. If the label is unset, it has a value of \u201cNone.\u201d","title":"Cost"},{"location":"beancount_design_doc.html#costspec","text":"In the input syntax, we allow users to provide as little information as necessary in order to disambiguate between the lots contained in the inventory prior to posting. The data provided filters the set of matching lots to an unambiguous choice, or to a subset from which an automated booking algorithm will apply (e.g., \u201cFIFO\u201d). In addition, we allow the user to provide either the per-unit cost and/or the total-cost. This convenience is useful to let Beancount automatically compute the per-unit cost from a total of proceeds. CostSpec = (Number-per-unit, Number-total, Currency, Date, Label, Merge) Since any of the input elements may be omitted, any of the attributes of a CostSpec may be left to None. If a number is missing and necessary to be filled in, the special value \u201cMISSING\u201d will be set on it. The Merge attribute it used to record a user request to merge all the input lots before applying the transaction and to merge them after. This is the method used for all transactions posted to an account with the \u201cAVERAGE\u201d booking method.","title":"CostSpec"},{"location":"beancount_design_doc.html#position","text":"A position represents some units of a particular commodity held at cost. It consists simply in Position = (Units, Cost) Units is an instance of Amount , and Cost is an instance of Cost , or a null value if the commodity is not held at cost. Inventories contain lists of Position instances. See its definition in beancount.core.position .","title":"Position"},{"location":"beancount_design_doc.html#posting","text":"Each Transaction directive is composed of multiple Postings (I often informally refer to these in the code and on the mailing-list as the \u201clegs\u201d of a transaction). Each of these postings is associated with an account, a position and an optional price and flag: Posting = (Account, Units, Cost-or-CostSpec, Price, Flag, Metadata) As you can see, a Posting embeds its Position instance 3 . The Units is an Amount , and the \u2018cost\u2019 attribute refers to either a Cost or a CostSpec instance (the parser outputs Posting instances with an CostSpec attribute which is resolved to a Cost instance by the booking process; see How Inventories Work for details). The Price is an instance of Amount or a null value. It is used to declare a currency conversion for balancing the transaction, or the current price of a position held at cost. It is the Amount that appears next to a \u201c@\u201d in the input. Flags on postings are relatively rare; users will normally find it sufficient to flag an entire transaction instead of a specific posting. The flag is usually left to None; if set, it is a single-character string. The Posting type is defined in beancount.core.data , along with all the directive types.","title":"Posting"},{"location":"beancount_design_doc.html#inventory","text":"An Inventory is a container for an account\u2019s balance in various lots of commodities. It is essentially a list of Position instances with suitable operations defined on it. Conceptually you may think of it as a mapping with unique keys: Inventory = [Position1, Position2, Position3, \u2026 , PositionN] Generally, the combination of a position\u2019s ( Units.Currency, Cost) is kept unique in the list, like the key of a mapping. Positions for equal values of currency and cost are merged together by summing up their Units.Number and keeping a single position for it. And simple positions are mixed in the list with positions held at cost. The Inventory is one of the most important and oft-used object in Beancount\u2019s implementation, because it is used to sum the balance of one or more accounts over time. It is also the place where the inventory reduction algorithms get applied to, and traces of that mechanism can be found there. The \u201c How Inventories Work \u201d document provides the full detail of that process. For testing, you can create initialized instances of Inventory using inventory.from_string() . All the inventory code is written in beancount.core.inventory .","title":"Inventory"},{"location":"beancount_design_doc.html#about-tuples-mutability","text":"Despite the program being written in a language which does not make mutability \u201cdifficult by default\u201d, I designed the software to avoid mutability in most places. Python provides a \u201c collections.namedtuple \u201d factory that makes up new record types whose attributes cannot be overridden. Well\u2026 this is only partially true: mutable attributes of immutable tuples can be modified. Python does not provide very strong mechanisms to enforce this property. Regardless, functional programming is not so much an attribute of the language used to implement our programs than of the guarantees we build into its structure. A language that supports strong guarantees helps to enforce this. But if, even by just using a set of conventions, we manage to mostly avoid mutability, we have a mostly functional program that avoids most of the pitfalls that occur from unpredictable mutations and our code is that much easier to maintain. Programs with no particular regard for where mutation occurs are most difficult to maintain. By avoiding most of the mutation in a functional approach, we avoid most of those problems. Most of the data structures used in Beancount are namedtuples , which make the modification of their attributes inconvenient on purpose. Most of the code will attempt to avoid mutation, except for local state (within a function) or in a narrow scope that we can easily wrap our head around. Where mutation is possible, by convention I try to avoid it by default. When we do mutation, I try to document the effects. I avoid the creation of classes which mutate their internal state, except for a few cases (e.g. the web application). I prefer functions to objects and where I define classes I mostly avoid inheritance. These properties are especially true for all the small objects: Amount, Lot, Position, Posting objects, and all the directives types from beancount.core.data . On the other hand, the Inventory object is nearly always used as an accumulator and does allow the modification of its internal state (it would require a special, persistent data structure to avoid this). You have to be careful how you share access to Inventory objects\u2026 and modify them, if you ever do. Finally, the loader produces lists of directives which are all simple namedtuple objects. These lists form the main application state. I\u2019ve avoided placing these inside some special container and instead pass them around explicitly, on purpose. Instead of having some sort of big \u201capplication\u201d object, I\u2019ve trimmed down all the fat and all your state is represented in two things: A dated and sorted list of directives which can be the subject of stream processing and a list of constant read-only option values. I think this is simpler. I credit my ability to make wide-ranging changes to a mid-size Python codebase to the adoption of these principles. I would love to have types in order to safeguard against another class of potential errors, and I plan to experiment with Python 3.5\u2019s upcoming typing module .","title":"About Tuples & Mutability"},{"location":"beancount_design_doc.html#summary","text":"The following diagram explains how these objects relate to each other, starting from a Posting. For example, to access the number of units of a postings you could use posting.units.number For the cost currency: posting.cost.currency You can print out the tuples in Python to figure out their structure.","title":"Summary"},{"location":"beancount_design_doc.html#previous-design","text":"For the sake of preservation, if you go back in time in the repository, the structure of postings was deeper and more complex. The new design reflects a flatter and simpler version of it. Here is what the old design used to look like:","title":"Previous Design"},{"location":"beancount_design_doc.html#directives","text":"The main production from loading a Beancount input file is a list of directives . I also call these entries interchangeably in the codebase and in the documents. There are directives of various types: Transaction Balance & Pad Open & Close Commodity Note Event Price Document In a typical Beancount input file, the great majority of entries will be Transactions and some Balance assertions. There will also be Open & perhaps some Close entries. Everything else is optional. Since these combined with a map of option values form the entire application state, you should be able to feed those entries to functions that will produce reports. The system is built around this idea of processing a stream of directives to extract all contents and produce reports, which are essentially different flavors of filtering and aggregations of values attached to this stream of directives.","title":"Directives"},{"location":"beancount_design_doc.html#common-properties","text":"Some properties are in common to all the directives: Date. A datetime.date object. This is useful and consistent. For a Transaction, that\u2019s the date at which it occurs. For an Open or Close directive, that\u2019s the date at which the account was opened or closed. For a Pad directive, that\u2019s the date at which to insert a transaction. For a Note or Document, it is the date at which to insert the note in the stream of postings of that account. For an Event, it is the date at which it occurs. For a Commodity directive which essentially provides a per-commodity hanging point for commodity-related metadata, the date isn\u2019t as meaningful; I choose to date those on the day the commodity was created. Meta . All the directives have metadata attribute (a Python dict object). The purpose of metadata is to allow the user to hang any kind of ancillary data they want and then use this in scripts or queries. Posting instances also have a metadata attribute.","title":"Common Properties"},{"location":"beancount_design_doc.html#transactions","text":"The Transaction directive is the subject of Beancount and is by far the most common directive found in input files and is worth of special attention. The function of a bookkeeping system is to organize the Postings attached to Transactions in various ways. All the other types of entries occupy supporting roles in our system. A Transaction has the following additional fields.","title":"Transactions"},{"location":"beancount_design_doc.html#flag_1","text":"The single-character flag is usually there to replace the \u201ctxn\u201d keyword (Transaction is the only directive which allows being entered without entering its keyword). I\u2019ve been considering changing the syntax definition somewhat to allow not entering the flag nor the keyword, because I\u2019d like to eventually support that. Right now, either the flag or keyword is required. The flag attribute may be set to None.","title":"Flag"},{"location":"beancount_design_doc.html#payee-narration","text":"The narration field is a user-provided description of the transaction, such as \"Dinner with Mary-Ann.\" You can put any information in this string. It shows up in the journal report. Oftentimes it is used to enrich the transaction with context that cannot be imported automatically, such as \"transfer from savings account to pay for car repairs.\" The payee name is optional, and exists to describe the entity with which the transaction is conducted, such as \"Whole Foods Market\" or \"Shell.\" Note that I want to be able to produce reports for all transactions associated with a particular payee, so it would be nice to enter consistent payee names. The problem with this is that the effort to do this right is sometimes too great. Either better tools or looser matching over names is required in order to make this work. The input syntax also allows only a single string to be provided. By default this becomes the narration, but I\u2019ve found that in practice it can be useful to have just the payee. It\u2019s just a matter of convenience. At the moment, if you want to enter just the payee string you need to append an empty narration string. This should be revisited at some point.","title":"Payee & Narration"},{"location":"beancount_design_doc.html#tags","text":"Tags are sets of strings that can be used to group sets of transactions (or set to None if there are no tags). A view of this subset of transactions can then be generated, including all the usual reports (balance sheet, income statement, journals, etc.). You can tag transactions for a variety of purposes; here are some examples: All transactions during a particular travel might be tagged to later summarize your trip expenses. Those transactions will usually occur around the same date and therefore there is convenient syntax used to automatically tag many transactions that are declared together in a file. Transactions related to a particular project to be accomplished. I took some classes in an online program once, and tagged all related expenses to it. I use this to track all my immigration-related expenses for a particular stage (e.g. green card). Declaring a group of expenses to be paid back by an employer (or your own company) later on. Expenses related to moving between locations. Typical tag names that I use for tags look like #trip-israel-2012 , #conference-siggraph , and #course-cfa . In general, tags are useful where adding a sub-accounts won't do. This is often the case where a group of related expenses are of differing types, and so they would not belong to a single account. Given the right support from the query tools, they could eventually be subsumed by metadata\u2014I have been considering converting tags into metadata keys with a boolean value of True, in order to remove unnecessary complexity.","title":"Tags"},{"location":"beancount_design_doc.html#links","text":"Links are a unique set of strings or None , and in practice will be usually empty for most transactions. They differ from tags only in purpose. Links are there to chain together a list of related transactions and there are tools used to list, navigate and balance a subset of transactions linked together. They are a way for a transaction to refer to other transactions. They are not meant to be used for summarizing. Examples include: transaction-ids from trading accounts (these often provide an id to a \"related\" or \"other\" transaction); account entries associated with related corrections, for example a reversed fee following a phone call could be linked to the original invalid fee from several weeks before; the purchase and sale of a home, and related acquisition expenses. In contrast to tags, their strings are most often unique numbers produced by the importers. No views are produced for links; only a journal of a particular links transactions can be produced and a rendered transaction should be accompanied by an actual \"link\" icon you can click on to view all the other related transactions.","title":"Links"},{"location":"beancount_design_doc.html#postings","text":"A list of Postings are attached to the Transaction object. Technically this list object is mutable but in practice we try not to modify it. A Posting can ever only be part of a single Transaction. Sometimes different postings of the same transaction will settle at different dates in their respective accounts, so eventually we may allow a posting to have its own date to override the transaction's date, to be used as documentation; in the simplest version, we enforce all transactions to occur punctually, which is simple to understand and was not found to be a significant problem in practice. Eventually we might implement this by implicitly converting Transactions with multiple dates into multiple Transactions and using some sort of transfer account to hold the pending balance in-between dates. See the associated proposal for details.","title":"Postings"},{"location":"beancount_design_doc.html#balancing-postings","text":"The fundamental principle of double-entry book-keeping is enforced in each of the Transaction entries: the sum of all postings must be zero. This section describes the specific way in which we do this, and is the key piece of logic that allow the entire system to balance nicely. This is also one of the few places in this system where calculations go beyond simple filtering and aggregation. As we saw earlier, postings are associated with A position, which is a number of units of a lot (which itself may or may not have a cost) An optional conversion price Given this, how do we balance a transaction? Some terminology is in order: for this example posting: Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD Units. The number of units is the number of the position, or \u201c50\u201d. Currency. The commodity of the lot, that is, \u201cHOOL\u201d. Cost. The cost of this position is the number of units times the per-unit cost in the cost currency, that is 50 x 700 = 35000 USD. (Total) Price. The price of a unit is the attached price amount, 920 USD. The total price of a position is its number of units times the conversion price, in this example 50 x 920 = 46000 USD. Weight. The amount used to balance each posting in the transaction: If the posting has an associated cost, we calculate the cost of the position (regardless of whether a price is present or not); If the posting has no associated cost but has a conversion price, we convert the position into its total price; Finally, if the posting has no associated cost nor conversion price, the number of units of the lot are used directly. Balancing is really simple: each of the Posting's positions are first converted into their weight. These amounts are then grouped together by currency, and the final sum for each currency is asserted to be close to zero, that is, within a small amount of tolerance (as inferred by a combination of by the numbers in the input and the options ). The following example is one of case (2), with a price conversion and a regular leg with neither a cost nor a price (case 3): 2013-07-22 * \"Wired money to foreign account\" Assets:Investment:HOOL -35350 CAD @ 1.01 USD ;; -35000 USD (2) Assets:Investment:Cash 35000 USD ;; 35000 USD (3) ;;------------------ ;; 0 USD In the next example, the posting for the first leg has a cost, the second posting has neither: 2013-07-22 * \"Bought some investment\" Assets:Investment:HOOL 50 HOOL {700 USD} ;; 35000 USD (1) Assets:Investment:Cash -35000 USD ;; -35000 USD (3) ;;------------------ ;; 0 USD Here's a more complex example with both a price and a cost; the price here is ignored for the purpose of balance and is used only to enter a data-point in the internal price database: 2013-07-22 * \"Sold some investment\" Assets:Investment:HOOL -50 HOOL {700 USD} @ 920 USD ;; -35000 USD (1) Assets:Investment:Cash 46000 USD ;; 46000 USD (3) Income:CapitalGains -11000 USD ;; -11000 USD (3) ;;------------------ ;; 0 USD Finally, here's an example with multiple commodities: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking 4585.38 USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib 540.00 IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation -4.62 VACHR Here there are three groups of weights to balance: USD: (4485.38) + (-25.38) + (-5000) + (540) ~= 0 USD IRAUSD: (540.00) + (-540.00) ~= 0 IRAUSD VACHR: (4.62) + (-4.62) ~= 0 VACHR","title":"Balancing Postings"},{"location":"beancount_design_doc.html#elision-of-amounts","text":"Transactions allow for at most one posting to be elided and automatically set; if an amount was elided, the final balance of all the other postings is attributed to the balance. With the inventory booking proposal , it should be extended to be able to handle more cases of elision. An interesting idea would be to allow the elided postings to at least specify the currency they're using. This would allow the user to elide multiple postings, like this: 2013-07-05 * \"COMPANY INC PAYROLL\" Assets:US:TD:Checking USD Income:US:Company:GroupTermLife -25.38 USD Income:US:Company:Salary -5000.00 USD Assets:US:Vanguard:Cash 540.00 USD Assets:US:Federal:IRAContrib -540.00 IRAUSD Expenses:Taxes:US:Federal:IRAContrib IRAUSD Assets:US:Company:Vacation 4.62 VACHR Income:US:Company:Vacation VACHR","title":"Elision of Amounts"},{"location":"beancount_design_doc.html#stream-processing","text":"An important by-product of representing all state using a single stream of directives is that most of the operations in Beancount can be implemented by simple functions that accept the list of directives as input and output a modified list of directives. For example, The \u201cPad\u201d directive is implemented by processing the stream of transactions, accumulating balances, inserting a new padding transaction after the Pad directive when a balance assertion fails. Summarization operations (open books, close books, clear net income to equity) are all implemented this way, as functional operators on the list of streams. For example, opening the books at a particular date is implemented by summing all balances before a certain date and replacing those transactions by new transactions that pull the final balance from an Equity account. Closing the books only involves moving the balances of income statement account to Equity and truncation the list of transactions to that date. This is very elegant\u2014the reporting code can be oblivious to the fact that summarization has occurred. Prices on postings held with a cost are normally ignored. The \u201cimplicit prices\u201d option is implemented using a plugin which works its magic by processing the stream of operations and inserting otherwise banal Price directives automatically when such a posting is found. Many types of verifications are also implemented this way; the sellgains plugin verifies that non-income balances check against the converted price of postings disregarding the cost. This one does not insert any new directives, but may generate new errors. These are just some examples. I\u2019m aiming to make most operations work this way. This design has proved to be elegant, but also surprisingly flexible and it has given birth to the plugins system available in Beancount; you might be surprised to learn that I had not originally thought to provide a plugins system... it just emerged over time teasing abstractions and trying to avoid state in my program. I am considering experimenting with weaving the errors within the stream of directives by providing a new \u201cError\u201d directive that could be inserted by stream processing functions. I\u2019m not sure whether this would make anything simpler yet, it\u2019s just an idea at this point [July 2015].","title":"Stream Processing"},{"location":"beancount_design_doc.html#stream-invariants","text":"The stream of directives comes with a few guarantees you can rest upon: All the directives are ordered by date. Because many dates have multiple directives, in order to have a stable sorting order the line number in the file is used as a secondary sort key. All uses of an account is preceded in the stream by a corresponding Open entry. If the input did not provide one, an Open directive is automatically synthesized. All Balance directives precede any other directive on a particular day. This is enforced in order to make the processing of balance assertions straightforward and to emphasize that their semantics is to occur at the beginning of the day.","title":"Stream Invariants"},{"location":"beancount_design_doc.html#loader-processing-order","text":"The process of loading a list of entries from an input file is the heart of the project. It is important to understand it in order to understand how Beancount works. Refer to the diagram below. It consists of A parsing step that reads in all the input files and produces a stream of potentially incomplete directives. The processing of this stream of entries by built-in and user-specified plugins. This step potentially produces new error objects. A final validation step that ensures the invariants have not been broken by the plugins. The beancount.loader module orchestrates this processing. In the code and documents, I\u2019m careful to distinguish between the terms \u201cparsing\u201d and \u201cloading\u201d carefully. These two concepts are distinct. \u201cParsing\u201d is only a part of \u201cloading.\u201d The user-specified plugins are run in the order they are provided in the input files. Raw mode. Some users have expressed a desire for more control over which built-in plugins are run and in which order they are to be run, and so enabling the \u201craw\u201d option disables the automatic loading of all built-in plugins (you have to replace those with explicit \u201cplugin\u201d directives if you want to do that). I\u2019m not sure if this is going to be useful yet, but it allows for tighter control over the loading process for experimentation.","title":"Loader & Processing Order"},{"location":"beancount_design_doc.html#loader-output","text":"The parser and the loader produce three lists: entries: A list of directive tuples from beancount.core.data. This is the stream of data which should consists mostly of Transaction and Balance instances. As much as possible, all data transformations are performed by inserting or removing entries from this list. Throughout the code and documents, I refer to these as entries or directives interchangeably. errors: A list of error objects. In Beancount I don\u2019t use exceptions to report errors; rather, all the functions produce error objects and they are displayed at the most appropriate time. (These deserve a base common type but for now the convention they respect is that they all provide a source (filename, line-no), message and entry attributes.) options_map: A dict of the options provided in the file and derived during parsing. Though this is a mutable object, we never modify it once produced by the parser.","title":"Loader Output"},{"location":"beancount_design_doc.html#parser-implementation","text":"The Beancount parser is a mix of C and Python 3 code. This is the reason you need to compile anything in the first place. The parser is implemented in C using a flex tokenizer and a Bison parser generator, mainly for performance reasons. I chose the C language and these crufty old GNU tools because they are portable and will work everywhere. There are better parser generators out there, but they would either require my users to install more compiler tools or exotic languages. The C language is a great common denominator. I want Beancount to work everywhere and to be easy to install. I want this to make it easy on others, but I also want this for myself, because at some point I\u2019ll be working on other projects and the less dependencies I have the lighter it will be to maintain aging software. Just looking ahead. (That\u2019s also why I chose to use Python instead of some of the other languages I\u2019m more naturally attracted to these days (Clojure, Go, ML); I need Beancount to work... when I\u2019m counting the beans I don\u2019t have time to debug problems. Python is careful about its changes and it will be around for a long long time as is.) There is a lexer file lexer.l written in flex and a Bison grammar in grammar.y. These tools are used to generate the corresponding C source code (lexer.h/.c and grammar.h/.c). These, along with some hand-written C code that defines some interface functions for the module (parser.h/.c), are compiled into an extension module (_parser.so). Eventually we could consider creating a small dependency rule in setup.py to invoke flex and Bison automatically but at the moment, in order to minimize the installation burden, I check the generated source code in the repository (lexer.h/c and grammar.h/c). The interaction between the Python and C code works like this: You import beancount.parser.parser and invoke either parse_file() or parse_string(). This uses code in grammar.py to create a Builder object which essentially provides callbacks in Python to process grammar rules. parser.py calls a C function in the extension module with the Builder object. That C code sets up flex to be able to read the input file or string then hands over control to the parser by calling the generated C function \u201cyyparse()\u201d. yyparse() is the parser and will fetch input tokens from the lexer C code using successive calls to \u201cyylex()\u201d and attempt to reduce rules. When a rule or token is successfully reduced, the C code invokes callback methods on the Builder object you provided. The parser\u2019s rules communicate between each other by passing around \u201cPyObject*\u201d instances, so that code has to be a little careful about correctly handling reference counting. This allows me to run Python code on rule reduction, which makes it really easy to customize parser behaviour yet maintain the performance of the grammar rule processing in C. Note that the grammar.py Builder derives from a similar Builder class defined in lexer.py, so that lexer tokens recognized calls methods defined in the lexer.py file to create them and parser rule correspondingly calls methods from grammar.py\u2019s Builder. This isolation is not only clean, it also makes it possible to just use the lexer to tokenize a file from Python (without parsing) for testing the tokenizer; see the tests where this is used if you\u2019re curious how this works. I just came up with this; I haven\u2019t seen this done anywhere else. I tried it and the faster parser made a huge difference compared to using PLY so I stuck with that. I quite like the pattern, it\u2019s a good compromise that offers the flexibility of a parser generator yet allows me to handle the rules using Python. Eventually I\u2019d like to move just some of the most important callbacks to C code in order to make this a great deal faster (I haven\u2019t done any real performance optimizations yet). This has worked well so far but for one thing: There are several limitations inherent in the flex tokenizer that have proved problematic. In particular, in order to recognize transaction postings using indent I have had to tokenize the whitespace that occurs at the beginning of a line. Also, single-characters in the input should be parsed as flags and at the moment this is limited to a small subset of characters. I\u2019d like to eventually write a custom lexer with some lookahead capabilities to better deal with these problems (this is easy to do).","title":"Parser Implementation"},{"location":"beancount_design_doc.html#two-stages-of-parsing-incomplete-entries","text":"At the moment, the parser produces transactions which may or may not balance. The validation stage which runs after the plugins carries out the balance assertions. This degree of freedom is allowed to provide the opportunity for users to enter incomplete lists of postings and for corresponding plugins to automate some data entry and insert completing postings. However, the postings on the transactions are always complete objects, with all their expected attributes set. For example, a posting which has its amount elided in the input syntax will have a filled in position by the time it comes out of the parser. We will have to loosen this property when we implement the inventory booking proposal , because we want to support the elision of numbers whose values depend on the accumulated state of previous transactions. Here is an obvious example. A posting like this 2015-04-03 * \"Sell stock\" Assets:Investments:AAPL -10 AAPL {} ... should succeed if at the beginning of April 3rd the account contains exactly 10 units of AAPL, in either a single or multiple lots. There is no ambiguity: \u201csell all the AAPL,\u201d this says. However, the fact of its unambiguity assumes that we\u2019ve computed the inventory balance of that account up until April 3rd\u2026 So the parser will need to be split into two phases: A simple parsing step that produces potentially incomplete entries with missing amounts A separate step for the interpolation which will have available the inventory balances of each account as inputs. This second step is where the booking algorithms (e.g., FIFO) will be invoked from. See the diagram above for reference. Once implemented, everything else should be the same.","title":"Two Stages of Parsing: Incomplete Entries"},{"location":"beancount_design_doc.html#the-printer","text":"In the same package as the parser lives a printer. This isolates all the functionality that deals with the Beancount \u201clanguage\u201d in the beancount.parser package: the parser converts from input syntax to data structure and the printer does the reverse. No code outside this package should concern itself with the Beancount syntax. At some point I decided to make sure that the printer was able to round-trip through the parser, that is, given a stream of entries produced by the loader, you should be able to convert those to text input and parse them back in and the resulting set of entries should be the same (outputting the re-read input back to text should produce the same text), e.g., Note that the reverse isn\u2019t necessarily true: reading an input file and processing it through the loader potentially synthesizes a lot of entries (thanks to the plugins), so printing it back may not produce the same input file (even ignoring reordering and white space concerns). This is a nice property to have. Among other things, it allows us to use the parser to easily create tests with expected outputs. While there is a test that attempts to protect this property, some of the recent changes break it partially: Metadata round-tripping hasn\u2019t been tested super well. In particular, some of the metadata fields need to be ignored (e.g., filename and lineno). Some directives contain derived data, e.g. the Balance directive has a \u201cdiff_amount\u201d field that contains the difference when there is a failure in asserting a balance. This is used to report errors more easily. I probably need to remove these exceptions at some point because it is the only one of its kind (I could replace it with an inserted \u201cError\u201d directive). This probably needs to get refined a bit at some point with a more complete test (it\u2019s not far as it is).","title":"The Printer"},{"location":"beancount_design_doc.html#uniqueness-hashing","text":"In order to make it possible to compare directives quickly, we support unique hashing of all directives, that is, from each directive we should be able to produce a short and unique id. We can then use these ids to perform set inclusion/exclusion/comparison tests for our unit tests. We provide a base test case class with assertion methods that use this capability . This feature is used liberally throughout our test suites. This is also used to detect and remove duplicates. This feature is optional and enabled by the beancount.plugins.noduplicates plugin. Note that the hashing of directives currently does not include user meta-data .","title":"Uniqueness & Hashing"},{"location":"beancount_design_doc.html#display-context","text":"Number are input with different numbers of fractional digits: 500 -> 0 fractional digits 520.23 -> 2 fractional digits 1.2357 -> 4 fractional digits 31.462 -> 3 fractional digits Often, a number used for the same currency is input with a different number of digits. Given this variation in the input, a question that arises is how to render the numbers consistently in reports? Consider that: We would like that the user not to have to specify the number of fractional digits to use for rendering by default. Rendering numbers may differ depending on the context: most of the time we want to render numbers using their most commonly seen number of digits, rounding if necessary, but when we are rendering conversion rates we would like to render numbers using the maximum number of digits ever seen. The number of digits used tends to vary with the commodity they represent. For example, US dollars will usually render with two digits of precision; the number of units of a mutual fund such as RGAGX might be recorded by your broker with 3 digits of precision. Japanese Yen will usually be specified in integer units. We want to render text report which need to align numbers with varying number of fractional digits together, aligning them by their period or rightmost digits, and possibly rendering a currency thereafter. In order to deal with this thorny problem, I built a kind of accumulator which is used to record all numbers seen from some input and tally statistics about the precisions witnessed for each currency. I call this a DisplayContext . From this object one can request to build a DisplayFormatter object which can be used to render numbers in a particular way. I refer to those objects using the variable names dcontext and dformat throughout the code. The parser automatically creates a DisplayContext object and feeds it all the numbers it sees during parsing. The object is available in the options_map produced by the loader.","title":"Display Context"},{"location":"beancount_design_doc.html#realization","text":"It occurs often that one needs to compute the final balances of a list of filtered transactions, and to report on them in a hierarchical account structure. See diagram below.","title":"Realization"},{"location":"beancount_design_doc.html#_1","text":"For the purpose of creating hierarchical renderings, I provide a process called a \u201c realization .\u201d A realization is a tree of nodes, each of which contains An account name, A list of postings and other entries to that account, and The final balance for that account, A mapping of sub-account names to child nodes. The nodes are of type \u201cRealAccount,\u201d which is also a dict of its children. All variables of type RealAccount in the codebase are by convention prefixed by \u201creal_*\u201d. The realization module provides functions to iterate and produce hierarchical reports on these trees of balances, and the reporting routines all use these. For example, here is a bit of code that will dump a tree of balances of your accounts to the console: import sys from beancount import loader from beancount.core import realization entries, errors, options_map = loader.load_file(\"filename.beancount\") real_root = realization.realize(entries) realization.dump_balances(real_root, file=sys.stdout)","title":""},{"location":"beancount_design_doc.html#the-web-interface","text":"Before the appearance of the SQL syntax to filter and aggregate postings, the only report produced by Beancount was the web interface provided by bean-web. As such, bean-web evolved to be good enough for most usage and general reports.","title":"The Web Interface"},{"location":"beancount_design_doc.html#reports-vs-web","text":"One of the important characteristics of bean-web is that it should just be a thin dispatching shell that serves reports generated from the beancount.reports layer. It used to contain the report rendering code itself, but at some point I began to factor out all the reporting code to a separate package in order to produce reports to other formats, such as text reports and output to CSV. This is mostly finished, but at this time [July 2015] some reports remain that only support HTML output. This is why.","title":"Reports vs. Web"},{"location":"beancount_design_doc.html#client-side-javascript","text":"I would like to eventually include a lot more client-side scripting in the web interface. However, I don\u2019t think I\u2019ll be able to work on this for a while, at least not until all the proposals for changes to the core are complete (e.g., inventory booking improvements, settlement splitting and merging, etc.). If you\u2019d like to contribute to Beancount, improving bean-web or creating your own visualizations would be a great venue.","title":"Client-Side JavaScript"},{"location":"beancount_design_doc.html#the-query-interface","text":"The current query interface is the result of a prototype . It has not yet been subjected to the amount of testing and refinement as the rest of the codebase. I\u2019ve been experimenting with it and have a lot of ideas for improving the SQL language and the kinds of outputs that can be produced from it. I think of it as \u201c70% done\u201d. However, it works, and though some of the reports can be a little clunky to specify, it produces useful results. It will be the subject of a complete rewrite at some point and when I do, I will keep the current implementation for a little while so that existing scripts don\u2019t just break; I\u2019ll implement a v2 of the shell.","title":"The Query Interface"},{"location":"beancount_design_doc.html#design-principles","text":"","title":"Design Principles"},{"location":"beancount_design_doc.html#minimize-configurability","text":"First, Beancount should have as few options as possible. Second, command-line programs should have no options that pertain to the processing and semantic of the input file (options that affect the output or that pertain to the script itself are fine). The goal is to avoid feature creep. Just a few options opens the possibility of them interacting in complex ways and raises a lot of questions. In this project, instead of providing options to customize every possible thing, I choose to bias the design towards providing the best behavior by default, even if that means less behavior. This is the Apple approach to design. I don\u2019t usually favor this approach for my projects, but I chose it for this particular one. What I\u2019ve learned in the process is how far you can get and still provide much of the functionality you originally set out for. Too many command-line options makes a program difficult to use. I think Ledger suffers from this, for instance. I can never remember options that I don\u2019t use regularly, so I prefer to just design without them. Options that affect semantics should live in the file itself (and should be minimized as well). Options provided when running a process should be only to affect this process. Having less options also makes the software much easier to refactor. The main obstacle to evolving a library of software is the number of complex interactions between the codes. Guaranteeing a well-defined set of invariants makes it much easier to later on split up functionality or group it differently. So.. by default I will resist making changes that aren't generic or that would not work for most users. On the other hand, large-scale changes that would generalize well and that require little configurability are more likely to see implementation.","title":"Minimize Configurability"},{"location":"beancount_design_doc.html#favor-code-over-dsls","text":"Beancount provides a simple syntax, a parser and printer for it, and a library of very simple data record types to allow a user to easily write their scripts to process their data, taking advantage of the multitude of libraries available from the Python environment. Should the built-in querying tools fail to suffice, I want it to be trivially easy for someone to build what they need on top of Beancount. This also means that the tools provided by Beancount don\u2019t have to support all the features everyone might ever possibly want. Beancount\u2019s strength should be representational coherence and simplicity rather than the richness of its reporting features. Creating new kinds of reports should be easy; changing the internals should be hard. (That said, it does provide an evolving palette of querying utilities that should be sufficient for most users.) In contrast, the implementation of the Ledger system provides high-level operations that are invoked as \"expressions\" defined in a domain-specific language, expressions which are either provided on the command-line or in the input file itself. For the user, this expression language is yet another thing to learn, and it\u2019s yet another thing that might involve limitations requiring expansion and work\u2026 I prefer to avoid DSLs if possible and use Python\u2019s well understood semantics instead, though this is not always sensible.","title":"Favor Code over DSLs"},{"location":"beancount_design_doc.html#file-format-or-input-language","text":"One may wonder whether Beancount\u2019s input syntax should be a computer language or just a data format. The problem we're trying to solve is essentially that of making it easy for a human being to create a transactions-to-postings-to-accounts-&-positions data structure. What is the difference between a data file format and a simple declarative computer language? One distinction is the intended writer of the file. Is it a human? Or is it a computer? The input files are meant to be manicured by a human, at the very least briefly eyeballed. While we are trying to automate as much of this process as possible via importing code\u2014well, the unpleasant bits, anyway\u2014we do want to ensure that we review all the new transactions that we're adding to the ledger in order to ensure correctness. If the intended writer is a human one could file the input under the computer language rubric (most data formats are designed to be written by computers). We can also judge it by the power of its semantics. Beancount\u2019s language is not one designed to be used to perform computation, that is, you cannot define new \u201cBeancount functions\u201d in it. But it has data types. In this regard, it is more like a file format. Just something to think about, especially in the context of adding new semantics.","title":"File Format or Input Language?"},{"location":"beancount_design_doc.html#grammar-via-parser-generator","text":"The grammar of its language should be compatible with the use of commonly used parser generator tools. It should be possible to parse the language with a simple lexer and some grammar input file. Beancount\u2019s original syntax was modeled after the syntax of Ledger. However, in its attempt to make the amount of markup minimal, that syntax is difficult to parse with regular tools. By making simple changes to the grammar, e.g. adding tokens for strings and accounts and commodities, the Beancount v2 reimplementation made it possible to use a flex/bison parser generator. This has important advantages: It makes it really easy to make incremental changes to the grammar. Using a custom parser without a well-defined grammar can make it quite difficult to prototype syntax changes. It makes it much easier to implement a parser in another computer language. I\u2019d like to eventually be able to parse Beancount input files in other languages. The data structures are simple enough that it should be easy to reimplement the core in a different language. It\u2019s just a lot less code to write and maintain. (Eventually I\u2019d like to create a file format to generate parsers in multiple languages from a single input. That will be done later, once all the major features are implemented.)","title":"Grammar via Parser Generator"},{"location":"beancount_design_doc.html#future-work","text":"","title":"Future Work"},{"location":"beancount_design_doc.html#tagged-strings","text":"At the moment, account names, tags, links and commodities are represented as simple Python strings. I\u2019ve tried to keep things simple. At some point I\u2019d like to refine this a bit and create a specialization of strings for each of these types. I\u2019d need to assess the performance impact of doing that beforehand. I\u2019m not entirely sure how it could benefit functionality yet.","title":"Tagged Strings"},{"location":"beancount_design_doc.html#errors-cleanup","text":"I\u2019ve been thinking about removing the separate \u201cerrors\u201d list and integrating it into the stream of entries as automatically produced \u201cError\u201d entries. This has the advantage that the errors could be rendered as part of the rest of the stream, but it means we have to process the whole list of entries any time we need to print and find errors (probably not a big deal, given how rarely we output errors).","title":"Errors Cleanup"},{"location":"beancount_design_doc.html#conclusion","text":"This document is bound to evolve. If you have questions about the internals, please post them to the mailing-list .","title":"Conclusion"},{"location":"beancount_design_doc.html#references","text":"Nothing in Beancount is inspired from the following docs, but you may find them interesting as well: Accounting for Computer Scientists I have been considering the addition of a very constrained form of non-balancing postings that would preserve the property of transactions otherwise being balanced, whereby the extra postings would not be able to interact (or sum with) regular balancing postings. \u21a9 There is an option called operating_currency but it is only used to provide good defaults for building reports, never in the processing of transactions. It is used to tell the reporting code which commodities should be broken out to have their own columns of balances, for example. \u21a9 In previous version of Beancount this was not true, the Posting used to have a \u2018position\u2019 attribute and composed it. I prefer the flattened design, as many of the functions apply to either a Position or a Posting. Think of a Posting as deriving from a Position, though, technically it does not. \u21a9","title":"References"},{"location":"beancount_history_and_credits.html","text":"Beancount History and Credits \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/history A history of the development of Beancount and credits for contributors.. History of Beancount \uf0c1 John Wiegley's Ledger was the inspiration for the first version of Beancount. His system is where much of the original ideas for this system came from. When I first learned about double-entry bookkeeping and realized that it could be the perfect method to solve many of the tracking problems I was having in counting various installments for my company, and after a quick disappointment in the solutions that were available at the time (including GnuCash, which I could break very easily), I was quickly led to the Ledger website. There, John laid out his vision for a text-based system, in particular, the idea of doing away with credits and debits and just the signs, and the basics of a convenient input syntax which allows you to omit the amount of one of the postings. I got really excited and had various enthusiastic discussions with him about Ledger and how I wanted to use it. There was some cross-pollination of ideas and John was very receptive to proposals for adding new features. I was so intensely curious about bookkeeping that I began writing a Python interface to Ledger. But in the end I found myself rewriting the entire thing in Python\u2013not for dislike of Ledger but rather because it was simple enough that I could do most of it in little time, and immediately add some features I thought would be useful. One reason for doing so was that instead of parsing the input file every time and generating one report to the console, I would parse it once and then serve the various reports from the in-memory database of transactions, requested via a web page. Therefore, I did not need processing speed, so having to use C++ for performance reasons was not necessary anymore, and I chose to just stick with a dynamic language, which allowed me to add many features quickly. This became Beancount version 1, which stood on its own and evolved its own set of experimental features. My dream was to be able to quickly and easily manipulate these transaction objects to get various views and breakdowns of the data. I don't think the first implementation pushed the limits far enough, however; the code was substandard, to be honest\u2014I wrote it really quickly\u2014and making modifications to the system was awkward. In particular, the way I originally implemented the tracking of capital gains was inelegant and involved some manual counting. I was unhappy with this, but it worked. It was also using ugly ad-hoc parser code in order to remain reasonably compatible with Ledger syntax\u2014I thought it would be interesting to be able to share some common input syntax and use either system for validation and maybe even to convert between them\u2013and that made me wary of making modifications to the syntax to evolve new features, so it stabilized for a few years and I lost interest in adding new features. But it was correct and it worked, mostly, so I used the system continuously from 2008 to 2012 to manage my own personal finances, my company's finances, and joint property with my wife, with detailed transactions; this was great. I learned a lot about how to keep books during that time (the cookbook document is meant to help you do the same). In the summer of 2013, I had an epiphany and realized a correct and generalizable way to implement capital gains, how to merge the tracking of positions held at a cost and regular positions, and a set of simple rules for carrying out operations on them sensibly (the design of how inventories work). I also saw a better way to factor out the internal data structures, and decided to break from the constraint of compatibility with Ledger and redesign the input syntax in order to parse the input language using a lex/yacc generator, which would allow me to easily evolve the input syntax without having to deal with parsing issues, and to create ports to other languages more easily. In the process, a much simpler and more consistent syntax emerged, and in a fit of sweat and a few intense weekends I re-implemented the entire thing from scratch, without even looking at the previous version, clean-room. Beancount version 2 was born, much better than the last, modular, and easy to extend with plugins. The result is what I think is an elegant design involving a small set of objects, a design that could easily be a basis for reimplementation in other computer languages. This is described in the accompanying design doc, for those who would have an interest in having a go at it (this would be welcome and I'm expecting this will happen). While Ledger remains an interesting project bubbling with ideas for expressing the problem of bookkeeping, the second version of Beancount proposes a simpler design that leaves out features that are not strictly necessary and aims at maximum usability through a simple web interface and a very small set of command-line options. Since I had more than 5 years worth of real-world usage experience with the first version, I set a goal for myself to remove all the features that I thought weren't actually useful and introduced unnecessary complexity (like virtual transactions, allowing accounts not in the five types, etc.), and to simplify the system as much as possible without compromising on its usability. The resulting language and software were much simpler to use and understand, the resulting data structures are much simpler to use, the processing more \u201cfunctional\u201d in nature, and the internals of Beancount are very modular. I converted all my 6 years worth of input data\u2013thanks to some very simple Python scripts to manipulate the file\u2013and began using the new version exclusively. It is now in a fully functional state. Ledger's syntax implements many features that trigger a lot of implicitly-defined behaviour; I find this confusing and some of the reasons for this are documented in the many improvement proposals. I don\u2019t like command-line options. In contrast, Beancount's design provides a less expressive, lower-level syntax but one that closely matches the generated in-memory data structure, and that is hopefully more explicit in that way. I think both projects have strengths and weaknesses. Despite its popularity, the latest version of Ledger remains with a number of shortcomings in my view. In particular, reductions in positions are not booked at the moment they occur, but rather they appear to simply accumulate and get matched only at display time, using different methods depending on the command-line options. Therefore, it is possible in Ledger to hold positive and negative lots of the same commodity in an account simultaneously. I believe that this can lead to confusing and even incorrect accounting for trading lots. I think this is still being figured out by the Ledger look-alikes community and that they will eventually converge to the same solution I have, or perhaps even figure out a better solution. In the redesign, I separated out configuration directives that I had used for importing and over many iterations eventually figured out an elegant way to mostly automate imports and automatically detect the various input files and convert them into my input syntax. The result is the LedgerHub design doc . LedgerHub is currently implemented and I\u2019m using it exclusively, but has insufficient testing for me to stamp a public release [July 2014]. You are welcome to try it out for yourself. In June and July 2014, I decided to dump seven years\u2019 worth of thinking about command-line accounting in a set of Google Docs and this now forms the basis for the current documentation of Beancount. I hope to be evolving it gradually from here on. Chronology \uf0c1 Ledger was begun in August 2003 http://web.archive.org/web/*/http://www.newartisans.com/ledger/ Beancount was begun in 2008 http://web.archive.org/web/*/furius.ca/beancount HLedger was also begun in 2008 https://github.com/simonmichael/hledger/graphs/contributors?from=2007-01-21&to=2014-09-08&type=c Credits \uf0c1 So far I\u2019ve been contributing all the code to Beancount. Some users have made significant contributions in other ways: Daniel Clemente has been reporting all the issues he came across while using Beancount to manage his company and personal finances. His relentless perseverance and attention to detail has helped me put a focus on fixing the rough corners of Beancount that I knew to avoid myself. After many years of prodding, my old friend Filippo Tampieri has finally decided to convert his trading history in Beancount format. He has contributed a number of sophisticated reviews of my documentation and is working on adding various methods for evaluating returns on assets.","title":"Beancount History and Credits"},{"location":"beancount_history_and_credits.html#beancount-history-and-credits","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/history A history of the development of Beancount and credits for contributors..","title":"Beancount History and Credits"},{"location":"beancount_history_and_credits.html#history-of-beancount","text":"John Wiegley's Ledger was the inspiration for the first version of Beancount. His system is where much of the original ideas for this system came from. When I first learned about double-entry bookkeeping and realized that it could be the perfect method to solve many of the tracking problems I was having in counting various installments for my company, and after a quick disappointment in the solutions that were available at the time (including GnuCash, which I could break very easily), I was quickly led to the Ledger website. There, John laid out his vision for a text-based system, in particular, the idea of doing away with credits and debits and just the signs, and the basics of a convenient input syntax which allows you to omit the amount of one of the postings. I got really excited and had various enthusiastic discussions with him about Ledger and how I wanted to use it. There was some cross-pollination of ideas and John was very receptive to proposals for adding new features. I was so intensely curious about bookkeeping that I began writing a Python interface to Ledger. But in the end I found myself rewriting the entire thing in Python\u2013not for dislike of Ledger but rather because it was simple enough that I could do most of it in little time, and immediately add some features I thought would be useful. One reason for doing so was that instead of parsing the input file every time and generating one report to the console, I would parse it once and then serve the various reports from the in-memory database of transactions, requested via a web page. Therefore, I did not need processing speed, so having to use C++ for performance reasons was not necessary anymore, and I chose to just stick with a dynamic language, which allowed me to add many features quickly. This became Beancount version 1, which stood on its own and evolved its own set of experimental features. My dream was to be able to quickly and easily manipulate these transaction objects to get various views and breakdowns of the data. I don't think the first implementation pushed the limits far enough, however; the code was substandard, to be honest\u2014I wrote it really quickly\u2014and making modifications to the system was awkward. In particular, the way I originally implemented the tracking of capital gains was inelegant and involved some manual counting. I was unhappy with this, but it worked. It was also using ugly ad-hoc parser code in order to remain reasonably compatible with Ledger syntax\u2014I thought it would be interesting to be able to share some common input syntax and use either system for validation and maybe even to convert between them\u2013and that made me wary of making modifications to the syntax to evolve new features, so it stabilized for a few years and I lost interest in adding new features. But it was correct and it worked, mostly, so I used the system continuously from 2008 to 2012 to manage my own personal finances, my company's finances, and joint property with my wife, with detailed transactions; this was great. I learned a lot about how to keep books during that time (the cookbook document is meant to help you do the same). In the summer of 2013, I had an epiphany and realized a correct and generalizable way to implement capital gains, how to merge the tracking of positions held at a cost and regular positions, and a set of simple rules for carrying out operations on them sensibly (the design of how inventories work). I also saw a better way to factor out the internal data structures, and decided to break from the constraint of compatibility with Ledger and redesign the input syntax in order to parse the input language using a lex/yacc generator, which would allow me to easily evolve the input syntax without having to deal with parsing issues, and to create ports to other languages more easily. In the process, a much simpler and more consistent syntax emerged, and in a fit of sweat and a few intense weekends I re-implemented the entire thing from scratch, without even looking at the previous version, clean-room. Beancount version 2 was born, much better than the last, modular, and easy to extend with plugins. The result is what I think is an elegant design involving a small set of objects, a design that could easily be a basis for reimplementation in other computer languages. This is described in the accompanying design doc, for those who would have an interest in having a go at it (this would be welcome and I'm expecting this will happen). While Ledger remains an interesting project bubbling with ideas for expressing the problem of bookkeeping, the second version of Beancount proposes a simpler design that leaves out features that are not strictly necessary and aims at maximum usability through a simple web interface and a very small set of command-line options. Since I had more than 5 years worth of real-world usage experience with the first version, I set a goal for myself to remove all the features that I thought weren't actually useful and introduced unnecessary complexity (like virtual transactions, allowing accounts not in the five types, etc.), and to simplify the system as much as possible without compromising on its usability. The resulting language and software were much simpler to use and understand, the resulting data structures are much simpler to use, the processing more \u201cfunctional\u201d in nature, and the internals of Beancount are very modular. I converted all my 6 years worth of input data\u2013thanks to some very simple Python scripts to manipulate the file\u2013and began using the new version exclusively. It is now in a fully functional state. Ledger's syntax implements many features that trigger a lot of implicitly-defined behaviour; I find this confusing and some of the reasons for this are documented in the many improvement proposals. I don\u2019t like command-line options. In contrast, Beancount's design provides a less expressive, lower-level syntax but one that closely matches the generated in-memory data structure, and that is hopefully more explicit in that way. I think both projects have strengths and weaknesses. Despite its popularity, the latest version of Ledger remains with a number of shortcomings in my view. In particular, reductions in positions are not booked at the moment they occur, but rather they appear to simply accumulate and get matched only at display time, using different methods depending on the command-line options. Therefore, it is possible in Ledger to hold positive and negative lots of the same commodity in an account simultaneously. I believe that this can lead to confusing and even incorrect accounting for trading lots. I think this is still being figured out by the Ledger look-alikes community and that they will eventually converge to the same solution I have, or perhaps even figure out a better solution. In the redesign, I separated out configuration directives that I had used for importing and over many iterations eventually figured out an elegant way to mostly automate imports and automatically detect the various input files and convert them into my input syntax. The result is the LedgerHub design doc . LedgerHub is currently implemented and I\u2019m using it exclusively, but has insufficient testing for me to stamp a public release [July 2014]. You are welcome to try it out for yourself. In June and July 2014, I decided to dump seven years\u2019 worth of thinking about command-line accounting in a set of Google Docs and this now forms the basis for the current documentation of Beancount. I hope to be evolving it gradually from here on.","title":"History of Beancount"},{"location":"beancount_history_and_credits.html#chronology","text":"Ledger was begun in August 2003 http://web.archive.org/web/*/http://www.newartisans.com/ledger/ Beancount was begun in 2008 http://web.archive.org/web/*/furius.ca/beancount HLedger was also begun in 2008 https://github.com/simonmichael/hledger/graphs/contributors?from=2007-01-21&to=2014-09-08&type=c","title":"Chronology"},{"location":"beancount_history_and_credits.html#credits","text":"So far I\u2019ve been contributing all the code to Beancount. Some users have made significant contributions in other ways: Daniel Clemente has been reporting all the issues he came across while using Beancount to manage his company and personal finances. His relentless perseverance and attention to detail has helped me put a focus on fixing the rough corners of Beancount that I knew to avoid myself. After many years of prodding, my old friend Filippo Tampieri has finally decided to convert his trading history in Beancount format. He has contributed a number of sophisticated reviews of my documentation and is working on adding various methods for evaluating returns on assets.","title":"Credits"},{"location":"beancount_language_syntax.html","text":"Beancount Language Syntax \uf0c1 Martin Blais , Updated: April 2016 http://furius.ca/beancount/doc/syntax Introduction Syntax Overview Directives Ordering of Directives Accounts Commodities / Currencies Strings Comments Directives Open Close Commodity Transactions Metadata Payee & Narration Costs and Prices Balancing Rule - The \u201cweight\u201d of postings Reducing Positions Amount Interpolation Tags The Tag Stack Links Balance Assertions Multiple Commodities Lots Are Aggregated Checks on Parent Accounts Before Close Pad Unused Pad Directives Commodities Cost Basis Multiple Paddings Notes Documents Documents from a Directory Prices Prices from Postings Prices on the Same Day Events Query Custom Metadata Options Operating Currencies Plugins Includes What\u2019s Next? Introduction \uf0c1 This is a user's manual to the language of Beancount, the command-line double-entry bookkeeping system. Beancount defines a computer language that allows you to enter financial transactions in a text file and extract various reports from it. It is a generic counting tool that works with multiple currencies, commodities held at cost (e.g., stocks), and even allows you to track unusual things, like vacation hours, air miles and rewards points, and anything else you might want to count, even beans. This document provides an introduction to Beancount\u2019s syntax and some of the technical details needed for one to understand how it carries out its calculations. This document does not provide an introduction to the double-entry method , a motivation , nor examples and guidelines for entering transactions in your input file, nor how to run the tools . These subjects have their own dedicated documents , and it is recommended that you have had a look at those before diving into this user\u2019s manual. This manual covers the technical details for using Beancount. Syntax Overview \uf0c1 Directives \uf0c1 Beancount is a declarative language. The input consists of a text file containing mainly a list of directives , or entries (we use these terms interchangeably in the code and documentation); there is also syntax for defining various options . Each directive begins with an associated date , which determines the point in time at which the directive will apply, and its type , which defines which kind of event this directive represents. All the directives begin with a syntax that looks like this: YYYY-MM-DD \u2026 where YYYY is the year, MM is the numerical month, and DD the numerical date. All digits are required, for example, the 7th of May 2007 should be \u201c2007-05-07\u201d, including its zeros. Beancount supports the international ISO 8601 standard format for dates, with dashes (e.g., \u201c2014-02-03\u201d), or the same ordering with slashes (e.g., \u201c2014/02/03\u201d). Here are some example directives, just to give you an idea of the aesthetics: 2014-02-03 open Assets:US:BofA:Checking 2014-04-10 note Assets:US:BofA:Checking \"Called to confirm wire transfer.\" 2014-05-02 balance Assets:US:BofA:Checking 154.20 USD The end product of a parsed input file is a simple list of these entries, in a data structure. All operations in Beancount are performed on these entries. Each particular directive type is documented in a section below. Ordering of Directives \uf0c1 The order of declaration of the directives is not important. In fact, the entries are re-sorted chronologically after parsing and before being processed. This is an important feature of the language, because it makes it possible for you to organize your input file any way you like without having to worry about affecting the meaning of the directives. Except for transactions, each directive is assumed to occur at the beginning of each day. For example, you could declare an account being opened on the same day as its first transaction: 2014-02-03 open Assets:US:BofA:Checking 2014-02-03 * \"Initial deposit\" Assets:US:BofA:Checking 100 USD Assets:Cash -100 USD However, if you hypothetically closed that account immediately, you could not declare it closed on the same day, you would have to fudge the date forward by declaring the close on 2/4: 2014-02-04 close Assets:US:BofA:Checking This also explains why balance assertions are verified before any transactions that occur on the same date. This is for consistency. Accounts \uf0c1 Beancount accumulates commodities in accounts. The names of these accounts do not have to be declared before being used in the file, they are recognized as \u201caccounts\u201d by virtue of their syntax alone 1 . An account name is a colon-separated list of capitalized words which begin with a letter, and whose first word must be one of five account types: Assets Liabilities Equity Income Expenses Each component of the account names begin with a capital letter or a number and are followed by letters, numbers or dash (-) characters. All other characters are disallowed. Here are some realistic example account names: Assets:US:BofA:Checking Liabilities:CA:RBC:CreditCard Equity:Retained-Earnings Income:US:Acme:Salary Expenses:Food:Groceries The set of all names of accounts seen in an input file implicitly define a hierarchy of accounts (sometimes called a chart-of-accounts ), similarly to how files are organized in a file system. For example, the following account names: Assets:US:BofA:Checking Assets:US:BofA:Savings Assets:US:Vanguard:Cash Assets:US:Vanguard:RGAGX Assets:Receivables implicitly declare a tree of accounts that looks like this: `-- Assets |-- Receivables `-- US |-- BofA | |-- Checking | `-- Savings `-- Vanguard |-- Cash `-- RGAGX We would say that \u201c Assets:US:BofA \u201d is the parent account of \u201c Assets:US:BofA:Checking \u201d, and that the latter is a child account of the former. Commodities / Currencies \uf0c1 Accounts contain currencies , which we sometimes also call commodities (we use both terms interchangeably). Like account names, currency names are recognized by their syntax, though, unlike account names, they need not be declared before being used). The syntax for a currency is a word all in capital letters, like these: USD CAD EUR MSFT IBM AIRMILE (Technically, a currency name may be up to 24 characters long, and it must start with a capital letter, must end with with a capital letter or number, and its other characters must only be capital letters, numbers, or punctuation limited to these characters: \u201c'._-\u201d (apostrophe, period, underscore, dash.) The first three might evoke real world currencies to you (US dollars, Canadian dollars, Euros); the next two, stock ticker names (Microsoft and IBM). And the last: rewards points (airmiles). Beancount knows of no such thing; from its perspective all of these instruments are treated similarly. There is no built-in notion of any previously existing currency. These currency names are just names of \u201cthings\u201d that can be put in accounts and accumulated in inventories associated with these accounts. There is something elegant about the fact that there is no \u201cspecial\u201d currency unit, that all commodities are treated equally the same: Beancount is inherently a multi-currency system. You will appreciate this if, like many of us, you are an expat and your life is divided between two or three continents. You can handle an international ledger of accounts without any problems. And your use of currencies can get quite creative: you can create a currency for your home, for example (e.g. MYLOFT ), a currency to count accumulated vacation hours ( VACHR ), or a currency to count potential contributions to your retirement accounts allowed annually ( IRAUSD ). You can actually solve many problems this way. The cookbook describes many such concrete examples. Beancount does not support the dollar sign syntax, e.g., \u201c$120.00\u201d. You should always use names for currencies in your input file. This makes the input more regular and is a design choice. For monetary units, I suggest that you use the standard ISO 4217 currency code as a guideline; these quickly become familiar. However, as detailed above, you may include some other characters in currency names, like underscores (_), dashes (-), periods (.), or apostrophes (\u2018), but no spaces. Finally, you will notice that there exists a \u201c commodity \u201d directive that can be used to declare currencies. It is entirely optional: currencies come into being as you use them. The purpose of the directive is simply to attach metadata to it. Strings \uf0c1 Whenever we need to insert some free text as part of an entry, it should be surrounded by double-quotes. This applies to the payee and narration fields, mainly; basically anything that\u2019s not a date, a number, a currency, an account name. Strings may be split over multiple lines. (Strings with multiple lines will include their newline characters and those need to be handled accordingly when rendering.) Comments \uf0c1 The Beancount input file isn\u2019t intended to contain only your directives: you can be liberal in placing comments and headers in it to organize your file. Any text on a line after the character \u201c;\u201d is ignored, text like this: ; I paid and left the taxi, forgot to take change, it was cold. 2015-01-01 * \"Taxi home from concert in Brooklyn\" Assets:Cash -20 USD ; inline comment Expenses:Taxi You can use one or more \u201c;\u201d characters if you like. Prepend on all lines if you want to enter a larger comment text. If you prefer to have the comment text parsed in and rendered in your journals, see the Note directive elsewhere in this document. Any line that does not begin as a valid Beancount syntax directive (e.g. with a date) is silently ignored. This way you can insert markup to organize your file for various outline modes, such as org-mode in Emacs. For example, you could organize your input file by institution like this and fold & unfold each of the sections independently,: * Banking ** Bank of America 2003-01-05 open Assets:US:BofA:Checking 2003-01-05 open Assets:US:BofA:Savings ;; Transactions follow \u2026 ** TD Bank 2006-03-15 open Assets:US:TD:Cash ;; More transactions follow \u2026 The unmatching lines are simply ignored. Note to visiting Ledger users: In Ledger, \u201c;\u201d is used both for marking comments and for attaching \u201cLedger tags\u201d (Beancount metadata) to postings. This is not the case in Beancount. In Beancount comments are always just comments. Metadata has its own separate syntax. Directives \uf0c1 For a quick reference & overview of directive syntax, please consult the Syntax Cheat Sheet . Open \uf0c1 All accounts need to be declared \u201copen\u201d in order to accept amounts posted to them. You do this by writing a directive that looks like this: 2014-05-01 open Liabilities:CreditCard:CapitalOne USD The general format of the Open directive is: YYYY-MM-DD open Account [ConstraintCurrency,...] [\"BookingMethod\"] The comma-separated optional list of constraint currencies enforces that all changes posted to this account are in units of one of the declared currencies. Specifying a currency constraint is recommended: the more constraints you provide Beancount with, the less likely you will be to make data entry mistakes because if you do, it will warn you about it. Each account should be declared \u201copened\u201d at a particular date that precedes (or is the same as) the date of the first transaction that posts an amount to that account. Just to be clear: an Open directive does not have to appear before transactions in the file , but rather, the date of the Open directive must precede the date of postings to that account. The order of declarations in the file is not important. So for example, this is a legal input file: 2014-05-05 * \"Using my new credit card\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant 2014-05-01 open Liabilities:CreditCard:CapitalOne USD 1990-01-01 open Expenses:Restaurant Another optional declaration for opening accounts is the \u201cbooking method\u201d, which is the algorithm that will be invoked if a reducing lot leads to an ambiguous choice of matching lots from the inventory (0, 2 or more lots match). Currently, the possible values it can take are: STRICT : The lot specification has to match exactly one lot. This is the default method. If this booking method is invoked, it will simply raise an error. This ensures that your input file explicitly selects all matching lots. NONE : No lot matching is performed. Lots of any price will be accepted. A mix of positive and negative numbers of lots for the same currency is allowed. (This is similar to how Ledger treats matching\u2026 it ignores it.) Close \uf0c1 Similarly to the Open directive, there is a Close directive that can be used to tell Beancount that an account has become inactive, for example: ; Closing credit card after fraud was detected. 2016-11-28 close Liabilities:CreditCard:CapitalOne The general format of the Close directive is: YYYY-MM-DD close Account This directive is used in a couple of ways: An error message is raised if you post amounts to that account after its closing date (it's a sanity check). This helps avoid mistakes in data entry. It helps the reporting code figure out which accounts are still active and filter out closed accounts outside of the reporting period. This is especially useful as your ledger accumulates much data over time, as there will be accounts that stop existing and which you just don't want to see in reports for years that follow their closure. Note that a Close directive does not currently generate an implicit zero balance check. You may want to add one just before the closing date to ensure that the account is correctly closed with empty contents. At the moment, once an account is closed, you cannot reopen it after that date. (Though you can, of course, delete or comment-out the directive that closed it.) Finally, there are utility functions in the code that allow you to establish which accounts are open on a particular date. I strongly recommend that you close accounts when they actually close in reality, it will keep your ledger more tidy. Commodity \uf0c1 There is a \u201cCommodity\u201d directive that can be used to declare currencies, financial instruments, commodities (different names for the same thing in Beancount): 1867-07-01 commodity CAD The general format of the Commodity directive is: YYYY-MM-DD commodity Currency This directive is a late arrival, and is entirely optional: you can use commodities without having to really declare them this way. The purpose of this directive is to attach commodity-specific metadata fields on it, so that it can be gathered by plugins later on. For example, you might want to provide a long descriptive name for each commodity, such as \u201cSwiss Franc\u201d for commodity \u201cCHF\u201d, or \u201cHooli Corporation Class C Shares\u201d for \u201cHOOL\u201d, like this: 1867-07-01 commodity CAD name: \"Canadian Dollar\" asset-class: \"cash\" 2012-01-01 commodity HOOL name: \"Hooli Corporation Class C Shares\" asset-class: \"stock\" For example, a plugin could then gather the metadata attribute names and perform some aggregations per asset class. You can use any date for a commodity\u2026 but a relevant date is the date at which it was created or introduced, e.g. Canadian dollars were first introduced in 1867, ILS (new Israeli Shekels) are in use since 1986-01-01. For a company, the date the corporation was formed and shares created could be a good date. Since the main purpose of this directive is to collect per-commodity information, the particular date you choose doesn\u2019t matter all that much. It is an error to declare the same commodity twice. Transactions \uf0c1 Transactions are the most common type of directives that occur in a ledger. They are slightly different to the other ones, because they can be followed by a list of postings. Here is an example: 2014-05-05 txn \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant As for all the other directives, a transaction directive begins with a date in the YYYY-MM-DD format and is followed by the directive name, in this case, \u201c txn \u201d. However, because transactions are the raison d\u2019\u00eatre for our double-entry system and as such are by far the most common type of directive that should appear in a Beancount input file, we make a special case and allow the user to elide the \u201ctxn\u201d keyword and just use a flag instead of it: 2014-05-05 * \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant A flag is used to indicate the status of a transaction, and the particular meaning of the flag is yours to define. We recommend using the following interpretation for them: *: Completed transaction, known amounts, \u201cthis looks correct.\u201d !: Incomplete transaction, needs confirmation or revision, \u201cthis looks incorrect.\u201d In the case of the first example using \u201ctxn\u201d to leave the transaction unflagged, the default flag (\u201c*\u201d) will be set on the transaction object. (I nearly always use the \u201c*\u201d variant and never the keyword one, it is mainly there for consistency with all the other directive formats.) You can also attach flags to the postings themselves, if you want to flag one of the transaction\u2019s legs in particular: 2014-05-05 * \"Transfer from Savings account\" Assets:MyBank:Checking -400.00 USD ! Assets:MyBank:Savings This is useful in the intermediate stage of de-duping transactions (see Getting Started document for more details). The general format of a Transaction directive is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ... The lines that follow the first line are for \u201cPostings.\u201d You can attach as many postings as you want to a transaction. For example, a salary entry might look like this: 2014-03-19 * \"Acme Corp\" \"Bi-monthly salary payment\" Assets:MyBank:Checking 3062.68 USD ; Direct deposit Income:AcmeCorp:Salary -4615.38 USD ; Gross salary Expenses:Taxes:TY2014:Federal 920.53 USD ; Federal taxes Expenses:Taxes:TY2014:SocSec 286.15 USD ; Social security Expenses:Taxes:TY2014:Medicare 66.92 USD ; Medicare Expenses:Taxes:TY2014:StateNY 277.90 USD ; New York taxes Expenses:Taxes:TY2014:SDI 1.20 USD ; Disability insurance The Amount in \u201cPostings\u201d can also be an arithmetic expression using ( ) * / - + . For example, 2014-10-05 * \"Costco\" \"Shopping for birthday\" Liabilities:CreditCard:CapitalOne -45.00 USD Assets:AccountsReceivable:John ((40.00/3) + 5) USD Assets:AccountsReceivable:Michael 40.00/3 USD Expenses:Shopping The crucial and only constraint on postings is that the sum of their balance amounts must be zero. This is explained in full detail below. Metadata \uf0c1 It\u2019s also possible to attach metadata to the transaction and/or any of its postings, so the fully general format is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... ... See the dedicated section on metadata below. Payee & Narration \uf0c1 A transaction may have an optional \u201cpayee\u201d and/or a \u201cnarration.\u201d In the first example above, the payee is \u201cCafe Mogador\u201d and the narration is \u201cLamb tagine with wine\u201d. The payee is a string that represents an external entity that is involved in the transaction. Payees are sometimes useful on transactions that post amounts to Expense accounts, whereby the account accumulates a category of expenses from multiple businesses. A good example is \u201c Expenses:Restaurant \u201d, which will include all postings for the various restaurants one might visit. A narration is a description of the transaction that you write. It can be a comment about the context, the person who accompanied you, some note about the product you bought... whatever you want it to be. It\u2019s for you to insert whatever you like. I like to insert notes when I reconcile, it\u2019s quick and I can refer to my notes later on, for example, to answer the question \u201cWhat was the name of that great little sushi place I visited with Andreas on the West side last winter?\u201d If you place a single string on a transaction line, it becomes its narration: 2014-05-05 * \"Lamb tagine with wine\" \u2026 If you want to set just a payee, put an empty narration string: 2014-05-05 * \"Cafe Mogador\" \"\" \u2026 For legacy reasons, a pipe symbol (\u201c|\u201d) is accepted between those strings (but this will be removed at some point in the future): 2014-05-05 * \"Cafe Mogador\" | \"\" \u2026 You may also leave out either (but you must provide a flag): 2014-05-05 * \u2026 Note for Ledger users. Ledger does not have separate narration and payee fields, it has only one field, which is referred to by the \u201cPayee\u201d metadata tag, and the narration ends up in a saved comment (a \u201cpersistent note\u201d). In Beancount, a Transaction object simply has two fields: payee and narration, where payee just happens to have an empty value a lot of the time. For a deeper discussion of how and when to use payees or not, see Payees, Subaccounts, and Assets . Costs and Prices \uf0c1 Postings represent a single amount being deposited to or withdrawn from an account. The simplest type of posting includes only its amount: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard 400.00 USD If you converted the amount from another currency, you must provide a conversion rate to balance the transaction (see next section). This is done by attaching a \u201cprice\u201d to the posting, which is the rate of conversion: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @ 1.09 CAD Assets:FR:SocGen:Checking 436.01 CAD You could also use the \u201c@@\u201d syntax to specify the total cost: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @@ 436.01 CAD Assets:FR:SocGen:Checking 436.01 CAD Beancount will automatically compute the per-unit price, that is 1.090025 CAD (note that the precision will differ between the last two examples). After the transaction, we are not interested in keeping track of the exchange rate for the units of USD deposited into the account; those units of \u201cUSD\u201d are simply deposited. In a sense, the rate at which they were converted at has been forgotten. However, some commodities that you deposit in accounts must be \u201cheld at cost.\u201d This happens when you want to keep track of the cost basis of those commodities. The typical use case is investing, for example when you deposit shares of a stock in an account. What you want in that circumstance is for the acquisition cost of the commodities you deposit to be attached to the units, such that when you remove those units later on (when you sell), you should be able to identify by cost which of those units to remove, in order to control the effect of taxes (and avoid errors). You can imagine that for each of the units in the account there is an attached cost basis. This will allow us to automatically compute capital gains later. In order to specify that a posting to an account is to be held at a specific cost, include the cost in curly brackets: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 10 IVV {183.07 USD} Assets:ETrade:Cash -1830.70 USD This is the subject of a deeper discussion. Refer to \u201c How Inventories Work \u201d and \u201c Trading with Beancount \u201d documents for an in-depth discussion of these topics. Finally, you can include both a cost and a price on a posting: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains The price will only be used to insert a price entry in the prices database (see Price section below). This is discussed in more details in the Balancing Postings section of this document. Important Note. Amounts specified as either per-share or total prices or costs are always unsigned . It is an error to use a negative sign or a negative cost and Beancount will raise an error if you attempt to do so. Balancing Rule - The \u201cweight\u201d of postings \uf0c1 A crucial aspect of the double-entry method is ensuring that the sum of all the amounts on its postings equals ZERO, in all currencies. This is the central, non-negotiable condition that engenders the accounting equation, and makes it possible to filter any subset of transactions and drawing balance sheets that balance to zero. But with different types of units, the previously introduced price conversions and units \u201cheld at cost\u201d, what does it all mean? It\u2019s simple: we have devised a simple and clear rule for obtaining an amount and a currency from each posting, to be used for balancing them together. We call this the \u201cweight\u201d of a posting, or the balancing amount. Here is a short example of weights derived from postings using the four possible types of cost/price combinations: YYYY-MM-DD Account 10.00 USD -> 10.00 USD Account 10.00 CAD @ 1.01 USD -> 10.10 USD Account 10 SOME {2.02 USD} -> 20.20 USD Account 10 SOME {2.02 USD} @ 2.50 USD -> 20.20 USD Here is the explanation of how it is calculated: If the posting has only an amount and no cost, the balance amount is just the amount and currency on that posting. Using the first example from the previous section, the amount is -400.00 USD, and that is balanced against the 400.00 USD of the second leg. If the posting has only a price , the price is multiplied by the number of units and the price currency is used. In the second example from the preceding section, that is -400.00 USD x 1.09 CAD(/USD) = -436.00 CAD, and that is balanced against the other posting of 436.00 CAD 2 . If the posting has a cost , the cost is multiplied by the number of units and the cost currency is used. In the third example from the preceding section, that is 10 IVV x 183.08 USD(/IVV) = 1830.70 USD. That is balanced against the cash leg of -1830.70 USD, so all is good. Finally, if a posting has both a cost and a price , we simply ignore the price. This optional price is used later on to generate an entry in the in-memory prices database, but it is not used in balancing at all. With this rule, you should be able to easily balance all your transactions. Moreover, this rule makes it possible to let Beancount automatically calculate capital gains for you (see Trading with Beancount for details). Reducing Positions \uf0c1 When you post a reduction to a position in an account, the reduction must always match an existing lot. For example, if an account holds 3200 USD and a transaction posts a -1200 USD change to that account, the 1200 USD match against the existing 3200 USD, and the result is a single position of 2000 USD. This also works for negative values. For example, if an account has a -1300 USD balance and you post a +2000 USD change to it, you obtain a 700 USD balance. A change posted to an account, regardless of the account type, can result in a positive or negative balance; there are no limitations on the balances of simple commodity amounts (that is, those with no cost associated to them). For example, while Assets accounts normally have a positive balance and Liabilities accounts usually a negative one, you can legally credit an Assets account to a negative balance, or debit a Liabilities account to a positive balance. This is because in the real world these things do happen: you might write a check too many and obtain temporary credit from your bank\u2019s checking account (usually along with an outrageous \u201coverdraft\u201d fee), or pay that credit card balance twice by mistake. For commodities held at cost, the cost specification of the posting must match one of the lots held in the inventory before the transaction is applied. The list of lots is gathered, and matched against the specification in the {...} part of the posting. For example, if you provide a cost, only those lots whose cost match that will remain. If you provide a date, only those lots which match that date will remain. And you can use a label as well. If you provide a cost and a date, both of these are matched against the list of candidate lots to reduce. This is essentially a filter on the list of lots. If the filtered list results in a single lot, that lot is chosen to be reduced. If the list results in multiple lots, but the total amount being reduced equals the total amount in the lots, all those lots are reduced by that posting. For example, if in the past you had the following transactions: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 20 IVV {183.07 USD, \"ref-001\"} \u2026 2014-03-22 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 15 IVV {187.12 USD} \u2026 Each of the following reductions would be unambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {183.07 USD} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {2014-02-11} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {\"ref-001\"} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -35 IVV {} \u2026 However, the following would be ambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {} \u2026 If multiple lots match against the reducing posting and their number is not the total number, we are in a situation of ambiguous matches . What happens then, is that the account\u2019s booking method is invoked. There are multiple booking methods, but by default, all accounts are set to use the \u201cSTRICT\u201d booking method. This method simply issues an error in an ambiguous situation. You may set the account\u2019s booking method to \u201cFIFO\u201d to instruct Beancount to select the oldest of the lots. Or \u201cLIFO\u201d for the latest (youngest) of the lots. This will automatically select all the necessary matching lots to fulfill the reduction. PLEASE NOTE! Requiring the dates to match will be dealt with more sensibly in the near future. See A Proposal for an Improvement on Inventory Booking for details of this upcoming change. For such postings, a change that results in a negative number of units is usually impossible. Beancount does not currently allow holding a negative number of a commodity held at cost. For example, an input with just this transaction will fail: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD If it did not, this would result in a balance of -10 units of MSFT. On the other hand, if the account had a balance of 12 units of MSFT held at 43.40 USD on 5/23, the transaction would book just fine, reducing the existing 12 units to 2. Most often, the error that will occur is that the account will be holding a balance of 10 or more units of MSFT at a different cost , and the user will specify an incorrect value for the cost. For instance, if the account had a positive balance of 20 MSFT {42.10 USD}, the transaction above would still fail, because there aren\u2019t 10 or more units of MSFT at 43.40 USD to remove from. This constraint is enforced for a few reasons: Mistakes in data entry for stock units are not uncommon, they have an important negative impact on the correctness of your Ledger\u2014the amounts are usually large\u2014and they will typically trigger an error from this constraint. Therefore, the error check is a useful way to detect these types of errors. Negative numbers of units held at cost are fairly rare. Chances are you don\u2019t need them at all. Exceptions include: short sales of stock, holding spreads on futures contracts, and depending on how you account for them, short positions in currency trading. This is why this check is enabled by default. PLEASE NOTE! In a future version of Beancount, we will relax this constraint somewhat. We will allow an account to hold a negative number of units of a commodity if and only if there are no other units of that commodity held in the account. Either that, or we will allow you to mark an account as having no such constraints at all. The purpose is to allow the account of short positions in commodities. The only blocking factor is this constraint. For more details of the inventory booking algorithm, see the How Inventories Work document. Amount Interpolation \uf0c1 Beancount is able to fill in some of the details of a transaction automatically. You can currently elide the amount of at most one posting within a transaction: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard In the example above, the amount of the credit card posting has been elided. It is automatically calculated by Beancount at 400.00 USD to balance the transaction. This also works with multiple postings, and with postings with costs: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains In this case, the units of IVV are sold at a higher price ($197.90) than they were bought for ($183.07). The cash first posting has a weight of -10 x 183.07 = -1830.70 and the second posting a straightforward $1979.90. The last posting will be ascribed the difference, that is, a balance of -149.20 USD , which is to say, a gain of $149.20. When calculating the amount to be balanced, the same balance amounts that are used to check that the transaction balances to zero are used to fill in the missing amounts. For example, the following would not trigger an error: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash The cash account would receive 1830.70 USD automatically, because the balance amount of the IVV posting is -1830.70 USD (if a posting has both a cost and a price, the cost basis is always used and the optional price ignored). While this is accepted and correct from a balancing perspective, this would be incomplete from an accounting perspective: the capital gain on a sale needs to be accounted for separately and besides, the amount deposited to the cash account if you did as above would fall short of the real deposit (1979.00 USD) and hopefully a subsequent balance assertion in the cash account would indicate this oversight by triggering an error. Finally, this also works when the balance includes multiple commodities: 2014-07-12 * \"Uncle Bob gave me his foreign currency collection!\" Income:Gifts -117.00 ILS Income:Gifts -3000.00 INR Income:Gifts -800.00 JPY Assets:ForeignCash Multiple postings (one for each commodity required to balance) will be inserted to replace the elided one. Tags \uf0c1 Transactions can be tagged with arbitrary strings: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 Expenses:Flights -1230.27 USD Liabilities:CreditCard This is similar to the popular idea of \u201chash tagging\u201d on Twitter and such. These tags essentially allow you to mark a subset of transactions. They can then be used as a filter to generate reports on only this subset of transactions. They have numerous uses. I like to use them to mark all my trips. Multiple tags can be specified as well: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 #germany Expenses:Flights -1230.27 USD Liabilities:CreditCard (If you want to store key-value pairs on directives, see the section on metadata below.) The Tag Stack \uf0c1 Oftentimes multiple transactions related to a single tag will be entered consecutively in a file. As a convenience, the parser can automatically tag transactions within a block of text. How this works is simple: the parser has a \u201cstack\u201d of current tags which it applies to all transactions as it reads them one-by-one. You can push and pop tags onto/from this stack, like this: pushtag #berlin-trip-2014 2014-04-23 * \"Flight to Berlin\" Expenses:Flights -1230.27 USD Liabilities:CreditCard poptag #berlin-trip-2014 This way, you can also push multiple tags onto a long, consecutive set of transactions without having to type them all in. Links \uf0c1 Transactions can also be linked together. You may think of the link as a special kind of tag that can be used to group together a set of financially related transactions over time. For example you may use links to group together transactions that are each related with a specific invoice. This allows to track payments (or write-offs) associated with the invoice: 2014-02-05 * \"Invoice for January\" ^invoice-pepe-studios-jan14 Income:Clients:PepeStudios -8450.00 USD Assets:AccountsReceivable 2014-02-20 * \"Check deposit - payment from Pepe\" ^invoice-pepe-studios-jan14 Assets:BofA:Checking 8450.00 USD Assets:AccountsReceivable Or track multiple transfers related to a single nefarious purpose: 2014-02-05 * \"Moving money to Isle of Man\" ^transfers-offshore-17 Assets:WellsFargo:Savings -40000.00 USD Assets:WellsFargo:Checking 40000.00 USD 2014-02-09 * \"Wire to FX broker\" ^transfers-offshore-17 Assets:WellsFargo:Checking -40025.00 USD Expenses:Fees:WireTransfers 25.00 USD Assets:OANDA:USDollar 40000.00 2014-03-16 * \"Conversion to offshore beans\" ^transfers-offshore-17 Assets:OANDA:USDollar -40000.00 USD Assets:OANDA:GBPounds 23391.81 GBP @ 1.71 USD 2014-03-16 * \"God save the Queen (and taxes)\" ^transfers-offshore-17 Assets:OANDA:GBPounds -23391.81 GBP Expenses:Fees:WireTransfers 15.00 GBP Assets:Brittania:PrivateBanking 23376.81 GBP Linked transactions can be rendered by the web interface in their own dedicated journal, regardless of the current view/filtered set of transactions (the list of links is a global page). Balance Assertions \uf0c1 A balance assertion is a way for you to input your statement balance into the flow of transactions. It tells Beancount to verify that the number of units of a particular commodity in some account should equal some expected value at some point in time. For instance, this 2014-12-26 balance Liabilities:US:CreditCard -3492.02 USD says \u201cCheck that the number of USD units in account \u201c Liabilities:US:CreditCard \u201d on the morning of December 26th, 2014 is -3492.02 USD.\u201d When processing the list of entries, if Beancount encounters a different balance than this for USD it will report an error. If no error is reported, you should have some confidence that the list of transactions that precedes it in this account is highly likely to be correct. This is useful in practice, because in many cases some transactions can get imported separately from the accounts of each of their postings (see the de-duping problem ). This can result in you booking the same transaction twice without noticing, and regularly inserting a balance assertion will catch that problem every time. The general format of the Balance directive is: YYYY-MM-DD balance Account Amount Note that a balance assertion, like all other non-transaction directives, applies at the beginning of its date (i.e., midnight at the start of day). Just imagine that the balance check occurs right after midnight on that day. Balance assertions only make sense on balance sheet accounts (Assets and Liabilities). Because the postings on Income and Expenses accounts are only interesting because of their transient value, i.e., for these accounts we\u2019re interested in sums of changes over a period of time (not the absolute value), it makes little sense to use a balance assertion on income statement accounts. Also, take note that each account benefits from an implicit balance assertion that the account is empty after it is opened at the date of its Open directive. You do not need to explicitly assert a zero balance when opening accounts. Multiple Commodities \uf0c1 A Beancount account may contain more than one commodity (although in practice, you will find that this does not occur often, it is sensible to create dedicated sub-accounts to hold each commodity, for example, holding a portfolio of stocks). A balance assertion applies only to the commodity of the assertion; it leaves the other commodities in the balance unchecked. If you want to check multiple commodities, use multiple balance assertions, like this: ; Check cash balances from wallet 2014-08-09 balance Assets:Cash 562.00 USD 2014-08-09 balance Assets:Cash 210.00 CAD 2014-08-09 balance Assets:Cash 60.00 EUR There is currently no way to exhaustively check the full list of commodities in an account ( a proposal is underway ). Note that in this example if an exhaustive check really matters to you, you could circumvent by defining a subaccount of the cash account to segregate each commodity separately, like this Assets:Cash:USD , Assets:Cash:CAD . Lots Are Aggregated \uf0c1 The balance assertion applies to the sum of units of a particular commody, irrespective of their cost. For example, if you hold three lots of the same commodity in an account, for example, 5 HOOL {500 USD} and 6 HOOL {510 USD}, the following balance check should succeed: 2014-08-09 balance Assets:Investing:HOOL 11 HOOL All the lots are aggregated together and you can verify their number of units. Checks on Parent Accounts \uf0c1 Balance assertions may be performed on parent accounts, and will include the balances of theirs and their sub-accounts: 2014-01-01 open Assets:Investing 2014-01-01 open Assets:Investing:Apple AAPL 2014-01-01 open Assets:Investing:Amazon AMZN 2014-01-01 open Assets:Investing:Microsoft MSFT 2014-01-01 open Equity:Opening-Balances 2014-06-01 * Assets:Investing:Apple 5 AAPL {578.23 USD} Assets:Investing:Amazon 5 AMZN {346.20 USD} Assets:Investing:Microsoft 5 MSFT {42.09 USD} Equity:Opening-Balances 2014-07-13 balance Assets:Investing 5 AAPL 2014-07-13 balance Assets:Investing 5 AMZN 2014-07-13 balance Assets:Investing 5 MSFT Note that this does require that a parent account have been declared as Open, in order to be legitimately used in the balance assertions directive. Before Close \uf0c1 It is useful to insert a balance assertion for 0 units just before closing an account, just to make sure its contents are empty as you close it. The Close directive does not insert that for you automatically (we may eventually build a plug-in for it). Local Tolerance \uf0c1 It's pretty common that sometimes one needs to override the tolerance on the balance check to loosen it on that balance assertion. This can be done using a local tolerance amount off of the balance amount, like this: 2013-09-20 balance Assets:Investing:Funds 319.020 ~ 0.002 RGAGX Pad \uf0c1 A padding directive automatically inserts a transaction that will make the subsequent balance assertion succeed, if it is needed. It inserts the difference needed to fulfill that balance assertion. (What \u201crubber space\u201d is in LaTeX, Pad directives are to balances in Beancount.) Note that by \u201csubsequent,\u201d I mean in date order , not in the order of the declarations in the file. This is the conceptual equivalent of a transaction that will automatically expand or contract to fill the difference between two balance assertions over time. It looks like this: 2014-06-01 pad Assets:BofA:Checking Equity:Opening-Balances The general format of the Pad directive is: YYYY-MM-DD pad Account AccountPad The first account is the account to credit the automatically calculated amount to. This is the account that should have a balance assertion following it (if the account does not have a balance assertion, the pad entry is benign and does nothing). The second account is for the other leg of the transaction, it is the source where the funds will come from, and this is almost always some Equity account. The reason for this is that this directive is generally used for initializing the balances of new accounts, to save us from having to either insert such a directive manually, or from having to enter the full past history of transactions that will bring the account to its current balance. Here is a realistic example usage scenario: ; Account was opened way back in the past. 2002-01-17 open Assets:US:BofA:Checking 2002-01-17 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD This will result in the following transaction being inserted right after the Pad directive, on the same date: 2002-01-17 P \"(Padding inserted for balance of 987.34 USD)\" Assets:US:BofA:Checking 987.34 USD Equity:Opening-Balances -987.34 USD This is a normal transaction\u2014you will see it appear in the rendered journals along with the other ones. (Note the special \u201cP\u201d flag, which can be used by scripts to find these.) Observe that without balance assertions, Pad directives make no sense. Therefore, like balance assertions, they are normally only used on balance sheet accounts (Assets and Liabilities). You could also insert Pad entries between balance assertions, it works too. For example: 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD \u2026 more transactions\u2026 2014-08-08 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-08-09 balance Assets:US:BofA:Checking 1137.23 USD Without intervening transactions, this would insert the following padding transaction: 2014-08-08 P \"(Padding inserted for balance of 1137.23 USD)\" Assets:US:BofA:Checking 149.89 USD Equity:Opening-Balances -149.89 USD In case that\u2019s not obvious, 149.89 USD is the difference between 1137.23 USD and 987.34 USD. If there were more intervening transactions posting amounts to the checking account, the amount would automatically have been adjusted to make the second assertion pass. Unused Pad Directives \uf0c1 You may not currently leave unused Pad directives in your input file. They will trigger an error: 2014-01-01 open Assets:US:BofA:Checking 2014-02-01 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-06-01 * \"Initializing account\" Assets:US:BofA:Checking 212.00 USD Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 212.00 USD (Being this strict is a matter for debate, I suppose, and it could eventually be moved to an optional plugin.) Commodities \uf0c1 Note that the Pad directive does not specify any commodities at all. All commodities with corresponding balance assertions in the account are affected. For instance, the following code would have a padding directive insert a transaction with separate postings for USD and CAD: 2002-01-17 open Assets:Cash 2002-01-17 pad Assets:Cash Equity:Opening-Balances 2014-07-09 balance Assets:Cash 987.34 USD 2014-07-09 balance Assets:Cash 236.24 CAD If the account contained other commodities that aren\u2019t balance asserted, no posting would be inserted for those. Cost Basis \uf0c1 At the moment, Pad directives do not work with accounts holding positions held at cost. The directive is really only useful for cash accounts. (This is mainly because balance assertions do not yet allow specifying a cost basis to assert. It is possible that in the future we decide to support asserting the total cost basis, and that point we could consider supporting padding with cost basis.) Multiple Paddings \uf0c1 You cannot currently insert multiple padding entries for the same account and commodity: 2002-01-17 open Assets:Cash 2002-02-01 pad Assets:Cash Equity:Opening-Balances 2002-03-01 pad Assets:Cash Equity:Opening-Balances 2014-04-19 balance Assets:Cash 987.34 USD (There is a proposal pending to allow this and spread the padding amount evenly among all the intervening Pad directives, but it is as of yet unimplemented.) Notes \uf0c1 A Note directive is simply used to attach a dated comment to the journal of a particular account, like this: 2013-11-03 note Liabilities:CreditCard \"Called about fraudulent card.\" When you render the journal, the note should be rendered in context. This can be useful to record facts and claims associated with a financial event. I often use this to record snippets of information that would otherwise not make their way to a transaction. The general format of the Note directive is: YYYY-MM-DD note Account Description The description string may be split among multiple lines. Documents \uf0c1 A Document directive can be used to attach an external file to the journal of an account: 2013-11-03 document Liabilities:CreditCard \"/home/joe/stmts/apr-2014.pdf\" The filename gets rendered as a browser link in the journals of the web interface for the corresponding account and you should be able to click on it to view the contents of the file itself. This is useful to integrate account statements and other downloads into the flow of accounts, so that they\u2019re easily accessible from a few clicks. Scripts could also be written to obtain this list of documents from an account name and do something with them. The general format of the Document directive is: YYYY-MM-DD document Account PathToDocument Documents from a Directory \uf0c1 A more convenient way to create these entries is to use a special option to specify directories that contain a hierarchy of sub-directories that mirrors that of the chart of accounts. For example, the Document directive shown in the previous section could have been created from a directory hierarchy that looks like this: stmts `-- Liabilities `-- CreditCard `-- 2014-04-27.apr-2014.pdf By simply specifying the root directory as an option (note the absence of a trailing slash): option \"documents\" \"/home/joe/stmts\" The files that will be picked up are those that begin with a date as above, in the YYYY-MM-DD format. You may specify this option multiple times if you have many such document archives. In the past I have used one directory for each year (if you scan all your documents, the directory can grow to a large size, scanned documents tend to be large files). Organizing your electronic document statements and scans using the hierarchy of your ledger\u2019s accounts is a fantastic way to organize them and establish a clear, unambiguous place to find these documents later on, when they\u2019re needed. Prices \uf0c1 Beancount sometimes creates an in-memory data store of prices for each commodity, that is used for various reasons. In particular, it is used to report unrealized gains on account holdings. Price directives can be used to provide data points for this database. A Price directive establishes the rate of exchange between one commodity (the base currency) and another (the quote currency): 2014-07-09 price HOOL 579.18 USD This directive says: \u201cThe price of one unit of HOOL on July 9th, 2014 was 579.18 USD.\u201d Price entries for currency exchange rates work the same way: 2014-07-09 price USD 1.08 CAD The general format of the Price directive is: YYYY-MM-DD price Commodity Price Remember that Beancount knows nothing about what HOOL, USD or CAD are. They are opaque \u201cthings.\u201d You attach meaning to them. So setting a price per hour for your vacation hours is perfectly valid and useful too, if you account for your unused vacations on such terms: 2014-07-09 price VACHR 38.46 USD ; Equiv. $80,000 year Prices from Postings \uf0c1 If you use the beancount.plugins.implicit_prices plugin, every time a Posting appears that has a cost or an optional price declared, it will use that cost or price to automatically synthesize a Price directive. For example, this transaction: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD automatically becomes this after parsing: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD 2014-05-23 price MSFT 43.40 USD This is convenient and if you enable it, will probably be where most of the price points in your ledger\u2019s price database will come from. You can print a table of the parsed prices from a ledger (it is just another type of report). Prices on the Same Day \uf0c1 Notice that there is no notion of time; Beancount is not designed to solve problems for intra-day traders, though of course, it certainly is able to handle multiple trades per day. It just stores its prices per day. (Generally, if you need many features that require a notion of intra-day time, you\u2019re better off using another system, this is not the scope of a bookkeeping system.) When multiple Price directives do exist for the same day, the last one to appear in the file will be selected for inclusion in the Price database. Events \uf0c1 Event directives 3 are used to track the value of some variable of your choice over time. For example, your location: 2014-07-09 event \"location\" \"Paris, France\" The above directive says: \u201cChange the value of the \u2018location\u2019 event to \u2018Paris, France\u2019 as of the 9th of July 2014 and onwards.\u201d A particular event type only has a single value per day. The general format of the Event directive is: YYYY-MM-DD event Name Value The event\u2019s Name string does not have to be declared anywhere, it just begins to exist the first time you use the directive. The Value string can be anything you like; it has no prescribed structure. How to use these is best explained by providing examples: Location : You can use events for tracking the city or country you\u2019re in. This is sensible usage within a ledger because the nature of your expenses are heavily correlated to where you are. It\u2019s convenient: if you use Beancount regularly, it is very little effort to add this bit of information to your file. I usually have a \u201ccash daybook\u201d section where I put miscellaneous cash expenses, and that\u2019s where I enter those directives. Address : If you move around a lot, it\u2019s useful to keep a record of your past home addresses. This is sometimes requested on government forms for immigration. The Green Card application process in the US, for instance, Employer : You can record your date of employment and departure for each job this way. Then you can count the number of days you worked there. Trading window : If you\u2019re an employee of a public company, you can record the dates that you\u2019re allowed to trade its stock. This can then be used to ensure you did not trade that stock when the window is closed. Events are often used to report on numbers of days. For example, in the province of Quebec (Canada) you are insured for free health care coverage if \u201c you spend 183 days of the calendar year or more, excluding trips of 21 days or less .\u201d If you travel abroad a lot, you could easily write a script to warn you of remaining day allocations to avoid losing your coverage. Many expats are in similar situations. In the same vein, the IRS recognizes US immigrants as \u201cresident alien\u201d\u2014and thus subject to filing taxes, yay!\u2014if you pass the Substantial Presence Test , which is defined as \u201cbeing physically present in the US on at least 31 days during the current year, and 193 days during the 3 year period that includes the current year and the 2 years immediately before that, counting: all the days you were present in the current year, and \u2153 of the days of the year before that, and \u2159 of the year preceding that one.\u201d Ouch\u2026 my head hurts. This gets complicated. Armed with your location over time, you could report it automatically. Events will also be usable in the filtering language, to specify non-contiguous periods of time. For example, if you are tracking your location using Event directives, you could produce reports for transactions that occur only when you are in a specific location, e.g., \u201cShow me my expenses on all my trips to Germany,\u201d or \u201cGive me a list of payees for restaurant transactions when I\u2019m in Montreal.\u201d PLEASE NOTE! Filters haven\u2019t been implemented yet. Also, reports on events have not yet been re-implemented in Beancount 2.0. They will be reintroduced again soon, as well as filtering. It is worth noticing that the Price and Event directives are the only ones not associated to an account. Query \uf0c1 It can be convenient to be able to associate SQL queries in a Beancount file to be able to run these as a report automatically. This is still an early development / experimental directive. In any case, you can insert queries in the stream of transactions like this: 2014-07-09 query \"france-balances\" \" SELECT account, sum(position) WHERE \u2018trip-france-2014\u2019 in tags\" The grammar is YYYY-MM-DD query Name SqlContents Each query has a name, which will probably be used to invoke its running as a report type. Also, the date of the query is intended to be the date at which the query is intended to be run for, that is, transactions following it should be ignored. If you\u2019re familiar with the SQL syntax, it\u2019s an implicit CLOSE. Custom \uf0c1 The long-term plan for Beancount is to allow plugins and external clients to define their own directive types, to be declared and validated by the Beancount input language parser. In the meantime, a generic directive is provided for clients to prototype new features, e.g., budgeting. 2014-07-09 custom \"budget\" \"...\" TRUE 45.30 USD The grammar for this directive is flexible: YYYY-MM-DD custom TypeName Value1 ... The first argument is a string and is intended to be unique to your directive. Think of this as the type of your directive. Following it, you can put an arbitrary list of strings, dates, booleans, amounts, and numbers. Note that there is no validation that checks that the number and types of arguments following the TypeName is consistent for a particular type. (See this thread for the origin story around this feature.) Metadata \uf0c1 You may attach arbitrary data to each of your entries and postings. The syntax for it looks like this: 2013-03-14 open Assets:BTrade:HOOLI category: \"taxable\" 2013-08-26 * \"Buying some shares of Hooli\" statement: \"confirmation-826453.pdf\" Assets:BTrade:HOOLI 10 HOOL @ {498.45 USD} decision: \"scheduled\" Assets:BTrade:Cash In this example, a \u201ccategory\u201d attribute is attached to an account\u2019s Open directive, a \u201cstatement\u201d attribute is attached to a Transaction directive (with a string value that represents a filename) and a \u201cdecision\u201d attribute has been attached to the first posting of the transaction (the additional indentation from the posting is not strictly necessary but it helps with readability). Metadata can be attached to any directive type. Keys must begin with a lowercase character from a-z and may contain (uppercase or lowercase) letters, numbers, dashes and underscores. Moreover, the values can be any of the following data types: Strings Accounts Currency Dates (datetime.date) Tags Numbers (Decimal) Amount (beancount.core.amount.Amount) There are two ways in which this data can be used: Query tools that come with Beancount (such as bean-query) will allow you to make use of the metadata values for filtering and aggregation. You can access and use the metadata in custom scripts. The metadata values are accessible as a \u201c .meta \u201d attribute on all directives and is a Python dict. There are no special meanings attached to particular attributes, these are intended for users to define. However, all directives are guaranteed to contain a \u2018 filename \u2019 (string) and a \u2018 lineno \u2019 (integer) attribute which reflect the location they were created from. Finally, attributes without a value will be parsed and have a value of 'None'. If an attribute is repeated multiple times, only the first value for this attribute will be parsed and retained and the following values ignored. Options \uf0c1 The great majority of a Beancount input file consists in directives, as seen in the previous section. However, there are a few global options that can be set in the input file as well, by using a special undated \u201coption\u201d directive: option \"title\" \"Ed\u2019s Personal Ledger\" The general format of the Option directive is: option Name Value where Name and Value are both strings. Note that depending the option, the effect may be to set a single value, or add to a list of existing values. In other words, some options are lists. There are three ways to view the list of options: In this document , which I update regularly. To view the definitive list of options supported by your installed version, use the following command: bean-doctor list-options Finally, you can peek at the source code as well. Operating Currencies \uf0c1 One notable option is \u201c operating_currency \u201d. By default Beancount does not treat any of the commodities any different from each other. In particular, it doesn\u2019t know that there\u2019s anything special about the most common of commodities one uses: their currencies. For example, if you live in New Zealand, you\u2019re going to have an overwhelming number of NZD commodities in your transactions. But useful reports try to reduce all non-currency commodities into one of the main currencies used. Also, it\u2019s useful to break out the currency units into their own dedicated columns. This may also be useful for exporting in order to avoid having to specify the units for that column and import to a spreadsheet with numbers you can process. For this reason, you are able to declare the most common currencies you use in an option: option \"operating_currency\" \"USD\" You may declare more than one. In any case, this option is only ever used by reporting code, it never changes the behavior of Beancount\u2019s processing or semantics. Plugins \uf0c1 In order to load plugin Python modules, use the dedicated \u201cplugin\u201d directive: plugin \"beancount.plugins.module_name\" The name of a plugin should be the name of a Python module in your PYTHONPATH. Those modules will be imported by the Beancount loader and run on the list of parsed entries in order for the plugins to transform the entries or output errors. This allows you to integrate some of your code within Beancount, making arbitrary transformations on the entries. See Scripting & Plugins for details. Plugins also optionally accept some configuration parameters. These can be provided by an optional final string argument, like this: plugin \"beancount.plugins.module_name\" \"configuration data\" The general format of the Option directive is: plugin ModuleName StringConfig The format of the configuration data is plugin-dependent. At the moment, an arbitrary string is passed provided to the plugin. See the plugins\u2019 documentation for specific detail on what it can accept. Also see the \u201cplugin processing mode\u201d option which affects the list of built-in plugins that get run. Includes \uf0c1 Include directives are supported. This allows you to split up large input files into multiple files. The syntax looks like this: include \"path/to/include/file.beancount\" The general format is include Filename The specified path can be an absolute or a relative filename. If the filename is relative, it is relative to the including filename\u2019s directory. This makes it easy to put relative includes in a hierarchy of directories that can be placed under source control and checked out anywhere. Include directives are not processed strictly (as in C, for example). The include directives are accumulated by the Beancount parser and processed separately by the loader. This is possible because the order of declarations of a Beancount input file is not relevant. However, for the moment, options are parsed per-file. The options-map that is kept for post-parse processing is the options-map returned for the top-level file. This is probably subject to review in the future. What\u2019s Next? \uf0c1 This document described all the possible syntax of the Beancount language. If you haven\u2019t written any Beancount input yet, you can head to the Getting Started guide, or browse through a list of practical use cases in the Command-line Accounting Cookbook . Note that there exists an \u201cOpen\u201d directive that is used to provide the start date of each account. That can be located anywhere in the file, it does not have to appear in the file somewhere before you use an account name. You can just start using account names in transactions right away, though all account names that receive postings to them will eventually have to have a corresponding Open directive with a date that precedes all transactions posted to the account in the input file. \u21a9 Note that this is valid whether the price is specified as a per-unit price with the @ syntax or as a total price using the @@ syntax. \u21a9 I really dislike the name \u201cevent\u201d for this directive. I\u2019ve been trying to find a better alternative, so far without success. The name \u201cregister\u201d might be more appropriate, as it resembles that of a processor\u2019s register, but that could be confused with an account register report. \u201cvariable\u201d might work, but somehow that just sounds too computer-sciency and out of context. If you can think of something better, please make a suggestion and I\u2019ll seriously entertain a complete rename (with legacy support for \u201cevent\u201d). \u21a9","title":"Beancount Language Syntax"},{"location":"beancount_language_syntax.html#beancount-language-syntax","text":"Martin Blais , Updated: April 2016 http://furius.ca/beancount/doc/syntax Introduction Syntax Overview Directives Ordering of Directives Accounts Commodities / Currencies Strings Comments Directives Open Close Commodity Transactions Metadata Payee & Narration Costs and Prices Balancing Rule - The \u201cweight\u201d of postings Reducing Positions Amount Interpolation Tags The Tag Stack Links Balance Assertions Multiple Commodities Lots Are Aggregated Checks on Parent Accounts Before Close Pad Unused Pad Directives Commodities Cost Basis Multiple Paddings Notes Documents Documents from a Directory Prices Prices from Postings Prices on the Same Day Events Query Custom Metadata Options Operating Currencies Plugins Includes What\u2019s Next?","title":"Beancount Language Syntax"},{"location":"beancount_language_syntax.html#introduction","text":"This is a user's manual to the language of Beancount, the command-line double-entry bookkeeping system. Beancount defines a computer language that allows you to enter financial transactions in a text file and extract various reports from it. It is a generic counting tool that works with multiple currencies, commodities held at cost (e.g., stocks), and even allows you to track unusual things, like vacation hours, air miles and rewards points, and anything else you might want to count, even beans. This document provides an introduction to Beancount\u2019s syntax and some of the technical details needed for one to understand how it carries out its calculations. This document does not provide an introduction to the double-entry method , a motivation , nor examples and guidelines for entering transactions in your input file, nor how to run the tools . These subjects have their own dedicated documents , and it is recommended that you have had a look at those before diving into this user\u2019s manual. This manual covers the technical details for using Beancount.","title":"Introduction"},{"location":"beancount_language_syntax.html#syntax-overview","text":"","title":"Syntax Overview"},{"location":"beancount_language_syntax.html#directives","text":"Beancount is a declarative language. The input consists of a text file containing mainly a list of directives , or entries (we use these terms interchangeably in the code and documentation); there is also syntax for defining various options . Each directive begins with an associated date , which determines the point in time at which the directive will apply, and its type , which defines which kind of event this directive represents. All the directives begin with a syntax that looks like this: YYYY-MM-DD \u2026 where YYYY is the year, MM is the numerical month, and DD the numerical date. All digits are required, for example, the 7th of May 2007 should be \u201c2007-05-07\u201d, including its zeros. Beancount supports the international ISO 8601 standard format for dates, with dashes (e.g., \u201c2014-02-03\u201d), or the same ordering with slashes (e.g., \u201c2014/02/03\u201d). Here are some example directives, just to give you an idea of the aesthetics: 2014-02-03 open Assets:US:BofA:Checking 2014-04-10 note Assets:US:BofA:Checking \"Called to confirm wire transfer.\" 2014-05-02 balance Assets:US:BofA:Checking 154.20 USD The end product of a parsed input file is a simple list of these entries, in a data structure. All operations in Beancount are performed on these entries. Each particular directive type is documented in a section below.","title":"Directives"},{"location":"beancount_language_syntax.html#ordering-of-directives","text":"The order of declaration of the directives is not important. In fact, the entries are re-sorted chronologically after parsing and before being processed. This is an important feature of the language, because it makes it possible for you to organize your input file any way you like without having to worry about affecting the meaning of the directives. Except for transactions, each directive is assumed to occur at the beginning of each day. For example, you could declare an account being opened on the same day as its first transaction: 2014-02-03 open Assets:US:BofA:Checking 2014-02-03 * \"Initial deposit\" Assets:US:BofA:Checking 100 USD Assets:Cash -100 USD However, if you hypothetically closed that account immediately, you could not declare it closed on the same day, you would have to fudge the date forward by declaring the close on 2/4: 2014-02-04 close Assets:US:BofA:Checking This also explains why balance assertions are verified before any transactions that occur on the same date. This is for consistency.","title":"Ordering of Directives"},{"location":"beancount_language_syntax.html#accounts","text":"Beancount accumulates commodities in accounts. The names of these accounts do not have to be declared before being used in the file, they are recognized as \u201caccounts\u201d by virtue of their syntax alone 1 . An account name is a colon-separated list of capitalized words which begin with a letter, and whose first word must be one of five account types: Assets Liabilities Equity Income Expenses Each component of the account names begin with a capital letter or a number and are followed by letters, numbers or dash (-) characters. All other characters are disallowed. Here are some realistic example account names: Assets:US:BofA:Checking Liabilities:CA:RBC:CreditCard Equity:Retained-Earnings Income:US:Acme:Salary Expenses:Food:Groceries The set of all names of accounts seen in an input file implicitly define a hierarchy of accounts (sometimes called a chart-of-accounts ), similarly to how files are organized in a file system. For example, the following account names: Assets:US:BofA:Checking Assets:US:BofA:Savings Assets:US:Vanguard:Cash Assets:US:Vanguard:RGAGX Assets:Receivables implicitly declare a tree of accounts that looks like this: `-- Assets |-- Receivables `-- US |-- BofA | |-- Checking | `-- Savings `-- Vanguard |-- Cash `-- RGAGX We would say that \u201c Assets:US:BofA \u201d is the parent account of \u201c Assets:US:BofA:Checking \u201d, and that the latter is a child account of the former.","title":"Accounts"},{"location":"beancount_language_syntax.html#commodities-currencies","text":"Accounts contain currencies , which we sometimes also call commodities (we use both terms interchangeably). Like account names, currency names are recognized by their syntax, though, unlike account names, they need not be declared before being used). The syntax for a currency is a word all in capital letters, like these: USD CAD EUR MSFT IBM AIRMILE (Technically, a currency name may be up to 24 characters long, and it must start with a capital letter, must end with with a capital letter or number, and its other characters must only be capital letters, numbers, or punctuation limited to these characters: \u201c'._-\u201d (apostrophe, period, underscore, dash.) The first three might evoke real world currencies to you (US dollars, Canadian dollars, Euros); the next two, stock ticker names (Microsoft and IBM). And the last: rewards points (airmiles). Beancount knows of no such thing; from its perspective all of these instruments are treated similarly. There is no built-in notion of any previously existing currency. These currency names are just names of \u201cthings\u201d that can be put in accounts and accumulated in inventories associated with these accounts. There is something elegant about the fact that there is no \u201cspecial\u201d currency unit, that all commodities are treated equally the same: Beancount is inherently a multi-currency system. You will appreciate this if, like many of us, you are an expat and your life is divided between two or three continents. You can handle an international ledger of accounts without any problems. And your use of currencies can get quite creative: you can create a currency for your home, for example (e.g. MYLOFT ), a currency to count accumulated vacation hours ( VACHR ), or a currency to count potential contributions to your retirement accounts allowed annually ( IRAUSD ). You can actually solve many problems this way. The cookbook describes many such concrete examples. Beancount does not support the dollar sign syntax, e.g., \u201c$120.00\u201d. You should always use names for currencies in your input file. This makes the input more regular and is a design choice. For monetary units, I suggest that you use the standard ISO 4217 currency code as a guideline; these quickly become familiar. However, as detailed above, you may include some other characters in currency names, like underscores (_), dashes (-), periods (.), or apostrophes (\u2018), but no spaces. Finally, you will notice that there exists a \u201c commodity \u201d directive that can be used to declare currencies. It is entirely optional: currencies come into being as you use them. The purpose of the directive is simply to attach metadata to it.","title":"Commodities / Currencies"},{"location":"beancount_language_syntax.html#strings","text":"Whenever we need to insert some free text as part of an entry, it should be surrounded by double-quotes. This applies to the payee and narration fields, mainly; basically anything that\u2019s not a date, a number, a currency, an account name. Strings may be split over multiple lines. (Strings with multiple lines will include their newline characters and those need to be handled accordingly when rendering.)","title":"Strings"},{"location":"beancount_language_syntax.html#comments","text":"The Beancount input file isn\u2019t intended to contain only your directives: you can be liberal in placing comments and headers in it to organize your file. Any text on a line after the character \u201c;\u201d is ignored, text like this: ; I paid and left the taxi, forgot to take change, it was cold. 2015-01-01 * \"Taxi home from concert in Brooklyn\" Assets:Cash -20 USD ; inline comment Expenses:Taxi You can use one or more \u201c;\u201d characters if you like. Prepend on all lines if you want to enter a larger comment text. If you prefer to have the comment text parsed in and rendered in your journals, see the Note directive elsewhere in this document. Any line that does not begin as a valid Beancount syntax directive (e.g. with a date) is silently ignored. This way you can insert markup to organize your file for various outline modes, such as org-mode in Emacs. For example, you could organize your input file by institution like this and fold & unfold each of the sections independently,: * Banking ** Bank of America 2003-01-05 open Assets:US:BofA:Checking 2003-01-05 open Assets:US:BofA:Savings ;; Transactions follow \u2026 ** TD Bank 2006-03-15 open Assets:US:TD:Cash ;; More transactions follow \u2026 The unmatching lines are simply ignored. Note to visiting Ledger users: In Ledger, \u201c;\u201d is used both for marking comments and for attaching \u201cLedger tags\u201d (Beancount metadata) to postings. This is not the case in Beancount. In Beancount comments are always just comments. Metadata has its own separate syntax.","title":"Comments"},{"location":"beancount_language_syntax.html#directives_1","text":"For a quick reference & overview of directive syntax, please consult the Syntax Cheat Sheet .","title":"Directives"},{"location":"beancount_language_syntax.html#open","text":"All accounts need to be declared \u201copen\u201d in order to accept amounts posted to them. You do this by writing a directive that looks like this: 2014-05-01 open Liabilities:CreditCard:CapitalOne USD The general format of the Open directive is: YYYY-MM-DD open Account [ConstraintCurrency,...] [\"BookingMethod\"] The comma-separated optional list of constraint currencies enforces that all changes posted to this account are in units of one of the declared currencies. Specifying a currency constraint is recommended: the more constraints you provide Beancount with, the less likely you will be to make data entry mistakes because if you do, it will warn you about it. Each account should be declared \u201copened\u201d at a particular date that precedes (or is the same as) the date of the first transaction that posts an amount to that account. Just to be clear: an Open directive does not have to appear before transactions in the file , but rather, the date of the Open directive must precede the date of postings to that account. The order of declarations in the file is not important. So for example, this is a legal input file: 2014-05-05 * \"Using my new credit card\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant 2014-05-01 open Liabilities:CreditCard:CapitalOne USD 1990-01-01 open Expenses:Restaurant Another optional declaration for opening accounts is the \u201cbooking method\u201d, which is the algorithm that will be invoked if a reducing lot leads to an ambiguous choice of matching lots from the inventory (0, 2 or more lots match). Currently, the possible values it can take are: STRICT : The lot specification has to match exactly one lot. This is the default method. If this booking method is invoked, it will simply raise an error. This ensures that your input file explicitly selects all matching lots. NONE : No lot matching is performed. Lots of any price will be accepted. A mix of positive and negative numbers of lots for the same currency is allowed. (This is similar to how Ledger treats matching\u2026 it ignores it.)","title":"Open"},{"location":"beancount_language_syntax.html#close","text":"Similarly to the Open directive, there is a Close directive that can be used to tell Beancount that an account has become inactive, for example: ; Closing credit card after fraud was detected. 2016-11-28 close Liabilities:CreditCard:CapitalOne The general format of the Close directive is: YYYY-MM-DD close Account This directive is used in a couple of ways: An error message is raised if you post amounts to that account after its closing date (it's a sanity check). This helps avoid mistakes in data entry. It helps the reporting code figure out which accounts are still active and filter out closed accounts outside of the reporting period. This is especially useful as your ledger accumulates much data over time, as there will be accounts that stop existing and which you just don't want to see in reports for years that follow their closure. Note that a Close directive does not currently generate an implicit zero balance check. You may want to add one just before the closing date to ensure that the account is correctly closed with empty contents. At the moment, once an account is closed, you cannot reopen it after that date. (Though you can, of course, delete or comment-out the directive that closed it.) Finally, there are utility functions in the code that allow you to establish which accounts are open on a particular date. I strongly recommend that you close accounts when they actually close in reality, it will keep your ledger more tidy.","title":"Close"},{"location":"beancount_language_syntax.html#commodity","text":"There is a \u201cCommodity\u201d directive that can be used to declare currencies, financial instruments, commodities (different names for the same thing in Beancount): 1867-07-01 commodity CAD The general format of the Commodity directive is: YYYY-MM-DD commodity Currency This directive is a late arrival, and is entirely optional: you can use commodities without having to really declare them this way. The purpose of this directive is to attach commodity-specific metadata fields on it, so that it can be gathered by plugins later on. For example, you might want to provide a long descriptive name for each commodity, such as \u201cSwiss Franc\u201d for commodity \u201cCHF\u201d, or \u201cHooli Corporation Class C Shares\u201d for \u201cHOOL\u201d, like this: 1867-07-01 commodity CAD name: \"Canadian Dollar\" asset-class: \"cash\" 2012-01-01 commodity HOOL name: \"Hooli Corporation Class C Shares\" asset-class: \"stock\" For example, a plugin could then gather the metadata attribute names and perform some aggregations per asset class. You can use any date for a commodity\u2026 but a relevant date is the date at which it was created or introduced, e.g. Canadian dollars were first introduced in 1867, ILS (new Israeli Shekels) are in use since 1986-01-01. For a company, the date the corporation was formed and shares created could be a good date. Since the main purpose of this directive is to collect per-commodity information, the particular date you choose doesn\u2019t matter all that much. It is an error to declare the same commodity twice.","title":"Commodity"},{"location":"beancount_language_syntax.html#transactions","text":"Transactions are the most common type of directives that occur in a ledger. They are slightly different to the other ones, because they can be followed by a list of postings. Here is an example: 2014-05-05 txn \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant As for all the other directives, a transaction directive begins with a date in the YYYY-MM-DD format and is followed by the directive name, in this case, \u201c txn \u201d. However, because transactions are the raison d\u2019\u00eatre for our double-entry system and as such are by far the most common type of directive that should appear in a Beancount input file, we make a special case and allow the user to elide the \u201ctxn\u201d keyword and just use a flag instead of it: 2014-05-05 * \"Cafe Mogador\" \"Lamb tagine with wine\" Liabilities:CreditCard:CapitalOne -37.45 USD Expenses:Restaurant A flag is used to indicate the status of a transaction, and the particular meaning of the flag is yours to define. We recommend using the following interpretation for them: *: Completed transaction, known amounts, \u201cthis looks correct.\u201d !: Incomplete transaction, needs confirmation or revision, \u201cthis looks incorrect.\u201d In the case of the first example using \u201ctxn\u201d to leave the transaction unflagged, the default flag (\u201c*\u201d) will be set on the transaction object. (I nearly always use the \u201c*\u201d variant and never the keyword one, it is mainly there for consistency with all the other directive formats.) You can also attach flags to the postings themselves, if you want to flag one of the transaction\u2019s legs in particular: 2014-05-05 * \"Transfer from Savings account\" Assets:MyBank:Checking -400.00 USD ! Assets:MyBank:Savings This is useful in the intermediate stage of de-duping transactions (see Getting Started document for more details). The general format of a Transaction directive is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Flag] Account Amount [{Cost}] [@ Price] [Flag] Account Amount [{Cost}] [@ Price] ... The lines that follow the first line are for \u201cPostings.\u201d You can attach as many postings as you want to a transaction. For example, a salary entry might look like this: 2014-03-19 * \"Acme Corp\" \"Bi-monthly salary payment\" Assets:MyBank:Checking 3062.68 USD ; Direct deposit Income:AcmeCorp:Salary -4615.38 USD ; Gross salary Expenses:Taxes:TY2014:Federal 920.53 USD ; Federal taxes Expenses:Taxes:TY2014:SocSec 286.15 USD ; Social security Expenses:Taxes:TY2014:Medicare 66.92 USD ; Medicare Expenses:Taxes:TY2014:StateNY 277.90 USD ; New York taxes Expenses:Taxes:TY2014:SDI 1.20 USD ; Disability insurance The Amount in \u201cPostings\u201d can also be an arithmetic expression using ( ) * / - + . For example, 2014-10-05 * \"Costco\" \"Shopping for birthday\" Liabilities:CreditCard:CapitalOne -45.00 USD Assets:AccountsReceivable:John ((40.00/3) + 5) USD Assets:AccountsReceivable:Michael 40.00/3 USD Expenses:Shopping The crucial and only constraint on postings is that the sum of their balance amounts must be zero. This is explained in full detail below.","title":"Transactions"},{"location":"beancount_language_syntax.html#metadata","text":"It\u2019s also possible to attach metadata to the transaction and/or any of its postings, so the fully general format is: YYYY-MM-DD [txn|Flag] [[Payee] Narration] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... [Flag] Account Amount [{Cost}] [@ Price] [Key: Value] ... ... See the dedicated section on metadata below.","title":"Metadata"},{"location":"beancount_language_syntax.html#payee-narration","text":"A transaction may have an optional \u201cpayee\u201d and/or a \u201cnarration.\u201d In the first example above, the payee is \u201cCafe Mogador\u201d and the narration is \u201cLamb tagine with wine\u201d. The payee is a string that represents an external entity that is involved in the transaction. Payees are sometimes useful on transactions that post amounts to Expense accounts, whereby the account accumulates a category of expenses from multiple businesses. A good example is \u201c Expenses:Restaurant \u201d, which will include all postings for the various restaurants one might visit. A narration is a description of the transaction that you write. It can be a comment about the context, the person who accompanied you, some note about the product you bought... whatever you want it to be. It\u2019s for you to insert whatever you like. I like to insert notes when I reconcile, it\u2019s quick and I can refer to my notes later on, for example, to answer the question \u201cWhat was the name of that great little sushi place I visited with Andreas on the West side last winter?\u201d If you place a single string on a transaction line, it becomes its narration: 2014-05-05 * \"Lamb tagine with wine\" \u2026 If you want to set just a payee, put an empty narration string: 2014-05-05 * \"Cafe Mogador\" \"\" \u2026 For legacy reasons, a pipe symbol (\u201c|\u201d) is accepted between those strings (but this will be removed at some point in the future): 2014-05-05 * \"Cafe Mogador\" | \"\" \u2026 You may also leave out either (but you must provide a flag): 2014-05-05 * \u2026 Note for Ledger users. Ledger does not have separate narration and payee fields, it has only one field, which is referred to by the \u201cPayee\u201d metadata tag, and the narration ends up in a saved comment (a \u201cpersistent note\u201d). In Beancount, a Transaction object simply has two fields: payee and narration, where payee just happens to have an empty value a lot of the time. For a deeper discussion of how and when to use payees or not, see Payees, Subaccounts, and Assets .","title":"Payee & Narration"},{"location":"beancount_language_syntax.html#costs-and-prices","text":"Postings represent a single amount being deposited to or withdrawn from an account. The simplest type of posting includes only its amount: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard 400.00 USD If you converted the amount from another currency, you must provide a conversion rate to balance the transaction (see next section). This is done by attaching a \u201cprice\u201d to the posting, which is the rate of conversion: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @ 1.09 CAD Assets:FR:SocGen:Checking 436.01 CAD You could also use the \u201c@@\u201d syntax to specify the total cost: 2012-11-03 * \"Transfer to account in Canada\" Assets:MyBank:Checking -400.00 USD @@ 436.01 CAD Assets:FR:SocGen:Checking 436.01 CAD Beancount will automatically compute the per-unit price, that is 1.090025 CAD (note that the precision will differ between the last two examples). After the transaction, we are not interested in keeping track of the exchange rate for the units of USD deposited into the account; those units of \u201cUSD\u201d are simply deposited. In a sense, the rate at which they were converted at has been forgotten. However, some commodities that you deposit in accounts must be \u201cheld at cost.\u201d This happens when you want to keep track of the cost basis of those commodities. The typical use case is investing, for example when you deposit shares of a stock in an account. What you want in that circumstance is for the acquisition cost of the commodities you deposit to be attached to the units, such that when you remove those units later on (when you sell), you should be able to identify by cost which of those units to remove, in order to control the effect of taxes (and avoid errors). You can imagine that for each of the units in the account there is an attached cost basis. This will allow us to automatically compute capital gains later. In order to specify that a posting to an account is to be held at a specific cost, include the cost in curly brackets: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 10 IVV {183.07 USD} Assets:ETrade:Cash -1830.70 USD This is the subject of a deeper discussion. Refer to \u201c How Inventories Work \u201d and \u201c Trading with Beancount \u201d documents for an in-depth discussion of these topics. Finally, you can include both a cost and a price on a posting: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains The price will only be used to insert a price entry in the prices database (see Price section below). This is discussed in more details in the Balancing Postings section of this document. Important Note. Amounts specified as either per-share or total prices or costs are always unsigned . It is an error to use a negative sign or a negative cost and Beancount will raise an error if you attempt to do so.","title":"Costs and Prices"},{"location":"beancount_language_syntax.html#balancing-rule-the-weight-of-postings","text":"A crucial aspect of the double-entry method is ensuring that the sum of all the amounts on its postings equals ZERO, in all currencies. This is the central, non-negotiable condition that engenders the accounting equation, and makes it possible to filter any subset of transactions and drawing balance sheets that balance to zero. But with different types of units, the previously introduced price conversions and units \u201cheld at cost\u201d, what does it all mean? It\u2019s simple: we have devised a simple and clear rule for obtaining an amount and a currency from each posting, to be used for balancing them together. We call this the \u201cweight\u201d of a posting, or the balancing amount. Here is a short example of weights derived from postings using the four possible types of cost/price combinations: YYYY-MM-DD Account 10.00 USD -> 10.00 USD Account 10.00 CAD @ 1.01 USD -> 10.10 USD Account 10 SOME {2.02 USD} -> 20.20 USD Account 10 SOME {2.02 USD} @ 2.50 USD -> 20.20 USD Here is the explanation of how it is calculated: If the posting has only an amount and no cost, the balance amount is just the amount and currency on that posting. Using the first example from the previous section, the amount is -400.00 USD, and that is balanced against the 400.00 USD of the second leg. If the posting has only a price , the price is multiplied by the number of units and the price currency is used. In the second example from the preceding section, that is -400.00 USD x 1.09 CAD(/USD) = -436.00 CAD, and that is balanced against the other posting of 436.00 CAD 2 . If the posting has a cost , the cost is multiplied by the number of units and the cost currency is used. In the third example from the preceding section, that is 10 IVV x 183.08 USD(/IVV) = 1830.70 USD. That is balanced against the cash leg of -1830.70 USD, so all is good. Finally, if a posting has both a cost and a price , we simply ignore the price. This optional price is used later on to generate an entry in the in-memory prices database, but it is not used in balancing at all. With this rule, you should be able to easily balance all your transactions. Moreover, this rule makes it possible to let Beancount automatically calculate capital gains for you (see Trading with Beancount for details).","title":"Balancing Rule - The \u201cweight\u201d of postings"},{"location":"beancount_language_syntax.html#reducing-positions","text":"When you post a reduction to a position in an account, the reduction must always match an existing lot. For example, if an account holds 3200 USD and a transaction posts a -1200 USD change to that account, the 1200 USD match against the existing 3200 USD, and the result is a single position of 2000 USD. This also works for negative values. For example, if an account has a -1300 USD balance and you post a +2000 USD change to it, you obtain a 700 USD balance. A change posted to an account, regardless of the account type, can result in a positive or negative balance; there are no limitations on the balances of simple commodity amounts (that is, those with no cost associated to them). For example, while Assets accounts normally have a positive balance and Liabilities accounts usually a negative one, you can legally credit an Assets account to a negative balance, or debit a Liabilities account to a positive balance. This is because in the real world these things do happen: you might write a check too many and obtain temporary credit from your bank\u2019s checking account (usually along with an outrageous \u201coverdraft\u201d fee), or pay that credit card balance twice by mistake. For commodities held at cost, the cost specification of the posting must match one of the lots held in the inventory before the transaction is applied. The list of lots is gathered, and matched against the specification in the {...} part of the posting. For example, if you provide a cost, only those lots whose cost match that will remain. If you provide a date, only those lots which match that date will remain. And you can use a label as well. If you provide a cost and a date, both of these are matched against the list of candidate lots to reduce. This is essentially a filter on the list of lots. If the filtered list results in a single lot, that lot is chosen to be reduced. If the list results in multiple lots, but the total amount being reduced equals the total amount in the lots, all those lots are reduced by that posting. For example, if in the past you had the following transactions: 2014-02-11 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 20 IVV {183.07 USD, \"ref-001\"} \u2026 2014-03-22 * \"Bought shares of S&P 500\" Assets:ETrade:IVV 15 IVV {187.12 USD} \u2026 Each of the following reductions would be unambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {183.07 USD} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {2014-02-11} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {\"ref-001\"} \u2026 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -35 IVV {} \u2026 However, the following would be ambiguous: 2014-05-01 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -20 IVV {} \u2026 If multiple lots match against the reducing posting and their number is not the total number, we are in a situation of ambiguous matches . What happens then, is that the account\u2019s booking method is invoked. There are multiple booking methods, but by default, all accounts are set to use the \u201cSTRICT\u201d booking method. This method simply issues an error in an ambiguous situation. You may set the account\u2019s booking method to \u201cFIFO\u201d to instruct Beancount to select the oldest of the lots. Or \u201cLIFO\u201d for the latest (youngest) of the lots. This will automatically select all the necessary matching lots to fulfill the reduction. PLEASE NOTE! Requiring the dates to match will be dealt with more sensibly in the near future. See A Proposal for an Improvement on Inventory Booking for details of this upcoming change. For such postings, a change that results in a negative number of units is usually impossible. Beancount does not currently allow holding a negative number of a commodity held at cost. For example, an input with just this transaction will fail: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD If it did not, this would result in a balance of -10 units of MSFT. On the other hand, if the account had a balance of 12 units of MSFT held at 43.40 USD on 5/23, the transaction would book just fine, reducing the existing 12 units to 2. Most often, the error that will occur is that the account will be holding a balance of 10 or more units of MSFT at a different cost , and the user will specify an incorrect value for the cost. For instance, if the account had a positive balance of 20 MSFT {42.10 USD}, the transaction above would still fail, because there aren\u2019t 10 or more units of MSFT at 43.40 USD to remove from. This constraint is enforced for a few reasons: Mistakes in data entry for stock units are not uncommon, they have an important negative impact on the correctness of your Ledger\u2014the amounts are usually large\u2014and they will typically trigger an error from this constraint. Therefore, the error check is a useful way to detect these types of errors. Negative numbers of units held at cost are fairly rare. Chances are you don\u2019t need them at all. Exceptions include: short sales of stock, holding spreads on futures contracts, and depending on how you account for them, short positions in currency trading. This is why this check is enabled by default. PLEASE NOTE! In a future version of Beancount, we will relax this constraint somewhat. We will allow an account to hold a negative number of units of a commodity if and only if there are no other units of that commodity held in the account. Either that, or we will allow you to mark an account as having no such constraints at all. The purpose is to allow the account of short positions in commodities. The only blocking factor is this constraint. For more details of the inventory booking algorithm, see the How Inventories Work document.","title":"Reducing Positions"},{"location":"beancount_language_syntax.html#amount-interpolation","text":"Beancount is able to fill in some of the details of a transaction automatically. You can currently elide the amount of at most one posting within a transaction: 2012-11-03 * \"Transfer to pay credit card\" Assets:MyBank:Checking -400.00 USD Liabilities:CreditCard In the example above, the amount of the credit card posting has been elided. It is automatically calculated by Beancount at 400.00 USD to balance the transaction. This also works with multiple postings, and with postings with costs: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} Assets:ETrade:Cash 1979.90 USD Income:ETrade:CapitalGains In this case, the units of IVV are sold at a higher price ($197.90) than they were bought for ($183.07). The cash first posting has a weight of -10 x 183.07 = -1830.70 and the second posting a straightforward $1979.90. The last posting will be ascribed the difference, that is, a balance of -149.20 USD , which is to say, a gain of $149.20. When calculating the amount to be balanced, the same balance amounts that are used to check that the transaction balances to zero are used to fill in the missing amounts. For example, the following would not trigger an error: 2014-07-11 * \"Sold shares of S&P 500\" Assets:ETrade:IVV -10 IVV {183.07 USD} @ 197.90 USD Assets:ETrade:Cash The cash account would receive 1830.70 USD automatically, because the balance amount of the IVV posting is -1830.70 USD (if a posting has both a cost and a price, the cost basis is always used and the optional price ignored). While this is accepted and correct from a balancing perspective, this would be incomplete from an accounting perspective: the capital gain on a sale needs to be accounted for separately and besides, the amount deposited to the cash account if you did as above would fall short of the real deposit (1979.00 USD) and hopefully a subsequent balance assertion in the cash account would indicate this oversight by triggering an error. Finally, this also works when the balance includes multiple commodities: 2014-07-12 * \"Uncle Bob gave me his foreign currency collection!\" Income:Gifts -117.00 ILS Income:Gifts -3000.00 INR Income:Gifts -800.00 JPY Assets:ForeignCash Multiple postings (one for each commodity required to balance) will be inserted to replace the elided one.","title":"Amount Interpolation"},{"location":"beancount_language_syntax.html#tags","text":"Transactions can be tagged with arbitrary strings: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 Expenses:Flights -1230.27 USD Liabilities:CreditCard This is similar to the popular idea of \u201chash tagging\u201d on Twitter and such. These tags essentially allow you to mark a subset of transactions. They can then be used as a filter to generate reports on only this subset of transactions. They have numerous uses. I like to use them to mark all my trips. Multiple tags can be specified as well: 2014-04-23 * \"Flight to Berlin\" #berlin-trip-2014 #germany Expenses:Flights -1230.27 USD Liabilities:CreditCard (If you want to store key-value pairs on directives, see the section on metadata below.)","title":"Tags"},{"location":"beancount_language_syntax.html#the-tag-stack","text":"Oftentimes multiple transactions related to a single tag will be entered consecutively in a file. As a convenience, the parser can automatically tag transactions within a block of text. How this works is simple: the parser has a \u201cstack\u201d of current tags which it applies to all transactions as it reads them one-by-one. You can push and pop tags onto/from this stack, like this: pushtag #berlin-trip-2014 2014-04-23 * \"Flight to Berlin\" Expenses:Flights -1230.27 USD Liabilities:CreditCard poptag #berlin-trip-2014 This way, you can also push multiple tags onto a long, consecutive set of transactions without having to type them all in.","title":"The Tag Stack"},{"location":"beancount_language_syntax.html#links","text":"Transactions can also be linked together. You may think of the link as a special kind of tag that can be used to group together a set of financially related transactions over time. For example you may use links to group together transactions that are each related with a specific invoice. This allows to track payments (or write-offs) associated with the invoice: 2014-02-05 * \"Invoice for January\" ^invoice-pepe-studios-jan14 Income:Clients:PepeStudios -8450.00 USD Assets:AccountsReceivable 2014-02-20 * \"Check deposit - payment from Pepe\" ^invoice-pepe-studios-jan14 Assets:BofA:Checking 8450.00 USD Assets:AccountsReceivable Or track multiple transfers related to a single nefarious purpose: 2014-02-05 * \"Moving money to Isle of Man\" ^transfers-offshore-17 Assets:WellsFargo:Savings -40000.00 USD Assets:WellsFargo:Checking 40000.00 USD 2014-02-09 * \"Wire to FX broker\" ^transfers-offshore-17 Assets:WellsFargo:Checking -40025.00 USD Expenses:Fees:WireTransfers 25.00 USD Assets:OANDA:USDollar 40000.00 2014-03-16 * \"Conversion to offshore beans\" ^transfers-offshore-17 Assets:OANDA:USDollar -40000.00 USD Assets:OANDA:GBPounds 23391.81 GBP @ 1.71 USD 2014-03-16 * \"God save the Queen (and taxes)\" ^transfers-offshore-17 Assets:OANDA:GBPounds -23391.81 GBP Expenses:Fees:WireTransfers 15.00 GBP Assets:Brittania:PrivateBanking 23376.81 GBP Linked transactions can be rendered by the web interface in their own dedicated journal, regardless of the current view/filtered set of transactions (the list of links is a global page).","title":"Links"},{"location":"beancount_language_syntax.html#balance-assertions","text":"A balance assertion is a way for you to input your statement balance into the flow of transactions. It tells Beancount to verify that the number of units of a particular commodity in some account should equal some expected value at some point in time. For instance, this 2014-12-26 balance Liabilities:US:CreditCard -3492.02 USD says \u201cCheck that the number of USD units in account \u201c Liabilities:US:CreditCard \u201d on the morning of December 26th, 2014 is -3492.02 USD.\u201d When processing the list of entries, if Beancount encounters a different balance than this for USD it will report an error. If no error is reported, you should have some confidence that the list of transactions that precedes it in this account is highly likely to be correct. This is useful in practice, because in many cases some transactions can get imported separately from the accounts of each of their postings (see the de-duping problem ). This can result in you booking the same transaction twice without noticing, and regularly inserting a balance assertion will catch that problem every time. The general format of the Balance directive is: YYYY-MM-DD balance Account Amount Note that a balance assertion, like all other non-transaction directives, applies at the beginning of its date (i.e., midnight at the start of day). Just imagine that the balance check occurs right after midnight on that day. Balance assertions only make sense on balance sheet accounts (Assets and Liabilities). Because the postings on Income and Expenses accounts are only interesting because of their transient value, i.e., for these accounts we\u2019re interested in sums of changes over a period of time (not the absolute value), it makes little sense to use a balance assertion on income statement accounts. Also, take note that each account benefits from an implicit balance assertion that the account is empty after it is opened at the date of its Open directive. You do not need to explicitly assert a zero balance when opening accounts.","title":"Balance Assertions"},{"location":"beancount_language_syntax.html#multiple-commodities","text":"A Beancount account may contain more than one commodity (although in practice, you will find that this does not occur often, it is sensible to create dedicated sub-accounts to hold each commodity, for example, holding a portfolio of stocks). A balance assertion applies only to the commodity of the assertion; it leaves the other commodities in the balance unchecked. If you want to check multiple commodities, use multiple balance assertions, like this: ; Check cash balances from wallet 2014-08-09 balance Assets:Cash 562.00 USD 2014-08-09 balance Assets:Cash 210.00 CAD 2014-08-09 balance Assets:Cash 60.00 EUR There is currently no way to exhaustively check the full list of commodities in an account ( a proposal is underway ). Note that in this example if an exhaustive check really matters to you, you could circumvent by defining a subaccount of the cash account to segregate each commodity separately, like this Assets:Cash:USD , Assets:Cash:CAD .","title":"Multiple Commodities"},{"location":"beancount_language_syntax.html#lots-are-aggregated","text":"The balance assertion applies to the sum of units of a particular commody, irrespective of their cost. For example, if you hold three lots of the same commodity in an account, for example, 5 HOOL {500 USD} and 6 HOOL {510 USD}, the following balance check should succeed: 2014-08-09 balance Assets:Investing:HOOL 11 HOOL All the lots are aggregated together and you can verify their number of units.","title":"Lots Are Aggregated"},{"location":"beancount_language_syntax.html#checks-on-parent-accounts","text":"Balance assertions may be performed on parent accounts, and will include the balances of theirs and their sub-accounts: 2014-01-01 open Assets:Investing 2014-01-01 open Assets:Investing:Apple AAPL 2014-01-01 open Assets:Investing:Amazon AMZN 2014-01-01 open Assets:Investing:Microsoft MSFT 2014-01-01 open Equity:Opening-Balances 2014-06-01 * Assets:Investing:Apple 5 AAPL {578.23 USD} Assets:Investing:Amazon 5 AMZN {346.20 USD} Assets:Investing:Microsoft 5 MSFT {42.09 USD} Equity:Opening-Balances 2014-07-13 balance Assets:Investing 5 AAPL 2014-07-13 balance Assets:Investing 5 AMZN 2014-07-13 balance Assets:Investing 5 MSFT Note that this does require that a parent account have been declared as Open, in order to be legitimately used in the balance assertions directive.","title":"Checks on Parent Accounts"},{"location":"beancount_language_syntax.html#before-close","text":"It is useful to insert a balance assertion for 0 units just before closing an account, just to make sure its contents are empty as you close it. The Close directive does not insert that for you automatically (we may eventually build a plug-in for it).","title":"Before Close"},{"location":"beancount_language_syntax.html#local-tolerance","text":"It's pretty common that sometimes one needs to override the tolerance on the balance check to loosen it on that balance assertion. This can be done using a local tolerance amount off of the balance amount, like this: 2013-09-20 balance Assets:Investing:Funds 319.020 ~ 0.002 RGAGX","title":"Local Tolerance"},{"location":"beancount_language_syntax.html#pad","text":"A padding directive automatically inserts a transaction that will make the subsequent balance assertion succeed, if it is needed. It inserts the difference needed to fulfill that balance assertion. (What \u201crubber space\u201d is in LaTeX, Pad directives are to balances in Beancount.) Note that by \u201csubsequent,\u201d I mean in date order , not in the order of the declarations in the file. This is the conceptual equivalent of a transaction that will automatically expand or contract to fill the difference between two balance assertions over time. It looks like this: 2014-06-01 pad Assets:BofA:Checking Equity:Opening-Balances The general format of the Pad directive is: YYYY-MM-DD pad Account AccountPad The first account is the account to credit the automatically calculated amount to. This is the account that should have a balance assertion following it (if the account does not have a balance assertion, the pad entry is benign and does nothing). The second account is for the other leg of the transaction, it is the source where the funds will come from, and this is almost always some Equity account. The reason for this is that this directive is generally used for initializing the balances of new accounts, to save us from having to either insert such a directive manually, or from having to enter the full past history of transactions that will bring the account to its current balance. Here is a realistic example usage scenario: ; Account was opened way back in the past. 2002-01-17 open Assets:US:BofA:Checking 2002-01-17 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD This will result in the following transaction being inserted right after the Pad directive, on the same date: 2002-01-17 P \"(Padding inserted for balance of 987.34 USD)\" Assets:US:BofA:Checking 987.34 USD Equity:Opening-Balances -987.34 USD This is a normal transaction\u2014you will see it appear in the rendered journals along with the other ones. (Note the special \u201cP\u201d flag, which can be used by scripts to find these.) Observe that without balance assertions, Pad directives make no sense. Therefore, like balance assertions, they are normally only used on balance sheet accounts (Assets and Liabilities). You could also insert Pad entries between balance assertions, it works too. For example: 2014-07-09 balance Assets:US:BofA:Checking 987.34 USD \u2026 more transactions\u2026 2014-08-08 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-08-09 balance Assets:US:BofA:Checking 1137.23 USD Without intervening transactions, this would insert the following padding transaction: 2014-08-08 P \"(Padding inserted for balance of 1137.23 USD)\" Assets:US:BofA:Checking 149.89 USD Equity:Opening-Balances -149.89 USD In case that\u2019s not obvious, 149.89 USD is the difference between 1137.23 USD and 987.34 USD. If there were more intervening transactions posting amounts to the checking account, the amount would automatically have been adjusted to make the second assertion pass.","title":"Pad"},{"location":"beancount_language_syntax.html#unused-pad-directives","text":"You may not currently leave unused Pad directives in your input file. They will trigger an error: 2014-01-01 open Assets:US:BofA:Checking 2014-02-01 pad Assets:US:BofA:Checking Equity:Opening-Balances 2014-06-01 * \"Initializing account\" Assets:US:BofA:Checking 212.00 USD Equity:Opening-Balances 2014-07-09 balance Assets:US:BofA:Checking 212.00 USD (Being this strict is a matter for debate, I suppose, and it could eventually be moved to an optional plugin.)","title":"Unused Pad Directives"},{"location":"beancount_language_syntax.html#commodities","text":"Note that the Pad directive does not specify any commodities at all. All commodities with corresponding balance assertions in the account are affected. For instance, the following code would have a padding directive insert a transaction with separate postings for USD and CAD: 2002-01-17 open Assets:Cash 2002-01-17 pad Assets:Cash Equity:Opening-Balances 2014-07-09 balance Assets:Cash 987.34 USD 2014-07-09 balance Assets:Cash 236.24 CAD If the account contained other commodities that aren\u2019t balance asserted, no posting would be inserted for those.","title":"Commodities"},{"location":"beancount_language_syntax.html#cost-basis","text":"At the moment, Pad directives do not work with accounts holding positions held at cost. The directive is really only useful for cash accounts. (This is mainly because balance assertions do not yet allow specifying a cost basis to assert. It is possible that in the future we decide to support asserting the total cost basis, and that point we could consider supporting padding with cost basis.)","title":"Cost Basis"},{"location":"beancount_language_syntax.html#multiple-paddings","text":"You cannot currently insert multiple padding entries for the same account and commodity: 2002-01-17 open Assets:Cash 2002-02-01 pad Assets:Cash Equity:Opening-Balances 2002-03-01 pad Assets:Cash Equity:Opening-Balances 2014-04-19 balance Assets:Cash 987.34 USD (There is a proposal pending to allow this and spread the padding amount evenly among all the intervening Pad directives, but it is as of yet unimplemented.)","title":"Multiple Paddings"},{"location":"beancount_language_syntax.html#notes","text":"A Note directive is simply used to attach a dated comment to the journal of a particular account, like this: 2013-11-03 note Liabilities:CreditCard \"Called about fraudulent card.\" When you render the journal, the note should be rendered in context. This can be useful to record facts and claims associated with a financial event. I often use this to record snippets of information that would otherwise not make their way to a transaction. The general format of the Note directive is: YYYY-MM-DD note Account Description The description string may be split among multiple lines.","title":"Notes"},{"location":"beancount_language_syntax.html#documents","text":"A Document directive can be used to attach an external file to the journal of an account: 2013-11-03 document Liabilities:CreditCard \"/home/joe/stmts/apr-2014.pdf\" The filename gets rendered as a browser link in the journals of the web interface for the corresponding account and you should be able to click on it to view the contents of the file itself. This is useful to integrate account statements and other downloads into the flow of accounts, so that they\u2019re easily accessible from a few clicks. Scripts could also be written to obtain this list of documents from an account name and do something with them. The general format of the Document directive is: YYYY-MM-DD document Account PathToDocument","title":"Documents"},{"location":"beancount_language_syntax.html#documents-from-a-directory","text":"A more convenient way to create these entries is to use a special option to specify directories that contain a hierarchy of sub-directories that mirrors that of the chart of accounts. For example, the Document directive shown in the previous section could have been created from a directory hierarchy that looks like this: stmts `-- Liabilities `-- CreditCard `-- 2014-04-27.apr-2014.pdf By simply specifying the root directory as an option (note the absence of a trailing slash): option \"documents\" \"/home/joe/stmts\" The files that will be picked up are those that begin with a date as above, in the YYYY-MM-DD format. You may specify this option multiple times if you have many such document archives. In the past I have used one directory for each year (if you scan all your documents, the directory can grow to a large size, scanned documents tend to be large files). Organizing your electronic document statements and scans using the hierarchy of your ledger\u2019s accounts is a fantastic way to organize them and establish a clear, unambiguous place to find these documents later on, when they\u2019re needed.","title":"Documents from a Directory"},{"location":"beancount_language_syntax.html#prices","text":"Beancount sometimes creates an in-memory data store of prices for each commodity, that is used for various reasons. In particular, it is used to report unrealized gains on account holdings. Price directives can be used to provide data points for this database. A Price directive establishes the rate of exchange between one commodity (the base currency) and another (the quote currency): 2014-07-09 price HOOL 579.18 USD This directive says: \u201cThe price of one unit of HOOL on July 9th, 2014 was 579.18 USD.\u201d Price entries for currency exchange rates work the same way: 2014-07-09 price USD 1.08 CAD The general format of the Price directive is: YYYY-MM-DD price Commodity Price Remember that Beancount knows nothing about what HOOL, USD or CAD are. They are opaque \u201cthings.\u201d You attach meaning to them. So setting a price per hour for your vacation hours is perfectly valid and useful too, if you account for your unused vacations on such terms: 2014-07-09 price VACHR 38.46 USD ; Equiv. $80,000 year","title":"Prices"},{"location":"beancount_language_syntax.html#prices-from-postings","text":"If you use the beancount.plugins.implicit_prices plugin, every time a Posting appears that has a cost or an optional price declared, it will use that cost or price to automatically synthesize a Price directive. For example, this transaction: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD automatically becomes this after parsing: 2014-05-23 * Assets:Investments:MSFT -10 MSFT {43.40 USD} Assets:Investments:Cash 434.00 USD 2014-05-23 price MSFT 43.40 USD This is convenient and if you enable it, will probably be where most of the price points in your ledger\u2019s price database will come from. You can print a table of the parsed prices from a ledger (it is just another type of report).","title":"Prices from Postings"},{"location":"beancount_language_syntax.html#prices-on-the-same-day","text":"Notice that there is no notion of time; Beancount is not designed to solve problems for intra-day traders, though of course, it certainly is able to handle multiple trades per day. It just stores its prices per day. (Generally, if you need many features that require a notion of intra-day time, you\u2019re better off using another system, this is not the scope of a bookkeeping system.) When multiple Price directives do exist for the same day, the last one to appear in the file will be selected for inclusion in the Price database.","title":"Prices on the Same Day"},{"location":"beancount_language_syntax.html#events","text":"Event directives 3 are used to track the value of some variable of your choice over time. For example, your location: 2014-07-09 event \"location\" \"Paris, France\" The above directive says: \u201cChange the value of the \u2018location\u2019 event to \u2018Paris, France\u2019 as of the 9th of July 2014 and onwards.\u201d A particular event type only has a single value per day. The general format of the Event directive is: YYYY-MM-DD event Name Value The event\u2019s Name string does not have to be declared anywhere, it just begins to exist the first time you use the directive. The Value string can be anything you like; it has no prescribed structure. How to use these is best explained by providing examples: Location : You can use events for tracking the city or country you\u2019re in. This is sensible usage within a ledger because the nature of your expenses are heavily correlated to where you are. It\u2019s convenient: if you use Beancount regularly, it is very little effort to add this bit of information to your file. I usually have a \u201ccash daybook\u201d section where I put miscellaneous cash expenses, and that\u2019s where I enter those directives. Address : If you move around a lot, it\u2019s useful to keep a record of your past home addresses. This is sometimes requested on government forms for immigration. The Green Card application process in the US, for instance, Employer : You can record your date of employment and departure for each job this way. Then you can count the number of days you worked there. Trading window : If you\u2019re an employee of a public company, you can record the dates that you\u2019re allowed to trade its stock. This can then be used to ensure you did not trade that stock when the window is closed. Events are often used to report on numbers of days. For example, in the province of Quebec (Canada) you are insured for free health care coverage if \u201c you spend 183 days of the calendar year or more, excluding trips of 21 days or less .\u201d If you travel abroad a lot, you could easily write a script to warn you of remaining day allocations to avoid losing your coverage. Many expats are in similar situations. In the same vein, the IRS recognizes US immigrants as \u201cresident alien\u201d\u2014and thus subject to filing taxes, yay!\u2014if you pass the Substantial Presence Test , which is defined as \u201cbeing physically present in the US on at least 31 days during the current year, and 193 days during the 3 year period that includes the current year and the 2 years immediately before that, counting: all the days you were present in the current year, and \u2153 of the days of the year before that, and \u2159 of the year preceding that one.\u201d Ouch\u2026 my head hurts. This gets complicated. Armed with your location over time, you could report it automatically. Events will also be usable in the filtering language, to specify non-contiguous periods of time. For example, if you are tracking your location using Event directives, you could produce reports for transactions that occur only when you are in a specific location, e.g., \u201cShow me my expenses on all my trips to Germany,\u201d or \u201cGive me a list of payees for restaurant transactions when I\u2019m in Montreal.\u201d PLEASE NOTE! Filters haven\u2019t been implemented yet. Also, reports on events have not yet been re-implemented in Beancount 2.0. They will be reintroduced again soon, as well as filtering. It is worth noticing that the Price and Event directives are the only ones not associated to an account.","title":"Events"},{"location":"beancount_language_syntax.html#query","text":"It can be convenient to be able to associate SQL queries in a Beancount file to be able to run these as a report automatically. This is still an early development / experimental directive. In any case, you can insert queries in the stream of transactions like this: 2014-07-09 query \"france-balances\" \" SELECT account, sum(position) WHERE \u2018trip-france-2014\u2019 in tags\" The grammar is YYYY-MM-DD query Name SqlContents Each query has a name, which will probably be used to invoke its running as a report type. Also, the date of the query is intended to be the date at which the query is intended to be run for, that is, transactions following it should be ignored. If you\u2019re familiar with the SQL syntax, it\u2019s an implicit CLOSE.","title":"Query"},{"location":"beancount_language_syntax.html#custom","text":"The long-term plan for Beancount is to allow plugins and external clients to define their own directive types, to be declared and validated by the Beancount input language parser. In the meantime, a generic directive is provided for clients to prototype new features, e.g., budgeting. 2014-07-09 custom \"budget\" \"...\" TRUE 45.30 USD The grammar for this directive is flexible: YYYY-MM-DD custom TypeName Value1 ... The first argument is a string and is intended to be unique to your directive. Think of this as the type of your directive. Following it, you can put an arbitrary list of strings, dates, booleans, amounts, and numbers. Note that there is no validation that checks that the number and types of arguments following the TypeName is consistent for a particular type. (See this thread for the origin story around this feature.)","title":"Custom"},{"location":"beancount_language_syntax.html#metadata_1","text":"You may attach arbitrary data to each of your entries and postings. The syntax for it looks like this: 2013-03-14 open Assets:BTrade:HOOLI category: \"taxable\" 2013-08-26 * \"Buying some shares of Hooli\" statement: \"confirmation-826453.pdf\" Assets:BTrade:HOOLI 10 HOOL @ {498.45 USD} decision: \"scheduled\" Assets:BTrade:Cash In this example, a \u201ccategory\u201d attribute is attached to an account\u2019s Open directive, a \u201cstatement\u201d attribute is attached to a Transaction directive (with a string value that represents a filename) and a \u201cdecision\u201d attribute has been attached to the first posting of the transaction (the additional indentation from the posting is not strictly necessary but it helps with readability). Metadata can be attached to any directive type. Keys must begin with a lowercase character from a-z and may contain (uppercase or lowercase) letters, numbers, dashes and underscores. Moreover, the values can be any of the following data types: Strings Accounts Currency Dates (datetime.date) Tags Numbers (Decimal) Amount (beancount.core.amount.Amount) There are two ways in which this data can be used: Query tools that come with Beancount (such as bean-query) will allow you to make use of the metadata values for filtering and aggregation. You can access and use the metadata in custom scripts. The metadata values are accessible as a \u201c .meta \u201d attribute on all directives and is a Python dict. There are no special meanings attached to particular attributes, these are intended for users to define. However, all directives are guaranteed to contain a \u2018 filename \u2019 (string) and a \u2018 lineno \u2019 (integer) attribute which reflect the location they were created from. Finally, attributes without a value will be parsed and have a value of 'None'. If an attribute is repeated multiple times, only the first value for this attribute will be parsed and retained and the following values ignored.","title":"Metadata"},{"location":"beancount_language_syntax.html#options","text":"The great majority of a Beancount input file consists in directives, as seen in the previous section. However, there are a few global options that can be set in the input file as well, by using a special undated \u201coption\u201d directive: option \"title\" \"Ed\u2019s Personal Ledger\" The general format of the Option directive is: option Name Value where Name and Value are both strings. Note that depending the option, the effect may be to set a single value, or add to a list of existing values. In other words, some options are lists. There are three ways to view the list of options: In this document , which I update regularly. To view the definitive list of options supported by your installed version, use the following command: bean-doctor list-options Finally, you can peek at the source code as well.","title":"Options"},{"location":"beancount_language_syntax.html#operating-currencies","text":"One notable option is \u201c operating_currency \u201d. By default Beancount does not treat any of the commodities any different from each other. In particular, it doesn\u2019t know that there\u2019s anything special about the most common of commodities one uses: their currencies. For example, if you live in New Zealand, you\u2019re going to have an overwhelming number of NZD commodities in your transactions. But useful reports try to reduce all non-currency commodities into one of the main currencies used. Also, it\u2019s useful to break out the currency units into their own dedicated columns. This may also be useful for exporting in order to avoid having to specify the units for that column and import to a spreadsheet with numbers you can process. For this reason, you are able to declare the most common currencies you use in an option: option \"operating_currency\" \"USD\" You may declare more than one. In any case, this option is only ever used by reporting code, it never changes the behavior of Beancount\u2019s processing or semantics.","title":"Operating Currencies"},{"location":"beancount_language_syntax.html#plugins","text":"In order to load plugin Python modules, use the dedicated \u201cplugin\u201d directive: plugin \"beancount.plugins.module_name\" The name of a plugin should be the name of a Python module in your PYTHONPATH. Those modules will be imported by the Beancount loader and run on the list of parsed entries in order for the plugins to transform the entries or output errors. This allows you to integrate some of your code within Beancount, making arbitrary transformations on the entries. See Scripting & Plugins for details. Plugins also optionally accept some configuration parameters. These can be provided by an optional final string argument, like this: plugin \"beancount.plugins.module_name\" \"configuration data\" The general format of the Option directive is: plugin ModuleName StringConfig The format of the configuration data is plugin-dependent. At the moment, an arbitrary string is passed provided to the plugin. See the plugins\u2019 documentation for specific detail on what it can accept. Also see the \u201cplugin processing mode\u201d option which affects the list of built-in plugins that get run.","title":"Plugins"},{"location":"beancount_language_syntax.html#includes","text":"Include directives are supported. This allows you to split up large input files into multiple files. The syntax looks like this: include \"path/to/include/file.beancount\" The general format is include Filename The specified path can be an absolute or a relative filename. If the filename is relative, it is relative to the including filename\u2019s directory. This makes it easy to put relative includes in a hierarchy of directories that can be placed under source control and checked out anywhere. Include directives are not processed strictly (as in C, for example). The include directives are accumulated by the Beancount parser and processed separately by the loader. This is possible because the order of declarations of a Beancount input file is not relevant. However, for the moment, options are parsed per-file. The options-map that is kept for post-parse processing is the options-map returned for the top-level file. This is probably subject to review in the future.","title":"Includes"},{"location":"beancount_language_syntax.html#whats-next","text":"This document described all the possible syntax of the Beancount language. If you haven\u2019t written any Beancount input yet, you can head to the Getting Started guide, or browse through a list of practical use cases in the Command-line Accounting Cookbook . Note that there exists an \u201cOpen\u201d directive that is used to provide the start date of each account. That can be located anywhere in the file, it does not have to appear in the file somewhere before you use an account name. You can just start using account names in transactions right away, though all account names that receive postings to them will eventually have to have a corresponding Open directive with a date that precedes all transactions posted to the account in the input file. \u21a9 Note that this is valid whether the price is specified as a per-unit price with the @ syntax or as a total price using the @@ syntax. \u21a9 I really dislike the name \u201cevent\u201d for this directive. I\u2019ve been trying to find a better alternative, so far without success. The name \u201cregister\u201d might be more appropriate, as it resembles that of a processor\u2019s register, but that could be confused with an account register report. \u201cvariable\u201d might work, but somehow that just sounds too computer-sciency and out of context. If you can think of something better, please make a suggestion and I\u2019ll seriously entertain a complete rename (with legacy support for \u201cevent\u201d). \u21a9","title":"What\u2019s Next?"},{"location":"beancount_options_reference.html","text":"option \"title\" \"Joe Smith's Personal Ledger\" The title of this ledger / input file. This shows up at the top of every page. option \"name_assets\" \"Assets\" option \"name_liabilities\" \"Liabilities\" option \"name_equity\" \"Equity\" option \"name_income\" \"Income\" option \"name_expenses\" \"Expenses\" Root names of every account. This can be used to customize your category names, so that if you prefer \"Revenue\" over \"Income\" or \"Capital\" over \"Equity\", you can set them here. The account names in your input files must match, and the parser will validate these. You should place these options at the beginning of your file, because they affect how the parser recognizes account names. option \"account_previous_balances\" \"Opening-Balances\" Leaf name of the equity account used for summarizing previous transactions into opening balances. option \"account_previous_earnings\" \"Earnings:Previous\" Leaf name of the equity account used for transferring previous retained earnings from income and expenses accrued before the beginning of the exercise into the balance sheet. option \"account_previous_conversions\" \"Conversions:Previous\" Leaf name of the equity account used for inserting conversions that will zero out remaining amounts due to transfers before the opening date. This will essentially \"fixup\" the basic accounting equation due to the errors that priced conversions introduce. option \"account_current_earnings\" \"Earnings:Current\" Leaf name of the equity account used for transferring current retained earnings from income and expenses accrued during the current exercise into the balance sheet. This is most often called \"Net Income\". option \"account_current_conversions\" \"Conversions:Current\" Leaf name of the equity account used for inserting conversions that will zero out remaining amounts due to transfers during the exercise period. option \"account_rounding\" \"Rounding\" The name of an account to be used to post to and accumulate rounding error. This is unset and this feature is disabled by default; setting this value to an account name will automatically enable the addition of postings on all transactions that have a residual amount. option \"conversion_currency\" \"NOTHING\" The imaginary currency used to convert all units for conversions at a degenerate rate of zero. This can be any currency name that isn't used in the rest of the ledger. Choose something unique that makes sense in your language. option \"inferred_tolerance_default\" \"CHF:0.01\" option \"default_tolerance\" \"CHF:0.01\" THIS OPTION IS DEPRECATED: This option has been renamed to 'inferred_tolerance_default' Mappings of currency to the tolerance used when it cannot be inferred automatically. The tolerance at hand is the one used for verifying (1) that transactions balance, (2) explicit balance checks from 'balance' directives balance, and (3) in the precision used for padding (from the 'pad' directive). The values must be strings in the following format: : for example, 'USD:0.005'. By default, the tolerance used for currencies without an inferred value is zero (which means infinite precision). As a special case, this value, that is, the fallabck value used for all currencies without an explicit default can be overridden using the '*' currency, like this: '*:0.5'. Used by itself, this last example sets the fallabck tolerance as '0.5' for all currencies. (Note: The new value of this option is \"inferred_tolerance_default\"; it renames the option which used to be called \"default_tolerance\". The latter name was confusing.) For detailed documentation about how precision is handled, see this doc: http://furius.ca/beancount/doc/tolerances (This option may be supplied multiple times.) option \"inferred_tolerance_multiplier\" \"1.1\" A multiplier for inferred tolerance values. When the tolerance values aren't specified explicitly via the 'inferred_tolerance_default' option, the tolerance is inferred from the numbers in the input file. For example, if a transaction has posting with a value like '32.424 CAD', the tolerance for CAD will be inferred to be 0.001 times some multiplier. This is the muliplier value. We normally assume that the institution we're reproducing this posting from applies rounding, and so the default value for the multiplier is 0.5, that is, half of the smallest digit encountered. You can customize this multiplier by changing this option, typically expanding it to account for amounts slightly beyond the usual tolerance, for example, if you deal with institutions with bad of unexpected rounding behaviour. For detailed documentation about how precision is handled, see this doc: http://furius.ca/beancount/doc/tolerances option \"infer_tolerance_from_cost\" \"True\" Enable a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 * M = 0.045 USD (where M is the inferred_tolerance_multiplier) and this is added to the mix to enlarge the tolerance allowed for units of USD on that transaction. All the normally inferred tolerances (see http://furius.ca/beancount/doc/tolerances) are still taken into account. Enabling this flag only makes the tolerances potentially wider. option \"tolerance\" \"0.015\" THIS OPTION IS DEPRECATED: The 'tolerance' option has been deprecated and has no effect. The tolerance allowed for balance checks and padding directives. In the real world, rounding occurs in various places, and we need to allow a small (but very small) amount of tolerance in checking the balance of transactions and in requiring padding entries to be auto- inserted. This is the tolerance amount, which you can override. option \"use_legacy_fixed_tolerances\" \"True\" Restore the legacy fixed handling of tolerances. Balance and Pad directives have a fixed tolerance of 0.015 units, and Transactions balance at 0.005 units. For any units. This is intended as a way for people to revert the behavior of Beancount to ease the transition to the new inferred tolerance logic. See http://furius.ca/beancount/doc/tolerances for more details. option \"documents\" \"/path/to/your/documents/archive\" A list of directory roots, relative to the CWD, which should be searched for document files. For the document files to be automatically found they must have the following filename format: YYYY-MM-DD.(.*) (This option may be supplied multiple times.) option \"operating_currency\" \"USD\" A list of currencies that we single out during reporting and create dedicated columns for. This is used to indicate the main currencies that you work with in real life. (Refrain from listing all the possible currencies here, this is not what it is made for; just list the very principal currencies you use daily only.) Because our system is agnostic to any unit definition that occurs in the input file, we use this to display these values in table cells without their associated unit strings. This allows you to import the numbers in a spreadsheet (e.g, \"101.00 USD\" does not get parsed by a spreadsheet import, but \"101.00\" does). If you need to enter a list of operating currencies, you may input this option multiple times, that is, you repeat the entire directive once for each desired operating currency. (This option may be supplied multiple times.) option \"render_commas\" \"TRUE\" A boolean, true if the number formatting routines should output commas as thousand separators in numbers. option \"plugin_processing_mode\" \"raw\" A string that defines which set of plugins is to be run by the loader: if the mode is \"default\", a preset list of plugins are automatically run before any user plugin. If the mode is \"raw\", no preset plugins are run at all, only user plugins are run (the user should explicitly load the desired list of plugins by using the 'plugin' option. This is useful in case the user wants full control over the ordering in which the plugins are run). option \"plugin\" \"beancount.plugins.module_name\" THIS OPTION IS DEPRECATED: The 'plugin' option is deprecated; it should be replaced by the 'plugin' directive A list of Python modules containing transformation functions to run the entries through after parsing. The parser reads the entries as they are, transforms them through a list of standard functions, such as balance checks and inserting padding entries, and then hands the entries over to those plugins to add more auto-generated goodies. The list is a list of pairs/tuples, in the format (plugin-name, plugin-configuration). The plugin-name should be the name of a Python module to import, and within the module we expect a special '__plugins__' attribute that should list the name of transform functions to run the entries through. The plugin-configuration argument is an optional string to be provided by the user. Each function accepts a pair of (entries, options_map) and should return a pair of (new entries, error instances). If a plugin configuration is provided, it is provided as an extra argument to the plugin function. Errors should not be printed out the output, they will be converted to strings by the loader and displayed as dictated by the output medium. (This option may be supplied multiple times.) option \"long_string_maxlines\" \"64\" The number of lines beyond which a multi-line string will trigger a overly long line warning. This warning is meant to help detect a dangling quote by warning users of unexpectedly long strings. option \"experiment_explicit_tolerances\" \"True\" Enable an EXPERIMENTAL feature that supports an explicit tolerance value on Balance assertions. If enabled, the balance amount supports a tolerance in the input, with this syntax: ~ , for example, \"532.23 ~ 0.001 USD\". See the document on tolerances for more details: http://furius.ca/beancount/doc/tolerances WARNING: This feature may go away at any time. It is an exploration to see if it is truly useful. We may be able to do without. option \"booking_method\" \"SIMPLE\" The booking method to apply, for interpolation and for matching lot specifications to the available lots in an inventory at the moment of the transaction. Values may be 'SIMPLE' for the original method used in Beancount, or 'FULL' for the newer method that does fuzzy matching against the inventory and allows multiple amounts to be interpolated (see http://furius.ca/beancount/doc/proposal-booking for details).","title":"Beancount Options Reference"},{"location":"beancount_query_language.html","text":"Beancount Query Language \uf0c1 Martin Blais, January 2015 http://furius.ca/beancount/doc/query Introduction \uf0c1 The purpose of Beancount is to allow the user to create an accurate and error-free representation of financial transactions, typically those occurring in a user or in an institution\u2019s associated set of accounts, to then extract various reports from this list of transactions. Beancount provides a few tools to extract reports from the corpus of transactions: custom reports (using the Beancount bean-report tool), a web interface (using the bean-web tool) and the ability for the user to write their own scripts to output anything they want. The repository of financial transactions is always read from the text file input, but once parsed and loaded in memory, extracting information from Beancount could be carried out much like you would another database, that is, instead of using custom code to generate output from the data structures, a query language could be compiled and run over the relatively regular list of transactions. In practice, you could flatten out the list of postings to an external SQL database and make queries using that database\u2019s own tools, but the results of this approach are quite disappointing, mainly due to the lack of operations on inventories which is the basis of balancing rules in Beancount. By providing a slightly specialized query engine that takes advantage of the structure of the double-entry transactions we can easily generate custom reports specific to accounting purposes. This document describes our specialized SQL-like query client. It assumes you have at least a passing knowledge of SQL syntax. If not, you may want to first read something about it. Motivation \uf0c1 So one might ask: Why create another SQL client? Why not output the data to an SQLite database and allow the user to use that SQL client? Well, we have done that (see the bean-sql script which converts your Beancount ledger into an SQLite database) and the results are not great. Writing queries is painful and carrying out operations on lots that are held at cost is difficult. By taking advantage of a few aspects of our in-memory data structures, we can do better. So Beancount comes with its own SQL-like query client called \u201c bean-query \u201d. The clients implements the following \u201cextras\u201d that are essential to Beancount: It allows to easily filter at two levels simultaneously: You can filter whole transactions, which has the benefit of respecting the accounting equation, and then, usually for presentation purposes, you can also filter at the postings level. The client supports the semantics of inventory booking implemented in Beancount. It also supports aggregation functions on inventory objects and rendering functions (e.g., COST() to render the cost of an inventory instead of its contents). The client allows you to flatten multiple lots into separate postings to produce lists of holdings each with their associated cost basis. Transactions can be summarized in a manner useful to produce balance sheets and income statements. For example, our SQL variant explicitly supports a \u201cclose\u201d operation with an effect similar to closing the year, which inserts transactions to clear income statement accounts to equity and removes past history. See this post as well for a similar answer. Warning & Caveat \uf0c1 Approximately 70% of the features desired in the original design doc were implemented in the query language in late 2014. More work will be needed to cover the full feature set, but the current iteration supports most of the use cases covered by Ledger, and I suspect by Beancount users. More feedback is desired on the current version before moving on, and I would like to move forward improving some of the more fundamental aspects of Beancount (namely, inventory booking) before spending more time on the query language. It is functional as it is, but a second revision will be made later on, informed by user feedback and prolonged use. Therefore, a first release of the query language has been merged in the default stable branch. This document presents this first iteration on the Beancount query language. Making Queries \uf0c1 The custom query client that we provide is called bean-query . Run it on your ledger file, like this: $ bean-query myfile.beancount Input file: \"My Ledger\u2019s Title\" Ready with 13996 directives (21112 postings in 8833 transactions). beancount> _ This launches the query tool in interactive mode, where you can enter multiple commands on the dataset loaded in memory. bean-query parses the input file, spits out a few basic statistics about your ledger, and provides a command prompt for you to enter query commands. You can type \u201c help \u201d here to view the list of available commands. If any errors in your ledger are incurred, they are printed before the prompt. To suppress error printing, run the tool with the \u201cno-errors\u201d option: $ bean-query -q myfile.beancount Batch Mode Queries \uf0c1 If you\u2019d like to run queries directly from the command-line, without an interactive prompt, you can provide the query directly following your filename: $ bean-query myfile.beancount 'balances from year = 2014' account balance ---------------------------------------------------------------------- \u2026 \u2026 All the interactive commands are supported. \uf0c1 Shell Variables \uf0c1 The interactive shell has a few \u201c set \u201d variables that you can customize to change some of the behavior of the shell. These are like environment variables. Type the \u201c set \u201d command to see the list of available variables and their current value. The variables are: format (string): The output format. Currently, only \u201ctext\u201d is supported. boxed (boolean): Whether we should draw a box around the output table. spaced (boolean): Whether to insert an empty line between every result row. This is only relevant because postings with multiple lots may require multiple lines to be rendered, and inserting an empty line helps delineate those as separate. pager (string): The name of the pager program to pipe multi-page output to when the output is larger than the screen. The initial value is copied from the PAGER environment variable. expand (boolean): If true, expand columns that render to lists on multiple rows. Transactions and Postings \uf0c1 The structure of transactions and entries can be explained by the following simplified diagram: \uf0c1 The contents of a ledger is parsed into a list of directives, most of which are \u201cTransaction\u201d objects which contain two or more \u201cPosting\u201d objects. Postings are always linked only to a single transaction (they are never shared between transactions). Each posting refers to its parent transaction but has a unique account name, amount and associated lot (possibly with a cost), a price and some other attributes. The parent transaction itself contains a few useful attributes as well, such as a date, the name of a payee, a narration string, a flag, links, tags, etc. If we ignore the list of directives other than transactions, you can view the dataset as a single table of all postings joined with their parent transaction. It is mainly on this joined table of postings that we want to perform filtering and aggregation operations. However, because of the double-entry bookkeeping constraint, that is, the sum of amounts on postings attached to a transaction is zero, it is also quite useful to perform filtering operations at the transaction level. Because any isolated transaction has a total impact of zero on the global balance, any subset of transactions will also respect the accounting equation (Assets + Liabilities + Equity + Income + Expenses = 0), and producing balance sheets and income statements on subset of transactions provides meaningful views, for example, \u201call asset changes and expenses incurred during a trip to the Bahamas\u201d which could be selected by a tag. For this reason, we modify the SQL SELECT syntax to provide a two-level filtering syntax: since we have a single table of data, we replace the table name in FROM by a filtering expression which applies over transactions, and the WHERE clause applies to data pulled from the resulting list of postings: SELECT , , \u2026 FROM WHERE ; Both filtering expressions are optional. If no filtering expressions are provided, all postings will be enumerated over. Note that since the transactions are always filtered in date order, the results will be processed and returned in this order by default. Posting Data Columns \uf0c1 The list of targets refers to attributes of postings or of their parent transaction. The same list of \u201ccolumns\u201d is made available in the , to filter by posting attributes. For example, you could write the following query: SELECT date, narration, account, position WHERE account ~ \u201c.*:Vacation\u201d AND year >= 2014; Here, the \u201cdate\u201d, \u201cyear\u201d and \u201cnarration\u201d columns refer to attributes of the parent transaction, the \u201caccount\u201d and \u201cposition\u201d columns refer to attributes of the posting itself. You may name targets explicitly with the familiar AS operator: SELECT last(date) as last_date, cost(sum(position)) as cost; The full list of posting columns and functions available on them is best viewed by querying your actual client using \u201c help targets \u201c or \u201c help where \u201d, which prints out the list and data type of each available data column. You may also refer to the following diagram of the structure of a Posting object for the correspondence between the columns and the data structure attributes. Entry Data Columns \uf0c1 A different list of column names is available on the of the FROM clause. These columns refer to attributes of the Transaction objects. This clause is intended to filter whole transactions (i.e., all their postings or none at all). Available attributes include the date, transaction flag, the optional payee, narration, set of tags and links. Use the \u201c help from \u201d command to find the complete list of columns and functions available in this clause. A Beancount input file consists of many different types of entries, not just transactions. Some of these other types of entries (such as Open, Close, Balance, etc.) may also provide attributes that can be accessed from the FROM clause. This is embryonic at this point. (It\u2019s unclear yet how these will be used in the future, but I suspect we will find some interesting applications for them eventually. The FROM clause provides access to the type of the data entry via column \u201c type \u201d. It\u2019s still an exploration how much we can make pragmatic use of the SQL language for other types of directives.) The \u201cid\u201d Column A special column exists that identifies each transaction uniquely: \u201c id \u201d. It is a unique hash automatically computed from the transaction and should be stable between runs. SELECT DISTINCT id; This hash is derived from the contents of the transaction object itself (if you change something about the transaction, e.g. you edit the narration, the id will change). You can print and select using this column. It can be used for debugging, e.g. PRINT FROM id = '8e7c47250d040ae2b85de580dd4f5c2a'; The \u201cbalance\u201d Column One common desired output is a journal of entries over time (also called a \u201cregister\u201d in Ledger): SELECT date, account, position WHERE account ~ \"Chase:Slate\"; For this type of report, it is convenient to also render a column of the cumulative balance of the selected postings rows. Access to the previous row is not a standard SQL feature, so we get a little creative and provide a special column called \u201c balance \u201d which is automatically calculated based on the previous selected rows: SELECT date, account, position, balance WHERE account ~ \"Chase:Slate\"; This provides the ability to render typical account statements such as those mailed to you by a bank. Output might look like this: $ bean-query $T \"select date, account, position, balance where account ~ 'Expenses:Food:Restaurant';\" date account position balance ---------- ------------------------ --------- ---------- 2012-01-02 Expenses:Food:Restaurant 31.02 USD 31.02 USD 2012-01-04 Expenses:Food:Restaurant 25.33 USD 56.35 USD 2012-01-08 Expenses:Food:Restaurant 67.88 USD 124.23 USD 2012-01-09 Expenses:Food:Restaurant 35.28 USD 159.51 USD 2012-01-14 Expenses:Food:Restaurant 25.84 USD 185.35 USD 2012-01-17 Expenses:Food:Restaurant 36.73 USD 222.08 USD 2012-01-21 Expenses:Food:Restaurant 28.11 USD 250.19 USD 2012-01-22 Expenses:Food:Restaurant 21.12 USD 271.31 USD Wildcard Targets \uf0c1 Using a wildcard as the target list (\u201c*\u201d) select a good default list of columns: SELECT * FROM year = 2014; To view the actual list of columns selected, you can use the EXPLAIN prefix: EXPLAIN SELECT * FROM year = 2014; Data Types \uf0c1 The data attributes extracted from the postings or transactions have particular types. Most of the data types are regular types as provided by the underlying Python implementation language, types such as String (Python str) Date (a datetime.date instance). You can parse a date with the #\"...\" syntax; this uses Python\u2019s dateutil module and is pretty liberal in the formats it accepts. Integer (Python int) Boolean (Python bool object), as TRUE , FALSE Number (a decimal.Decimal object) Set of Strings (a Python set of str objects) Null objects ( NULL ) Positions and Inventories \uf0c1 However, one reason that our SQL-like client exists in the first place is for its ability to carry out aggregation operations on inventories of positions, the data structures at the core of Beancount, that implements its balancing semantics. Internally, Beancount defines Position and Inventory objects and is able to aggregate them together in an instance of Inventory. On each Posting, the \u201cposition\u201d column extracts an object of type Position, which when summed over produces an instance of Inventory. The shell is able to display those appropriately. More specifically, Inventory objects can contain multiple different lots of holdings, and each of these will get rendered on a separate line. Quantities of Positions and Inventories \uf0c1 Objects of type Position are rendered in their full detail by default, including not just their number and currency, but the details of their lot. Inventories consist of a list of lots, and as such are rendered similarly, as a list of positions (one per line) each with their full detail by default. This is generally too much detail. The shell provides functions that allow the user to summarize the positions into one of the various derived quantities. The types of derived quantities are: \u201c raw \u201d: render the position in its full detail, including cost and lot date \u201c units \u201d: render just the number and currency of the position \u201c cost \u201d: render the total cost of the position, that is the number of units x the per-unit cost \u201c weight \u201d: render the amount that is used to balance the postings of a transaction. The main distinction between cost and weight is for postings with a price conversion. \u201c value \u201d: render the amount at the market value of the last entry rendered. Functions with the same names are available to operate on position or inventory objects. For example, one could generate a table of final balances for each account like this: SELECT account, units(sum(position)), cost(sum(position)) GROUP BY 1; Refer to the table below for explicit examples of each type of posting and how it would get converted and rendered. posting raw (full detail) units cost weight market Simple 50.00 USD 50.00 USD 50.00 USD 50.00 USD 50.00 USD With Price Conversion 50.00 USD @ 1.35 CAD 50.00 USD 50.00 USD 67.50 CAD 50.00 USD Held at Cost 50 VEA {1.35 CAD} 50 VEA 67.50 CAD 67.50 CAD 67.50 CAD Held at Cost with Price 50 VEA {1.35 CAD} @ 1.45 CAD 50 VEA 67.50 CAD 67.50 CAD 72.50 CAD Operators \uf0c1 Common comparison and logical operators are provided to operate on the available data columns: = (equality), != (inequality) < (less than), <= (less than or equal) (greater than), >= (greater than or equal) AND (logical conjunction) OR (logical disjunction) NOT (logical negation) IN (set membership) We also provide a regular expression search operator into a string object: ~ (search regexp) At the moment, matching groups are ignored. You can use string, number and integer constants with those operators, and parentheses to explicitly state precedence. You can use the #\u201d...\u201d literal syntax to input dates (valid contents for the string are pretty liberal, it supports anything Python\u2019s dateutil.parser supports). Here is an example query that uses a few of these: SELECT date, payee WHERE account ~ 'Expenses:Food:Restaurant' AND 'trip-new-york' IN tags AND NOT payee = 'Uncle Boons' Unlike SQL, bean-query does not implement three-valued logic for NULL . This means that e.g. the expression NULL = NULL yields TRUE instead of NULL , which simplifies things, but may come as a surprise to veteran SQL users. Simple Functions \uf0c1 The shell provides a list of simple function that operate on a single data column and return a new value. These functions operate on particular types. The shell implements rudimentary type verification and should be able to warn you on incompatible types. Some example functions follow: COST(Inventory), COST(Position): Return an Amount, the cost of the position or inventory. UNITS(Inventory), UNITS(Position): Return the units of the position or inventory. DAY(date), MONTH(date), YEAR(date): Return an integer, the day, month or year of the posting or entry\u2019s date. LENGTH(list): Computes the length of a list or a set, e.g. on tags. PARENT(account-string): Returns the name of the parent account. These are just examples; for the complete list, see \u201c help targets \u201d, \u201c help where \u201d, \u201c help from \u201d. Note that it is exceedingly easy to add new functions to this list. As of December 2014, we are just beginning using the shell widely and we expect to be adding new functions as needed. If you need a function, please add a comment here or log a ticket and we will consider adding it to the list (we understand that the current list is limited). I intend to be liberal about adding new functions; as long as they have generic application, I don\u2019t think it should be a problem. Otherwise, I may be able to provide a mechanism for user to register new functions as part of Python plugins that could live outside the Beancount codebase. Aggregate Functions \uf0c1 Some functions operate on more than a single row. These functions aggregate and summarize the multiple values for the data column that they operate on. A prototypical usage of such a function is to sum the positions in an inventory: SELECT account, sum(position) WHERE account ~ 'Income' GROUP BY account; If a query target has at least one aggregating function, the query becomes an aggregated query (see relevant section for details). Note that you cannot use aggregation functions in the FROM or WHERE clauses. Examples of aggregate functions include: COUNT(...): Computes the number of postings selected (an integer). FIRST(...), LAST(...): Returns first or last value seen. MIN(...), MAX(...): Computes the minimum or maximum value seen. SUM(...): Sums up the values of each set. This works on amounts, positions, inventories, numbers, etc. As for simple functions, this is just a starting list. We will be adding more as needed. Use \u201c help targets \u201d to access the full list of available aggregate functions. Note: You cannot filter (using a WHERE clause) the results of aggregation functions; this requires the implementation offering a HAVING clause, and at the moment, HAVING filtering is not yet implemented. Simple vs. Aggregated Queries \uf0c1 There are two types of queries: Simple queries , which produce a row of results for each posting that matches the restricts in the WHERE clause. Aggregate queries , which produce a row of results for each group of postings that match the restricts in the WHERE clause. A query is \u201caggregate\u201d if it has at least one aggregate function in its list of targets. In order to identify the aggregation keys, all the non-aggregate columns have to be flagged using the GROUP BY clause, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY payee, account; You may also use the positional order of the targets to declare the group key, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY 1, 2; Furthermore, if you name your targets, you can use the explicit target names: SELECT payee, account as acc, COST(SUM(position)), LAST(date) GROUP BY 1, acc; This should all feel familiar if you have preliminary knowledge of SQL. Finally, because we implement a limited version of SQL, and that the simple columns must always be specified, omitting the GROUP BY clause should also eventually work and we should group by those columns implicitly, as a convenience. Distinct \uf0c1 There is a post-filtering phase that supports uniquifying result rows. You can trigger this unique filter with the DISTINCT flag after SELECT , as is common in SQL, e.g. SELECT DISTINCT account; Controlling Results \uf0c1 Order By \uf0c1 Analogous to the GROUP BY clause is an ORDER BY clause that controls the final ordering of the result rows: SELECT \u2026 GROUP BY account, payee ORDER BY payee, date; The clause is optional. If you do not specify it, the default order of iteration of selected postings is used to output the results (that is, the order of transactions-sorted by date- and then their postings). As in SQL, you may reverse the order of sorting by a DESC suffix (the default is the same as specifying ASC ): SELECT \u2026 GROUP BY account, payee ORDER BY payee, date DESC; Limit \uf0c1 Our query language also supports a LIMIT clause to interrupt output row generation: SELECT \u2026 LIMIT 100; This would output the first 100 result rows and then stop. While this is a common clause present in the SQL language, in the context of double-entry bookkeeping it is not very useful: we always have relatively small datasets to work from. Nevertheless, we provide it for completeness. Format \uf0c1 For SELECT , JOURNAL and BALANCES queries, the output format is a table of text by default. We support CSV output. ( We could easily add support for XLS or Google Sheets output.) However, for PRINT queries, the output format is Beancount input text format. Statement Operators \uf0c1 The shell provides a few operators designed to facilitate the generation of balance sheets and income statements. The particular methodology used to define these operations should be described in detail in the \u201c introduction to double-entry bookkeeping \u201d document that accompanies Beancount and is mostly located in the source code in the summarize module. These special operators are provided on the FROM clause that is made available on the various forms of query commands in the shell. These further transform the set of entries selected by the FROM expression at the transaction levels (not postings). Please note that these are not from standard SQL; these are extensions provided by this shell language only. Opening a Period \uf0c1 Opening an exercise period at a particular date replaces all entries before that date by summarization entries that book the expected balance against an Equity \u201copening balances\u201d account and implicitly clears the income and expenses to zero by transferring their balances to an Equity \u201cprevious earnings\u201d account (see beancount.ops.summarize.open() for implementation details). It is invoked like this: SELECT \u2026 FROM OPEN ON \u2026 For example: SELECT * FROM has_account(\"Invest\") OPEN ON 2014-01-01; If you want, you can view just the inserted summarization entries like this: PRINT FROM flag = \"S\" AND account ~ \"Invest\" OPEN ON 2014-01-01; Closing a Period \uf0c1 Closing an exercise period involves mainly truncating all entries that come after the given date and ensuring that currency conversions are correctly corrected for (see beancount.ops.summarize.close() for implementation details). It is invoked like this: SELECT \u2026 FROM CLOSE [ON ] \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01; Note that the closing date should be one day after the last transaction you would like to include (this is in line with the convention we use everywhere in Beancount whereby starting dates are inclusive and ending dates exclusive). The closing date is optional. If the date is not specified, the date one day beyond the date of the last entry is used. Closing a period leaves the Income and Expenses accounts as they are, that is, their balances are not cleared to zero to Equity. This is because closing is also used to produce final balances for income statements. \u201cClearing\u201d, as described in the next section, is only needed for balance sheets. Clearing Income & Expenses \uf0c1 In order to produce a balance sheet, we need to transfer final balances of the Income and Expenses to an Equity \u201ccurrent earnings\u201d account (sometimes called \u201cretained earnings\u201d or \u201cnet income\u201d; you can select the specific account name to use using options in the input file). The resulting balances of income statement accounts should be zero (see beancount.ops.summarize.clear() for implementation details). You can clear like this: SELECT \u2026 FROM CLEAR \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01 CLEAR; This is a statement suitable to produce a list of accounts to build a balance sheet. The \u201c Equity:Earnings:Current \u201d (by default) will contain the net income accumulated during the preceding period. No balances for the Income nor Expenses accounts should appear in the output. Example Statements \uf0c1 The statement operators of course may be combined. For instance, if you wanted to output data for an income statement for year 2013, you could issue the following statement: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 WHERE account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; This would produce a list of balances for Income and Expenses accounts. To generate a balance sheet, you would add the CLEAR option and select the other accounts: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 CLEAR WHERE not account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; Note that if you added the CLEAR operator to the statement of income statement accounts, all the balances would show at zero because of the inserted transactions that move those balances to the Equity net income account at the end of the period. It is relevant to notice that the examples above do not filter the transactions any further. If you are selecting a subset of transactions you may want to leave the accounts unopened, unclosed and uncleared because applying only some of the transactions on top of the opening balances of Assets and Liabilities accounts will not produce correct balances for those accounts. It would be more useful to leave them all opened, and to interpret the balances of the balance sheet accounts as the changes in those accounts for the subset of transactions selected. For example, if you selected all transactions from a trip (using a tag), you would obtain a list of changes in Expenses (and possibly Income) tagged as being included in this trip, and the Assets and Liabilities accounts would show where the funds for those Expenses came from. Consult the \u201cintroduction to double-entry method\u201d document for a pictorial representation of this. (Granted, this is probably worth a dedicated document and I might produce one at some point.) Example Fetching Cost Basis \uf0c1 \u201c... is there currently an easy way to determine what my cost basis is for an account on a given date (other than manually adding up UNITS * COST for every contribution, which is kind of a pain)? I'm trying to estimate the tax implications of potential stock sales.\u201d [Question from Matthew Harris] For a detailed report of share movements: SELECT account, currency, position, COST(position) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" Or this for the sum total of the cost bases: SELECT sum(cost(position)) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" High-Level Shortcuts \uf0c1 There are two types of queries that are very common for accounting applications: journals and balances reports. While we have explicit implementations of such reports that can be produced using the bean-report tool, we are also able to synthesize good approximations of such reports using SELECT statements. This section describes a few additional selection commands that translate directly into SELECT statements and which are then run with the same query code. These are intended as convenient shortcuts. Selecting Journals \uf0c1 A common type of query is one that generates a linear journal of entries (Ledger calls this a \u201cregister\u201d). This roughly corresponds to an account statement, but with our language, such a statement can be generated for any subset of postings. You can generate a journal with the following syntax: JOURNAL [AT ] [FROM \u2026] The regular expression account-regexp is used to select which subset of accounts to generate a journal for. The optional \u201cAT \u201d clause is used to specify an aggregation function for the amounts rendered (typically UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. Here is an example journal-generating query: JOURNAL \"Invest\" AT COST FROM HAS_ACCOUNT(\"Assets:US\"); Selecting Balances \uf0c1 The other most common type of report is a table of the balances of various accounts at a particular date. This can be viewed as a SELECT query aggregating positions grouping by account. You can generate a balances report with the following syntax: BALANCES [AT ] [FROM \u2026] The optional \u201cAT \u201d clause is used to specify an aggregation function for the balances rendered (usually UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. To generate your balances at a particular date, close your set of entries using the \u201c FROM\u2026 CLOSE ON \u201d form described above. Observe that typical balance sheets and income statements seen in an accounting context are subsets of tables of balances such as reported by this query. An income statement reports on just the transactions that appears during a period of time, and a balance sheet summarizes transactions before its reporting before and clears the income & expenses accumulated during the period to an equity account. Then some minor reformatting is carried out. Please consult the introduction document on double-entry bookkeeping for more details, and the section above that discusses the \u201copen\u201d, \u201cclose\u201d and \u201cclear\u201d operations. We will also be providing a separate text processing tool that can accept balance reports and reformat them in a two-column format similar to that you would see balance sheets and income statements. Print \uf0c1 It can be useful to generate output in Beancount format, so that subsets of transactions can be saved to files, for example. The shell provides that ability via the PRINT command: PRINT [FROM \u2026] The FROM clause obeys the usual semantics as described elsewhere in this document. The resulting filtered stream of Beancount entries is then printed out on the output in Beancount syntax. In particular, just running the \u201c PRINT \u201d command will spit out the parsed and loaded contents of a Beancount file. You can use this for troubleshooting if needed, or to expand transactions generated from a plugin you may be in the process of developing. Debugging / Explain \uf0c1 If you\u2019re having trouble getting a particular statement to compile and run,. you can prefix any query statement with the EXPLAIN modifier, e.g.: EXPLAIN SELECT \u2026 This will not run the statement, but rather print out the intermediate AST and compiled representation as well as the list of computed statements. This can be useful to report bugs on the mailing-list. Also, this shows you the translated form of the JOURNAL and BALANCES statements. Future Features \uf0c1 The following list of features were planned for the first release but I\u2019ve decided to make a first cut without them. I\u2019ll be adding those during a revision. Flattening Inventories \uf0c1 If you provide the FLATTEN option after a query, it tells the query engine to flatten inventories with multiple lots into separate rows for each lot. For example, if you have an inventory balance with the following contents: 3 AAPL {102.34 USD} 4 AAPL {104.53 USD} 5 AAPL {106.23 USD} Using the following query: SELECT account, sum(position) GROUP BY account; It should return a single row of results, rendered over three lines. However, adding the option: SELECT account, sum(position) GROUP BY account FLATTEN; This should return three separate rows, with all the selected attributes, as if there were that many postings. Sub-Selects \uf0c1 The ability to select from the result of another SELECT is not currently supported, but the internals of the query code are prepared to do so. More Information \uf0c1 This document attempts to provide a good high-level summary of the features supported in our query language. However, should you find you need more information, you may take a look at the original proposal , or consult the source code under the beancount.query directory. In particular, the parser will provide insight into the specifics of the syntax, and the environments will shed some light on the supported data columns and functions. Feel free to rummage in the source code and ask questions on the mailing-list. Appendix \uf0c1 Future Features \uf0c1 This section documents ideas for features to be implemented in a future version. Pivot By \uf0c1 A special PIVOT BY clause will eventually be usable to convert the output from a one-dimensional list of results to a two-dimensional table. For example, the following query: SELECT account, YEAR(date) AS year, SUM(COST(position)) AS balance WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1,2; Might generate the following table of results: account year balance ------------------------ ---- ----------- Expenses:Food:Alcohol 2012 57.91 USD Expenses:Food:Alcohol 2013 33.45 USD Expenses:Food:Coffee 2012 42.07 USD Expenses:Food:Coffee 2013 124.69 USD Expenses:Food:Coffee 2014 38.74 USD Expenses:Food:Groceries 2012 2172.97 USD Expenses:Food:Groceries 2013 2161.90 USD Expenses:Food:Groceries 2014 2072.36 USD Expenses:Food:Restaurant 2012 4310.60 USD Expenses:Food:Restaurant 2013 5053.61 USD Expenses:Food:Restaurant 2014 4209.06 USD If you add a PIVOT clause to the query, like this: \u2026 PIVOT BY account, year; You would get a table like this: account/year 2012 2013 2014 ------------------------ ----------- ----------- ----------- Expenses:Food:Alcohol 57.91 USD 33.45 USD Expenses:Food:Coffee 42.07 USD 124.69 USD 38.74 USD Expenses:Food:Groceries 2172.97 USD 2161.90 USD 2072.36 USD Expenses:Food:Restaurant 4310.60 USD 5053.61 USD 4209.06 USD If your table has more than three columns, the non-pivoted column would get expanded horizontally, like this: SELECT account, YEAR(date), SUM(COST(position)) AS balance, LAST(date) AS updated WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1, 2; You would get a table like this: account year balance updated ------------------------ ---- ----------- ---------- Expenses:Food:Alcohol 2012 57.91 USD 2012-07-17 Expenses:Food:Alcohol 2013 33.45 USD 2013-12-13 Expenses:Food:Coffee 2012 42.07 USD 2012-07-19 Expenses:Food:Coffee 2013 124.69 USD 2013-12-16 Expenses:Food:Coffee 2014 38.74 USD 2014-09-21 Expenses:Food:Groceries 2012 2172.97 USD 2012-12-30 Expenses:Food:Groceries 2013 2161.90 USD 2013-12-31 Expenses:Food:Groceries 2014 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 2012 4310.60 USD 2012-12-30 Expenses:Food:Restaurant 2013 5053.61 USD 2013-12-29 Expenses:Food:Restaurant 2014 4209.06 USD 2014-11-28 Pivoting, this would generate this table: account/balance,updated 2012/balanc 2012/updat 2013/balanc 2013/updat 2014/balanc 2014/updat ------------------------ ----------- ---------- ----------- ---------- ----------- ---------- Expenses:Food:Alcohol 57.91 USD 2012-07-17 33.45 USD 2013-12-13 Expenses:Food:Coffee 42.07 USD 2012-07-19 124.69 USD 2013-12-16 38.74 USD 2014-09-21 Expenses:Food:Groceries 2172.97 USD 2012-12-30 2161.90 USD 2013-12-31 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 4310.60 USD 2012-12-30 5053.61 USD 2013-12-29 4209.06 USD 2014-11-28","title":"Beancount Query Language"},{"location":"beancount_query_language.html#beancount-query-language","text":"Martin Blais, January 2015 http://furius.ca/beancount/doc/query","title":"Beancount Query Language"},{"location":"beancount_query_language.html#introduction","text":"The purpose of Beancount is to allow the user to create an accurate and error-free representation of financial transactions, typically those occurring in a user or in an institution\u2019s associated set of accounts, to then extract various reports from this list of transactions. Beancount provides a few tools to extract reports from the corpus of transactions: custom reports (using the Beancount bean-report tool), a web interface (using the bean-web tool) and the ability for the user to write their own scripts to output anything they want. The repository of financial transactions is always read from the text file input, but once parsed and loaded in memory, extracting information from Beancount could be carried out much like you would another database, that is, instead of using custom code to generate output from the data structures, a query language could be compiled and run over the relatively regular list of transactions. In practice, you could flatten out the list of postings to an external SQL database and make queries using that database\u2019s own tools, but the results of this approach are quite disappointing, mainly due to the lack of operations on inventories which is the basis of balancing rules in Beancount. By providing a slightly specialized query engine that takes advantage of the structure of the double-entry transactions we can easily generate custom reports specific to accounting purposes. This document describes our specialized SQL-like query client. It assumes you have at least a passing knowledge of SQL syntax. If not, you may want to first read something about it.","title":"Introduction"},{"location":"beancount_query_language.html#motivation","text":"So one might ask: Why create another SQL client? Why not output the data to an SQLite database and allow the user to use that SQL client? Well, we have done that (see the bean-sql script which converts your Beancount ledger into an SQLite database) and the results are not great. Writing queries is painful and carrying out operations on lots that are held at cost is difficult. By taking advantage of a few aspects of our in-memory data structures, we can do better. So Beancount comes with its own SQL-like query client called \u201c bean-query \u201d. The clients implements the following \u201cextras\u201d that are essential to Beancount: It allows to easily filter at two levels simultaneously: You can filter whole transactions, which has the benefit of respecting the accounting equation, and then, usually for presentation purposes, you can also filter at the postings level. The client supports the semantics of inventory booking implemented in Beancount. It also supports aggregation functions on inventory objects and rendering functions (e.g., COST() to render the cost of an inventory instead of its contents). The client allows you to flatten multiple lots into separate postings to produce lists of holdings each with their associated cost basis. Transactions can be summarized in a manner useful to produce balance sheets and income statements. For example, our SQL variant explicitly supports a \u201cclose\u201d operation with an effect similar to closing the year, which inserts transactions to clear income statement accounts to equity and removes past history. See this post as well for a similar answer.","title":"Motivation"},{"location":"beancount_query_language.html#warning-caveat","text":"Approximately 70% of the features desired in the original design doc were implemented in the query language in late 2014. More work will be needed to cover the full feature set, but the current iteration supports most of the use cases covered by Ledger, and I suspect by Beancount users. More feedback is desired on the current version before moving on, and I would like to move forward improving some of the more fundamental aspects of Beancount (namely, inventory booking) before spending more time on the query language. It is functional as it is, but a second revision will be made later on, informed by user feedback and prolonged use. Therefore, a first release of the query language has been merged in the default stable branch. This document presents this first iteration on the Beancount query language.","title":"Warning & Caveat"},{"location":"beancount_query_language.html#making-queries","text":"The custom query client that we provide is called bean-query . Run it on your ledger file, like this: $ bean-query myfile.beancount Input file: \"My Ledger\u2019s Title\" Ready with 13996 directives (21112 postings in 8833 transactions). beancount> _ This launches the query tool in interactive mode, where you can enter multiple commands on the dataset loaded in memory. bean-query parses the input file, spits out a few basic statistics about your ledger, and provides a command prompt for you to enter query commands. You can type \u201c help \u201d here to view the list of available commands. If any errors in your ledger are incurred, they are printed before the prompt. To suppress error printing, run the tool with the \u201cno-errors\u201d option: $ bean-query -q myfile.beancount","title":"Making Queries"},{"location":"beancount_query_language.html#batch-mode-queries","text":"If you\u2019d like to run queries directly from the command-line, without an interactive prompt, you can provide the query directly following your filename: $ bean-query myfile.beancount 'balances from year = 2014' account balance ---------------------------------------------------------------------- \u2026 \u2026","title":"Batch Mode Queries"},{"location":"beancount_query_language.html#all-the-interactive-commands-are-supported","text":"","title":"All the interactive commands are supported."},{"location":"beancount_query_language.html#shell-variables","text":"The interactive shell has a few \u201c set \u201d variables that you can customize to change some of the behavior of the shell. These are like environment variables. Type the \u201c set \u201d command to see the list of available variables and their current value. The variables are: format (string): The output format. Currently, only \u201ctext\u201d is supported. boxed (boolean): Whether we should draw a box around the output table. spaced (boolean): Whether to insert an empty line between every result row. This is only relevant because postings with multiple lots may require multiple lines to be rendered, and inserting an empty line helps delineate those as separate. pager (string): The name of the pager program to pipe multi-page output to when the output is larger than the screen. The initial value is copied from the PAGER environment variable. expand (boolean): If true, expand columns that render to lists on multiple rows.","title":"Shell Variables"},{"location":"beancount_query_language.html#transactions-and-postings","text":"The structure of transactions and entries can be explained by the following simplified diagram:","title":"Transactions and Postings"},{"location":"beancount_query_language.html#_1","text":"The contents of a ledger is parsed into a list of directives, most of which are \u201cTransaction\u201d objects which contain two or more \u201cPosting\u201d objects. Postings are always linked only to a single transaction (they are never shared between transactions). Each posting refers to its parent transaction but has a unique account name, amount and associated lot (possibly with a cost), a price and some other attributes. The parent transaction itself contains a few useful attributes as well, such as a date, the name of a payee, a narration string, a flag, links, tags, etc. If we ignore the list of directives other than transactions, you can view the dataset as a single table of all postings joined with their parent transaction. It is mainly on this joined table of postings that we want to perform filtering and aggregation operations. However, because of the double-entry bookkeeping constraint, that is, the sum of amounts on postings attached to a transaction is zero, it is also quite useful to perform filtering operations at the transaction level. Because any isolated transaction has a total impact of zero on the global balance, any subset of transactions will also respect the accounting equation (Assets + Liabilities + Equity + Income + Expenses = 0), and producing balance sheets and income statements on subset of transactions provides meaningful views, for example, \u201call asset changes and expenses incurred during a trip to the Bahamas\u201d which could be selected by a tag. For this reason, we modify the SQL SELECT syntax to provide a two-level filtering syntax: since we have a single table of data, we replace the table name in FROM by a filtering expression which applies over transactions, and the WHERE clause applies to data pulled from the resulting list of postings: SELECT , , \u2026 FROM WHERE ; Both filtering expressions are optional. If no filtering expressions are provided, all postings will be enumerated over. Note that since the transactions are always filtered in date order, the results will be processed and returned in this order by default.","title":""},{"location":"beancount_query_language.html#posting-data-columns","text":"The list of targets refers to attributes of postings or of their parent transaction. The same list of \u201ccolumns\u201d is made available in the , to filter by posting attributes. For example, you could write the following query: SELECT date, narration, account, position WHERE account ~ \u201c.*:Vacation\u201d AND year >= 2014; Here, the \u201cdate\u201d, \u201cyear\u201d and \u201cnarration\u201d columns refer to attributes of the parent transaction, the \u201caccount\u201d and \u201cposition\u201d columns refer to attributes of the posting itself. You may name targets explicitly with the familiar AS operator: SELECT last(date) as last_date, cost(sum(position)) as cost; The full list of posting columns and functions available on them is best viewed by querying your actual client using \u201c help targets \u201c or \u201c help where \u201d, which prints out the list and data type of each available data column. You may also refer to the following diagram of the structure of a Posting object for the correspondence between the columns and the data structure attributes.","title":"Posting Data Columns"},{"location":"beancount_query_language.html#entry-data-columns","text":"A different list of column names is available on the of the FROM clause. These columns refer to attributes of the Transaction objects. This clause is intended to filter whole transactions (i.e., all their postings or none at all). Available attributes include the date, transaction flag, the optional payee, narration, set of tags and links. Use the \u201c help from \u201d command to find the complete list of columns and functions available in this clause. A Beancount input file consists of many different types of entries, not just transactions. Some of these other types of entries (such as Open, Close, Balance, etc.) may also provide attributes that can be accessed from the FROM clause. This is embryonic at this point. (It\u2019s unclear yet how these will be used in the future, but I suspect we will find some interesting applications for them eventually. The FROM clause provides access to the type of the data entry via column \u201c type \u201d. It\u2019s still an exploration how much we can make pragmatic use of the SQL language for other types of directives.) The \u201cid\u201d Column A special column exists that identifies each transaction uniquely: \u201c id \u201d. It is a unique hash automatically computed from the transaction and should be stable between runs. SELECT DISTINCT id; This hash is derived from the contents of the transaction object itself (if you change something about the transaction, e.g. you edit the narration, the id will change). You can print and select using this column. It can be used for debugging, e.g. PRINT FROM id = '8e7c47250d040ae2b85de580dd4f5c2a'; The \u201cbalance\u201d Column One common desired output is a journal of entries over time (also called a \u201cregister\u201d in Ledger): SELECT date, account, position WHERE account ~ \"Chase:Slate\"; For this type of report, it is convenient to also render a column of the cumulative balance of the selected postings rows. Access to the previous row is not a standard SQL feature, so we get a little creative and provide a special column called \u201c balance \u201d which is automatically calculated based on the previous selected rows: SELECT date, account, position, balance WHERE account ~ \"Chase:Slate\"; This provides the ability to render typical account statements such as those mailed to you by a bank. Output might look like this: $ bean-query $T \"select date, account, position, balance where account ~ 'Expenses:Food:Restaurant';\" date account position balance ---------- ------------------------ --------- ---------- 2012-01-02 Expenses:Food:Restaurant 31.02 USD 31.02 USD 2012-01-04 Expenses:Food:Restaurant 25.33 USD 56.35 USD 2012-01-08 Expenses:Food:Restaurant 67.88 USD 124.23 USD 2012-01-09 Expenses:Food:Restaurant 35.28 USD 159.51 USD 2012-01-14 Expenses:Food:Restaurant 25.84 USD 185.35 USD 2012-01-17 Expenses:Food:Restaurant 36.73 USD 222.08 USD 2012-01-21 Expenses:Food:Restaurant 28.11 USD 250.19 USD 2012-01-22 Expenses:Food:Restaurant 21.12 USD 271.31 USD","title":"Entry Data Columns"},{"location":"beancount_query_language.html#wildcard-targets","text":"Using a wildcard as the target list (\u201c*\u201d) select a good default list of columns: SELECT * FROM year = 2014; To view the actual list of columns selected, you can use the EXPLAIN prefix: EXPLAIN SELECT * FROM year = 2014;","title":"Wildcard Targets"},{"location":"beancount_query_language.html#data-types","text":"The data attributes extracted from the postings or transactions have particular types. Most of the data types are regular types as provided by the underlying Python implementation language, types such as String (Python str) Date (a datetime.date instance). You can parse a date with the #\"...\" syntax; this uses Python\u2019s dateutil module and is pretty liberal in the formats it accepts. Integer (Python int) Boolean (Python bool object), as TRUE , FALSE Number (a decimal.Decimal object) Set of Strings (a Python set of str objects) Null objects ( NULL )","title":"Data Types"},{"location":"beancount_query_language.html#positions-and-inventories","text":"However, one reason that our SQL-like client exists in the first place is for its ability to carry out aggregation operations on inventories of positions, the data structures at the core of Beancount, that implements its balancing semantics. Internally, Beancount defines Position and Inventory objects and is able to aggregate them together in an instance of Inventory. On each Posting, the \u201cposition\u201d column extracts an object of type Position, which when summed over produces an instance of Inventory. The shell is able to display those appropriately. More specifically, Inventory objects can contain multiple different lots of holdings, and each of these will get rendered on a separate line.","title":"Positions and Inventories"},{"location":"beancount_query_language.html#quantities-of-positions-and-inventories","text":"Objects of type Position are rendered in their full detail by default, including not just their number and currency, but the details of their lot. Inventories consist of a list of lots, and as such are rendered similarly, as a list of positions (one per line) each with their full detail by default. This is generally too much detail. The shell provides functions that allow the user to summarize the positions into one of the various derived quantities. The types of derived quantities are: \u201c raw \u201d: render the position in its full detail, including cost and lot date \u201c units \u201d: render just the number and currency of the position \u201c cost \u201d: render the total cost of the position, that is the number of units x the per-unit cost \u201c weight \u201d: render the amount that is used to balance the postings of a transaction. The main distinction between cost and weight is for postings with a price conversion. \u201c value \u201d: render the amount at the market value of the last entry rendered. Functions with the same names are available to operate on position or inventory objects. For example, one could generate a table of final balances for each account like this: SELECT account, units(sum(position)), cost(sum(position)) GROUP BY 1; Refer to the table below for explicit examples of each type of posting and how it would get converted and rendered. posting raw (full detail) units cost weight market Simple 50.00 USD 50.00 USD 50.00 USD 50.00 USD 50.00 USD With Price Conversion 50.00 USD @ 1.35 CAD 50.00 USD 50.00 USD 67.50 CAD 50.00 USD Held at Cost 50 VEA {1.35 CAD} 50 VEA 67.50 CAD 67.50 CAD 67.50 CAD Held at Cost with Price 50 VEA {1.35 CAD} @ 1.45 CAD 50 VEA 67.50 CAD 67.50 CAD 72.50 CAD","title":"Quantities of Positions and Inventories"},{"location":"beancount_query_language.html#operators","text":"Common comparison and logical operators are provided to operate on the available data columns: = (equality), != (inequality) < (less than), <= (less than or equal) (greater than), >= (greater than or equal) AND (logical conjunction) OR (logical disjunction) NOT (logical negation) IN (set membership) We also provide a regular expression search operator into a string object: ~ (search regexp) At the moment, matching groups are ignored. You can use string, number and integer constants with those operators, and parentheses to explicitly state precedence. You can use the #\u201d...\u201d literal syntax to input dates (valid contents for the string are pretty liberal, it supports anything Python\u2019s dateutil.parser supports). Here is an example query that uses a few of these: SELECT date, payee WHERE account ~ 'Expenses:Food:Restaurant' AND 'trip-new-york' IN tags AND NOT payee = 'Uncle Boons' Unlike SQL, bean-query does not implement three-valued logic for NULL . This means that e.g. the expression NULL = NULL yields TRUE instead of NULL , which simplifies things, but may come as a surprise to veteran SQL users.","title":"Operators"},{"location":"beancount_query_language.html#simple-functions","text":"The shell provides a list of simple function that operate on a single data column and return a new value. These functions operate on particular types. The shell implements rudimentary type verification and should be able to warn you on incompatible types. Some example functions follow: COST(Inventory), COST(Position): Return an Amount, the cost of the position or inventory. UNITS(Inventory), UNITS(Position): Return the units of the position or inventory. DAY(date), MONTH(date), YEAR(date): Return an integer, the day, month or year of the posting or entry\u2019s date. LENGTH(list): Computes the length of a list or a set, e.g. on tags. PARENT(account-string): Returns the name of the parent account. These are just examples; for the complete list, see \u201c help targets \u201d, \u201c help where \u201d, \u201c help from \u201d. Note that it is exceedingly easy to add new functions to this list. As of December 2014, we are just beginning using the shell widely and we expect to be adding new functions as needed. If you need a function, please add a comment here or log a ticket and we will consider adding it to the list (we understand that the current list is limited). I intend to be liberal about adding new functions; as long as they have generic application, I don\u2019t think it should be a problem. Otherwise, I may be able to provide a mechanism for user to register new functions as part of Python plugins that could live outside the Beancount codebase.","title":"Simple Functions"},{"location":"beancount_query_language.html#aggregate-functions","text":"Some functions operate on more than a single row. These functions aggregate and summarize the multiple values for the data column that they operate on. A prototypical usage of such a function is to sum the positions in an inventory: SELECT account, sum(position) WHERE account ~ 'Income' GROUP BY account; If a query target has at least one aggregating function, the query becomes an aggregated query (see relevant section for details). Note that you cannot use aggregation functions in the FROM or WHERE clauses. Examples of aggregate functions include: COUNT(...): Computes the number of postings selected (an integer). FIRST(...), LAST(...): Returns first or last value seen. MIN(...), MAX(...): Computes the minimum or maximum value seen. SUM(...): Sums up the values of each set. This works on amounts, positions, inventories, numbers, etc. As for simple functions, this is just a starting list. We will be adding more as needed. Use \u201c help targets \u201d to access the full list of available aggregate functions. Note: You cannot filter (using a WHERE clause) the results of aggregation functions; this requires the implementation offering a HAVING clause, and at the moment, HAVING filtering is not yet implemented.","title":"Aggregate Functions"},{"location":"beancount_query_language.html#simple-vs-aggregated-queries","text":"There are two types of queries: Simple queries , which produce a row of results for each posting that matches the restricts in the WHERE clause. Aggregate queries , which produce a row of results for each group of postings that match the restricts in the WHERE clause. A query is \u201caggregate\u201d if it has at least one aggregate function in its list of targets. In order to identify the aggregation keys, all the non-aggregate columns have to be flagged using the GROUP BY clause, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY payee, account; You may also use the positional order of the targets to declare the group key, like this: SELECT payee, account, COST(SUM(position)), LAST(date) GROUP BY 1, 2; Furthermore, if you name your targets, you can use the explicit target names: SELECT payee, account as acc, COST(SUM(position)), LAST(date) GROUP BY 1, acc; This should all feel familiar if you have preliminary knowledge of SQL. Finally, because we implement a limited version of SQL, and that the simple columns must always be specified, omitting the GROUP BY clause should also eventually work and we should group by those columns implicitly, as a convenience.","title":"Simple vs. Aggregated Queries"},{"location":"beancount_query_language.html#distinct","text":"There is a post-filtering phase that supports uniquifying result rows. You can trigger this unique filter with the DISTINCT flag after SELECT , as is common in SQL, e.g. SELECT DISTINCT account;","title":"Distinct"},{"location":"beancount_query_language.html#controlling-results","text":"","title":"Controlling Results"},{"location":"beancount_query_language.html#order-by","text":"Analogous to the GROUP BY clause is an ORDER BY clause that controls the final ordering of the result rows: SELECT \u2026 GROUP BY account, payee ORDER BY payee, date; The clause is optional. If you do not specify it, the default order of iteration of selected postings is used to output the results (that is, the order of transactions-sorted by date- and then their postings). As in SQL, you may reverse the order of sorting by a DESC suffix (the default is the same as specifying ASC ): SELECT \u2026 GROUP BY account, payee ORDER BY payee, date DESC;","title":"Order By"},{"location":"beancount_query_language.html#limit","text":"Our query language also supports a LIMIT clause to interrupt output row generation: SELECT \u2026 LIMIT 100; This would output the first 100 result rows and then stop. While this is a common clause present in the SQL language, in the context of double-entry bookkeeping it is not very useful: we always have relatively small datasets to work from. Nevertheless, we provide it for completeness.","title":"Limit"},{"location":"beancount_query_language.html#format","text":"For SELECT , JOURNAL and BALANCES queries, the output format is a table of text by default. We support CSV output. ( We could easily add support for XLS or Google Sheets output.) However, for PRINT queries, the output format is Beancount input text format.","title":"Format"},{"location":"beancount_query_language.html#statement-operators","text":"The shell provides a few operators designed to facilitate the generation of balance sheets and income statements. The particular methodology used to define these operations should be described in detail in the \u201c introduction to double-entry bookkeeping \u201d document that accompanies Beancount and is mostly located in the source code in the summarize module. These special operators are provided on the FROM clause that is made available on the various forms of query commands in the shell. These further transform the set of entries selected by the FROM expression at the transaction levels (not postings). Please note that these are not from standard SQL; these are extensions provided by this shell language only.","title":"Statement Operators"},{"location":"beancount_query_language.html#opening-a-period","text":"Opening an exercise period at a particular date replaces all entries before that date by summarization entries that book the expected balance against an Equity \u201copening balances\u201d account and implicitly clears the income and expenses to zero by transferring their balances to an Equity \u201cprevious earnings\u201d account (see beancount.ops.summarize.open() for implementation details). It is invoked like this: SELECT \u2026 FROM OPEN ON \u2026 For example: SELECT * FROM has_account(\"Invest\") OPEN ON 2014-01-01; If you want, you can view just the inserted summarization entries like this: PRINT FROM flag = \"S\" AND account ~ \"Invest\" OPEN ON 2014-01-01;","title":"Opening a Period"},{"location":"beancount_query_language.html#closing-a-period","text":"Closing an exercise period involves mainly truncating all entries that come after the given date and ensuring that currency conversions are correctly corrected for (see beancount.ops.summarize.close() for implementation details). It is invoked like this: SELECT \u2026 FROM CLOSE [ON ] \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01; Note that the closing date should be one day after the last transaction you would like to include (this is in line with the convention we use everywhere in Beancount whereby starting dates are inclusive and ending dates exclusive). The closing date is optional. If the date is not specified, the date one day beyond the date of the last entry is used. Closing a period leaves the Income and Expenses accounts as they are, that is, their balances are not cleared to zero to Equity. This is because closing is also used to produce final balances for income statements. \u201cClearing\u201d, as described in the next section, is only needed for balance sheets.","title":"Closing a Period"},{"location":"beancount_query_language.html#clearing-income-expenses","text":"In order to produce a balance sheet, we need to transfer final balances of the Income and Expenses to an Equity \u201ccurrent earnings\u201d account (sometimes called \u201cretained earnings\u201d or \u201cnet income\u201d; you can select the specific account name to use using options in the input file). The resulting balances of income statement accounts should be zero (see beancount.ops.summarize.clear() for implementation details). You can clear like this: SELECT \u2026 FROM CLEAR \u2026 For example: SELECT * FROM has_account(\"Invest\") CLOSE ON 2015-04-01 CLEAR; This is a statement suitable to produce a list of accounts to build a balance sheet. The \u201c Equity:Earnings:Current \u201d (by default) will contain the net income accumulated during the preceding period. No balances for the Income nor Expenses accounts should appear in the output.","title":"Clearing Income & Expenses"},{"location":"beancount_query_language.html#example-statements","text":"The statement operators of course may be combined. For instance, if you wanted to output data for an income statement for year 2013, you could issue the following statement: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 WHERE account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; This would produce a list of balances for Income and Expenses accounts. To generate a balance sheet, you would add the CLEAR option and select the other accounts: SELECT account, sum(position) FROM OPEN ON 2013-01-01 CLOSE ON 2014-01-01 CLEAR WHERE not account ~ \"Income|Expenses\" GROUP BY 1 ORDER BY 1; Note that if you added the CLEAR operator to the statement of income statement accounts, all the balances would show at zero because of the inserted transactions that move those balances to the Equity net income account at the end of the period. It is relevant to notice that the examples above do not filter the transactions any further. If you are selecting a subset of transactions you may want to leave the accounts unopened, unclosed and uncleared because applying only some of the transactions on top of the opening balances of Assets and Liabilities accounts will not produce correct balances for those accounts. It would be more useful to leave them all opened, and to interpret the balances of the balance sheet accounts as the changes in those accounts for the subset of transactions selected. For example, if you selected all transactions from a trip (using a tag), you would obtain a list of changes in Expenses (and possibly Income) tagged as being included in this trip, and the Assets and Liabilities accounts would show where the funds for those Expenses came from. Consult the \u201cintroduction to double-entry method\u201d document for a pictorial representation of this. (Granted, this is probably worth a dedicated document and I might produce one at some point.)","title":"Example Statements"},{"location":"beancount_query_language.html#example-fetching-cost-basis","text":"\u201c... is there currently an easy way to determine what my cost basis is for an account on a given date (other than manually adding up UNITS * COST for every contribution, which is kind of a pain)? I'm trying to estimate the tax implications of potential stock sales.\u201d [Question from Matthew Harris] For a detailed report of share movements: SELECT account, currency, position, COST(position) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\" Or this for the sum total of the cost bases: SELECT sum(cost(position)) WHERE year <= 2015 AND account ~ \"Assets:US:Schwab\" AND currency != \"USD\"","title":"Example Fetching Cost Basis"},{"location":"beancount_query_language.html#high-level-shortcuts","text":"There are two types of queries that are very common for accounting applications: journals and balances reports. While we have explicit implementations of such reports that can be produced using the bean-report tool, we are also able to synthesize good approximations of such reports using SELECT statements. This section describes a few additional selection commands that translate directly into SELECT statements and which are then run with the same query code. These are intended as convenient shortcuts.","title":"High-Level Shortcuts"},{"location":"beancount_query_language.html#selecting-journals","text":"A common type of query is one that generates a linear journal of entries (Ledger calls this a \u201cregister\u201d). This roughly corresponds to an account statement, but with our language, such a statement can be generated for any subset of postings. You can generate a journal with the following syntax: JOURNAL [AT ] [FROM \u2026] The regular expression account-regexp is used to select which subset of accounts to generate a journal for. The optional \u201cAT \u201d clause is used to specify an aggregation function for the amounts rendered (typically UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. Here is an example journal-generating query: JOURNAL \"Invest\" AT COST FROM HAS_ACCOUNT(\"Assets:US\");","title":"Selecting Journals"},{"location":"beancount_query_language.html#selecting-balances","text":"The other most common type of report is a table of the balances of various accounts at a particular date. This can be viewed as a SELECT query aggregating positions grouping by account. You can generate a balances report with the following syntax: BALANCES [AT ] [FROM \u2026] The optional \u201cAT \u201d clause is used to specify an aggregation function for the balances rendered (usually UNITS or COST ). The FROM clause follows the same rules as for the SELECT statement and is optional. To generate your balances at a particular date, close your set of entries using the \u201c FROM\u2026 CLOSE ON \u201d form described above. Observe that typical balance sheets and income statements seen in an accounting context are subsets of tables of balances such as reported by this query. An income statement reports on just the transactions that appears during a period of time, and a balance sheet summarizes transactions before its reporting before and clears the income & expenses accumulated during the period to an equity account. Then some minor reformatting is carried out. Please consult the introduction document on double-entry bookkeeping for more details, and the section above that discusses the \u201copen\u201d, \u201cclose\u201d and \u201cclear\u201d operations. We will also be providing a separate text processing tool that can accept balance reports and reformat them in a two-column format similar to that you would see balance sheets and income statements.","title":"Selecting Balances"},{"location":"beancount_query_language.html#print","text":"It can be useful to generate output in Beancount format, so that subsets of transactions can be saved to files, for example. The shell provides that ability via the PRINT command: PRINT [FROM \u2026] The FROM clause obeys the usual semantics as described elsewhere in this document. The resulting filtered stream of Beancount entries is then printed out on the output in Beancount syntax. In particular, just running the \u201c PRINT \u201d command will spit out the parsed and loaded contents of a Beancount file. You can use this for troubleshooting if needed, or to expand transactions generated from a plugin you may be in the process of developing.","title":"Print"},{"location":"beancount_query_language.html#debugging-explain","text":"If you\u2019re having trouble getting a particular statement to compile and run,. you can prefix any query statement with the EXPLAIN modifier, e.g.: EXPLAIN SELECT \u2026 This will not run the statement, but rather print out the intermediate AST and compiled representation as well as the list of computed statements. This can be useful to report bugs on the mailing-list. Also, this shows you the translated form of the JOURNAL and BALANCES statements.","title":"Debugging / Explain"},{"location":"beancount_query_language.html#future-features","text":"The following list of features were planned for the first release but I\u2019ve decided to make a first cut without them. I\u2019ll be adding those during a revision.","title":"Future Features"},{"location":"beancount_query_language.html#flattening-inventories","text":"If you provide the FLATTEN option after a query, it tells the query engine to flatten inventories with multiple lots into separate rows for each lot. For example, if you have an inventory balance with the following contents: 3 AAPL {102.34 USD} 4 AAPL {104.53 USD} 5 AAPL {106.23 USD} Using the following query: SELECT account, sum(position) GROUP BY account; It should return a single row of results, rendered over three lines. However, adding the option: SELECT account, sum(position) GROUP BY account FLATTEN; This should return three separate rows, with all the selected attributes, as if there were that many postings.","title":"Flattening Inventories"},{"location":"beancount_query_language.html#sub-selects","text":"The ability to select from the result of another SELECT is not currently supported, but the internals of the query code are prepared to do so.","title":"Sub-Selects"},{"location":"beancount_query_language.html#more-information","text":"This document attempts to provide a good high-level summary of the features supported in our query language. However, should you find you need more information, you may take a look at the original proposal , or consult the source code under the beancount.query directory. In particular, the parser will provide insight into the specifics of the syntax, and the environments will shed some light on the supported data columns and functions. Feel free to rummage in the source code and ask questions on the mailing-list.","title":"More Information"},{"location":"beancount_query_language.html#appendix","text":"","title":"Appendix"},{"location":"beancount_query_language.html#future-features_1","text":"This section documents ideas for features to be implemented in a future version.","title":"Future Features"},{"location":"beancount_query_language.html#pivot-by","text":"A special PIVOT BY clause will eventually be usable to convert the output from a one-dimensional list of results to a two-dimensional table. For example, the following query: SELECT account, YEAR(date) AS year, SUM(COST(position)) AS balance WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1,2; Might generate the following table of results: account year balance ------------------------ ---- ----------- Expenses:Food:Alcohol 2012 57.91 USD Expenses:Food:Alcohol 2013 33.45 USD Expenses:Food:Coffee 2012 42.07 USD Expenses:Food:Coffee 2013 124.69 USD Expenses:Food:Coffee 2014 38.74 USD Expenses:Food:Groceries 2012 2172.97 USD Expenses:Food:Groceries 2013 2161.90 USD Expenses:Food:Groceries 2014 2072.36 USD Expenses:Food:Restaurant 2012 4310.60 USD Expenses:Food:Restaurant 2013 5053.61 USD Expenses:Food:Restaurant 2014 4209.06 USD If you add a PIVOT clause to the query, like this: \u2026 PIVOT BY account, year; You would get a table like this: account/year 2012 2013 2014 ------------------------ ----------- ----------- ----------- Expenses:Food:Alcohol 57.91 USD 33.45 USD Expenses:Food:Coffee 42.07 USD 124.69 USD 38.74 USD Expenses:Food:Groceries 2172.97 USD 2161.90 USD 2072.36 USD Expenses:Food:Restaurant 4310.60 USD 5053.61 USD 4209.06 USD If your table has more than three columns, the non-pivoted column would get expanded horizontally, like this: SELECT account, YEAR(date), SUM(COST(position)) AS balance, LAST(date) AS updated WHERE account ~ 'Expenses:Food' AND currency = 'USD' AND year >= 2012 GROUP BY 1,2 ORDER BY 1, 2; You would get a table like this: account year balance updated ------------------------ ---- ----------- ---------- Expenses:Food:Alcohol 2012 57.91 USD 2012-07-17 Expenses:Food:Alcohol 2013 33.45 USD 2013-12-13 Expenses:Food:Coffee 2012 42.07 USD 2012-07-19 Expenses:Food:Coffee 2013 124.69 USD 2013-12-16 Expenses:Food:Coffee 2014 38.74 USD 2014-09-21 Expenses:Food:Groceries 2012 2172.97 USD 2012-12-30 Expenses:Food:Groceries 2013 2161.90 USD 2013-12-31 Expenses:Food:Groceries 2014 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 2012 4310.60 USD 2012-12-30 Expenses:Food:Restaurant 2013 5053.61 USD 2013-12-29 Expenses:Food:Restaurant 2014 4209.06 USD 2014-11-28 Pivoting, this would generate this table: account/balance,updated 2012/balanc 2012/updat 2013/balanc 2013/updat 2014/balanc 2014/updat ------------------------ ----------- ---------- ----------- ---------- ----------- ---------- Expenses:Food:Alcohol 57.91 USD 2012-07-17 33.45 USD 2013-12-13 Expenses:Food:Coffee 42.07 USD 2012-07-19 124.69 USD 2013-12-16 38.74 USD 2014-09-21 Expenses:Food:Groceries 2172.97 USD 2012-12-30 2161.90 USD 2013-12-31 2072.36 USD 2014-11-20 Expenses:Food:Restaurant 4310.60 USD 2012-12-30 5053.61 USD 2013-12-29 4209.06 USD 2014-11-28","title":"Pivot By"},{"location":"beancount_scripting_plugins.html","text":"Beancount Scripting & Plugins \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/scripting Introduction Load Pipeline Writing Plug-ins Writing Scripts Loading from File Loading from String Printing Errors Printing Entries & Round-Tripping Going Further Introduction \uf0c1 This document provides examples and guidelines on how to write scripts that use the contents of your ledger. It also provides information on how to write your own \u201cplugins,\u201d which are just Python functions that you can configure to transform your transactions or synthesize ones programmatically. These are the main two methods for extending Beancount\u2019s features and for writing your own custom reports. You simply use Python to do this. Load Pipeline \uf0c1 You need to know a little bit about how Beancount processes its input files. Internally, the single point of entry to load an input file is the beancount.loader.load_file() function, which accepts an input file and carries out a list of transformation steps, as in this diagram: The stages of loading are as follows: Parser. Run the input file through the parser. The output of this stage is entries: A list of tuples (defined in beancount.core.data) corresponding to each directive exactly as it appeared in the file, and sorted by date and line number. Moreover, Transaction directives that occur on the same date as other directives are always guaranteed to be sorted after them. This prepares the entries for processing. This list of entries will get transformed and refined by the various subsequent stages. options_map: A Python dict of the option values from the input file. See beancount.parser.options for details. Once created, this will never be modified thereafter. errors: A list of error objects, if any occurred. At every stage, new errors generated are collected. Process plugins. For each plugin, load the plugin module and call its functions with the list of entries and the options_map from the previous stage, replacing the current list by the ones returned by the plugin. This effectively allows the plugin to filter the entries. The list of plugins to run is composed of a set of default plugin modules that implement some of the built-in features of Beancount, followed by the list provided by the user from the \u201cplugin\u201d options in the input file. Validation. Run the resulting entries through a validation stage, to ensure that directives synthesized or modified by the plugins conform to some invariants that the codebase depends on. This mainly exists to generate errors. The list of entries generated by this pipeline are of the various types defined in beancount.core.data , and in a typical input file, most of them will be of type Transaction . Beancount\u2019s own filtering and reporting programs directly process those, and so can you too. These entries are dumb read-only objects (Python namedtuples ) and have no methods that modify their contents explicitly. All processing within Beancount is performed functional-style by processing lists of entries that are assumed immutable 1 . The list of user plugins to run is part of the load stage because that allows programs that monitor the file for changes to reload it and reapply the same list of plugins. It also allows the author of the input file to selectively enable various optional features that way. Writing Plug-ins \uf0c1 As you saw in the previous section, loading a Beancount file essentially produces a list of directives. Many syntax extensions can be carried out by transforming the list of directives into a new list in the plug-ins processing stage. Here are some examples of transformations that you might want to carry out on some of the directives: Add some postings automatically Link some transactions with a common tag Synthesize new transactions Remove or replace some sets of transactions Modify the various fields There is no limit to what you can do, as long as the entries your plugin produces fulfill certain constraints (all postings balance, all data types are as expected). A plugin is added to the input file via the option syntax , for example, like this: plugin \"accounting.wash_sales\" With this directive, the loader will attempt to import the accounting.wash_sales Python module (the code must be Python-3.3 or above), look for a special __plugins__ attribute which should be a sequence of functions to run, and then run those functions. For running the plugins, see the Executing Plugins section below. As an example, you would place code like this in a \u201c accounting/wash_sales.py \u201d file: __plugins__ = ['wash_sales'] def wash_sales(entries, options_map): errors = [] for entry in entries: print(type(entry)) return entries, errors This is a minimal example which does not modify the entries and prints them on the console. In practice, to do something useful, you would modify some of the entries in the list and output them. You then invoke the usual tools provided by Beancount on your input file. The various filters and reports will then operate on the list of entries output by your plugin. Refer to the source code in beancount.core for details and examples of how to manipulate entries. Plugin Configuration \uf0c1 Some plugins will require configuration. In order to provide a plugin some data specific to your file, you can provide a configuration string: plugin \"accounting.wash_sales\" \"days=31\" The plugin function will then receive an extra parameter, the configuration string. It is up to the plugin itself to define how it gets interpreted. Writing Scripts \uf0c1 If you need to produce some custom analysis or visualization that cannot be achieved using the built-in filtering and reporting capabilities, you can just write a script that loads the directives explicitly. This gives you control over the flow of the program and you can do anything you want. Loading from File \uf0c1 You can simply call the beancount.loader.load_file() loader function yourself. Here is an example minimal script: #!/usr/bin/env python3 from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) \u2026 At this point you can process the entries as you like, print them out, generate HTML, call out to Python libraries, etc. (I recommend that you use best programming practices and use docstrings on your script and a main function; the script above is meant to be minimal). Once again, refer to the source code in beancount.core for details and examples of how to manipulate entries. Loading from String \uf0c1 You can also parse a string directly. Use beancount.loader.load_string() : #!/usr/bin/env python3 from beancount import loader entries, errors, options = loader.load_string(\"\"\" 2014-02-02 open Assets:TestAccount USD \u2026 \"\"\") The stdlib textwrap.dedent function comes in handy if you want to indent the Beancount directives and have it automatically remove indentation. For a source of many examples, see the various tests in the Beancount source code. Printing Errors \uf0c1 By default, the loader will not print any errors upon loading; we prefer loading not to have any side-effect by default. You can provide an optional argument to print errors, which is the function to call to write error strings: #!/usr/bin/env python3 import sys from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename, log_errors=sys.stderr) \u2026 Or if you prefer to do it yourself explicitly, you can call the beancount.parser.printer.print_errors() helper function: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) printer.print_errors(errors) \u2026 Printing Entries & Round-Tripping \uf0c1 Printing namedtuple entries directly will output some readable though relatively poorly formatted output. It\u2019s best to use the beancount.parser.printer.print_entry() utility function to print out an entry in a readable way: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) for entry in entries: printer.print_entry(entry) In particular, Beancount offers the guarantee that the output of the printer should always be parseable and should result in the same data structure when read back in. (It should be considered a bug if that is not the case.) See the beancount.parser.printer module source code for more utility functions. Executing Plugins \uf0c1 All that is required for the plug-in module to be found, is that it must be present in your PYTHONPATH environment variable (you need to make sure that the relevant __init__.py files exist for import). It can live in your own code: you don\u2019t have to modify Beancount itself. There is also an option, which can be added to your beancount file: option \"insert_pythonpath\" \"True\" This will add the folder which contains the beancount file to the PYTHONPATH . The result is that you can place the plugins along the beancount file and have them execute when you use this file. Here is a brief example, using the wash_sales.py plugin we wrote above. Your beancount file would include the following lines: option \"insert_pythonpath\" \"True\" plugin \"wash_sales\" The Python file wash_sales.py would be stored in the same folder as the .beancount file. Going Further \uf0c1 To understand how to manipulate entries, you should refer to the source code, and probably learn more about the following modules: beancount.core.data beancount.core.account beancount.core.number beancount.core.amount beancount.core.position beancount.core.inventory Refer to the Design Doc for more details. Enjoy! Technically, Python does not prevent the modifications of namedtuple attributes that are themselves mutable such as lists and sets, but in practice, by convention, once an entry is created we never modify it in any way. Avoiding side-effects and using a functional style provides benefits in any language. \u21a9","title":"Beancount Scripting Plugins"},{"location":"beancount_scripting_plugins.html#beancount-scripting-plugins","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/scripting Introduction Load Pipeline Writing Plug-ins Writing Scripts Loading from File Loading from String Printing Errors Printing Entries & Round-Tripping Going Further","title":"Beancount Scripting & Plugins"},{"location":"beancount_scripting_plugins.html#introduction","text":"This document provides examples and guidelines on how to write scripts that use the contents of your ledger. It also provides information on how to write your own \u201cplugins,\u201d which are just Python functions that you can configure to transform your transactions or synthesize ones programmatically. These are the main two methods for extending Beancount\u2019s features and for writing your own custom reports. You simply use Python to do this.","title":"Introduction"},{"location":"beancount_scripting_plugins.html#load-pipeline","text":"You need to know a little bit about how Beancount processes its input files. Internally, the single point of entry to load an input file is the beancount.loader.load_file() function, which accepts an input file and carries out a list of transformation steps, as in this diagram: The stages of loading are as follows: Parser. Run the input file through the parser. The output of this stage is entries: A list of tuples (defined in beancount.core.data) corresponding to each directive exactly as it appeared in the file, and sorted by date and line number. Moreover, Transaction directives that occur on the same date as other directives are always guaranteed to be sorted after them. This prepares the entries for processing. This list of entries will get transformed and refined by the various subsequent stages. options_map: A Python dict of the option values from the input file. See beancount.parser.options for details. Once created, this will never be modified thereafter. errors: A list of error objects, if any occurred. At every stage, new errors generated are collected. Process plugins. For each plugin, load the plugin module and call its functions with the list of entries and the options_map from the previous stage, replacing the current list by the ones returned by the plugin. This effectively allows the plugin to filter the entries. The list of plugins to run is composed of a set of default plugin modules that implement some of the built-in features of Beancount, followed by the list provided by the user from the \u201cplugin\u201d options in the input file. Validation. Run the resulting entries through a validation stage, to ensure that directives synthesized or modified by the plugins conform to some invariants that the codebase depends on. This mainly exists to generate errors. The list of entries generated by this pipeline are of the various types defined in beancount.core.data , and in a typical input file, most of them will be of type Transaction . Beancount\u2019s own filtering and reporting programs directly process those, and so can you too. These entries are dumb read-only objects (Python namedtuples ) and have no methods that modify their contents explicitly. All processing within Beancount is performed functional-style by processing lists of entries that are assumed immutable 1 . The list of user plugins to run is part of the load stage because that allows programs that monitor the file for changes to reload it and reapply the same list of plugins. It also allows the author of the input file to selectively enable various optional features that way.","title":"Load Pipeline"},{"location":"beancount_scripting_plugins.html#writing-plug-ins","text":"As you saw in the previous section, loading a Beancount file essentially produces a list of directives. Many syntax extensions can be carried out by transforming the list of directives into a new list in the plug-ins processing stage. Here are some examples of transformations that you might want to carry out on some of the directives: Add some postings automatically Link some transactions with a common tag Synthesize new transactions Remove or replace some sets of transactions Modify the various fields There is no limit to what you can do, as long as the entries your plugin produces fulfill certain constraints (all postings balance, all data types are as expected). A plugin is added to the input file via the option syntax , for example, like this: plugin \"accounting.wash_sales\" With this directive, the loader will attempt to import the accounting.wash_sales Python module (the code must be Python-3.3 or above), look for a special __plugins__ attribute which should be a sequence of functions to run, and then run those functions. For running the plugins, see the Executing Plugins section below. As an example, you would place code like this in a \u201c accounting/wash_sales.py \u201d file: __plugins__ = ['wash_sales'] def wash_sales(entries, options_map): errors = [] for entry in entries: print(type(entry)) return entries, errors This is a minimal example which does not modify the entries and prints them on the console. In practice, to do something useful, you would modify some of the entries in the list and output them. You then invoke the usual tools provided by Beancount on your input file. The various filters and reports will then operate on the list of entries output by your plugin. Refer to the source code in beancount.core for details and examples of how to manipulate entries.","title":"Writing Plug-ins"},{"location":"beancount_scripting_plugins.html#plugin-configuration","text":"Some plugins will require configuration. In order to provide a plugin some data specific to your file, you can provide a configuration string: plugin \"accounting.wash_sales\" \"days=31\" The plugin function will then receive an extra parameter, the configuration string. It is up to the plugin itself to define how it gets interpreted.","title":"Plugin Configuration"},{"location":"beancount_scripting_plugins.html#writing-scripts","text":"If you need to produce some custom analysis or visualization that cannot be achieved using the built-in filtering and reporting capabilities, you can just write a script that loads the directives explicitly. This gives you control over the flow of the program and you can do anything you want.","title":"Writing Scripts"},{"location":"beancount_scripting_plugins.html#loading-from-file","text":"You can simply call the beancount.loader.load_file() loader function yourself. Here is an example minimal script: #!/usr/bin/env python3 from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) \u2026 At this point you can process the entries as you like, print them out, generate HTML, call out to Python libraries, etc. (I recommend that you use best programming practices and use docstrings on your script and a main function; the script above is meant to be minimal). Once again, refer to the source code in beancount.core for details and examples of how to manipulate entries.","title":"Loading from File"},{"location":"beancount_scripting_plugins.html#loading-from-string","text":"You can also parse a string directly. Use beancount.loader.load_string() : #!/usr/bin/env python3 from beancount import loader entries, errors, options = loader.load_string(\"\"\" 2014-02-02 open Assets:TestAccount USD \u2026 \"\"\") The stdlib textwrap.dedent function comes in handy if you want to indent the Beancount directives and have it automatically remove indentation. For a source of many examples, see the various tests in the Beancount source code.","title":"Loading from String"},{"location":"beancount_scripting_plugins.html#printing-errors","text":"By default, the loader will not print any errors upon loading; we prefer loading not to have any side-effect by default. You can provide an optional argument to print errors, which is the function to call to write error strings: #!/usr/bin/env python3 import sys from beancount import loader filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename, log_errors=sys.stderr) \u2026 Or if you prefer to do it yourself explicitly, you can call the beancount.parser.printer.print_errors() helper function: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) printer.print_errors(errors) \u2026","title":"Printing Errors"},{"location":"beancount_scripting_plugins.html#printing-entries-round-tripping","text":"Printing namedtuple entries directly will output some readable though relatively poorly formatted output. It\u2019s best to use the beancount.parser.printer.print_entry() utility function to print out an entry in a readable way: #!/usr/bin/env python3 from beancount import loader from beancount.parser import printer filename = \"/path/to/my/input.beancount\" entries, errors, options = loader.load_file(filename) for entry in entries: printer.print_entry(entry) In particular, Beancount offers the guarantee that the output of the printer should always be parseable and should result in the same data structure when read back in. (It should be considered a bug if that is not the case.) See the beancount.parser.printer module source code for more utility functions.","title":"Printing Entries & Round-Tripping"},{"location":"beancount_scripting_plugins.html#executing-plugins","text":"All that is required for the plug-in module to be found, is that it must be present in your PYTHONPATH environment variable (you need to make sure that the relevant __init__.py files exist for import). It can live in your own code: you don\u2019t have to modify Beancount itself. There is also an option, which can be added to your beancount file: option \"insert_pythonpath\" \"True\" This will add the folder which contains the beancount file to the PYTHONPATH . The result is that you can place the plugins along the beancount file and have them execute when you use this file. Here is a brief example, using the wash_sales.py plugin we wrote above. Your beancount file would include the following lines: option \"insert_pythonpath\" \"True\" plugin \"wash_sales\" The Python file wash_sales.py would be stored in the same folder as the .beancount file.","title":"Executing Plugins"},{"location":"beancount_scripting_plugins.html#going-further","text":"To understand how to manipulate entries, you should refer to the source code, and probably learn more about the following modules: beancount.core.data beancount.core.account beancount.core.number beancount.core.amount beancount.core.position beancount.core.inventory Refer to the Design Doc for more details. Enjoy! Technically, Python does not prevent the modifications of namedtuple attributes that are themselves mutable such as lists and sets, but in practice, by convention, once an entry is created we never modify it in any way. Avoiding side-effects and using a functional style provides benefits in any language. \u21a9","title":"Going Further"},{"location":"beancount_v3.html","text":"Beancount Vnext: Goals & Design \uf0c1 Martin Blais , July 2020 http://furius.ca/beancount/doc/Vnext Motivation \uf0c1 It's time to give Beancount a refresh and to put down a concrete plan for what the next iteration of it ought to be. I've had these thoughts in the back of my mind for a long while\u2014at least a year\u2014and I'm putting these in writing partly to share a vision of what the product we all use to organize our finances can become, partly to solicit feedback, and partly to organize my thoughts and to prioritize well on the stuff that matters. Current status. The current state of Beancount is that development has been static for a while now, for a number of reasons. The software is in a state that's far from perfect (and I'll be enumerating the main problems in this document) but I've been resisting making too many changes in order to provide myself and others a really stable base to work from. More importantly, while I used to be able to spend a significant amount of weekend time on its development, life changes and a focus on my career of late has made it difficult for me to justify or find the extra time (it has been 10 years after all). A multitude of ideas have aged to this TODO file but it's too detailed to grok and a bit of a dump, this document should be more useful. Why rewrites happen. When I wrote version 2 of Beancount (a full rewrite of the first version), it was because of a confluence of ideas for improving my first draft; I resisted for a while, but eventually it made so much sense to me that it became simply impossible not to write it. Many of the ideas driving the redesign at the time are still axioms in today's design: removing order dependence, normalizing the syntax to be well-defined with a BNF grammar, converting custom processing to a sequence of plugins off of a simple stream of directives, the current design of booking selection and how cost basis works, and all the directives beyond \"Transaction\". These ideas largely shape what a lot of people like about using Beancount today. Goals. Now is the time for yet another wave of evolution for Beancount, and similarly, a set of new ideas I'm going to lay down in this document form as potent a change as the v1 to v2 transition. The vision I have for Vnext will simplify Beancount, by factoring into simpler, more isolated, more reusable, better defined parts, and not merely by adding new features on top of what's there. In many ways, Vnext will be a distillation of the current system. It will also make space to finally implement some of the core features most often desired by users. And those changes will enhance some organizational aspects: allow for more contributions, and also trim down the part that I'm handling myself to less code, so I can more effectively focus on just the core features. Current Problems \uf0c1 Performance \uf0c1 My personal ledger, and I know that the ledgers of many users, are simply too large to process instantly. My current file takes 6 seconds on the souped-up NUC I use for a desktop at home\u2014but that's just too long. I'm really quite attached to the idea of processing the entire set of inputs every time, instead of forcing users to cut-up their ledgers into multiple files with \"closing\" transitions at arbitrary points in the year, but I really do want that \"instant\" feeling that you get when you run two-letter UNIX programs, that it runs in well under half a second . It makes it a lot more interactive and fun to use. C++ rewrite. One of the reasons for the slow performance right now is the fact that Beancount is implemented in Python, even at the level of the parser (C code calling back into a Python driver). An obvious solution is to rewrite the core of the software in a language closer to the metal, and that will be C++. I'm selecting C++ for its control and because the current slate of tools around it is mature and widespread enough that it should be easy for most to build without too many problems, and I can leverage C libraries that I will need. Using a functional language could have been fun but many of the libraries I want simply would not be available or it would be too difficult for mere mortals to build. Simple, portable C++. It's important to mention that the C++ code I have in mind is not in the style of template-heavy modern C++ code you'd find in something like Boost. Rather, it's a lot more like the conservative \"almost C without exceptions\" subset of C++ that Google uses , with a base on Abseil-Cpp (for example and flavor, see tips ). The reasons for this are stability and portability, and while this rewrite is for faster performance, I believe that it will not be necessary to pull template tricks to make it run fast enough; just a straightforward port to avoid the Python runtime will likely be sufficient. Above all I want to keep the new code simple and \"functional-ish\" as much as possible (no classes if I can avoid it), relying on a trusted set of stable dependencies , built hermetically using the Bazel build tool. Python API. It's also important that the Python API remains for plugins and scripts, and that the full suite of unit tests be carried over to the newer version of the code. After all, the ability to write custom scripts using all that personal finance data is one of the most attractive features of the text-based approach. Code beyond the new core implementation will remain in Python, and existing code built on top of the Python API should be very easily portable to Vnext. This can be achieved by exposing the directives with wrappers written in pybind11. Other languages. The resolved output of the Beancount core will be a stream of protocol buffer objects, so processing from other languages (e.g., Go) will have first-class support. Processing model. An additional algorithmic improvement to performance, should it be necessary, would be to define plugins processing in terms of iterator functions that cascade and interleave the processing of the directive stream without making entirely disjoint passes over the full list of directives. While the freedom to have each plugin process all the directives on their own has been instrumental in keeping the system free of synchronized application state and has allowed me to isolate behavior of the plugins from each other, there are opportunities to join together multiple transformations in a single pass. Fewer passes = faster. Intermediate Parsed Data vs. Final List of Directives \uf0c1 In Beancount v2, I was never too careful to clearly distinguish between The list of directives coming out of the parser , missing interpolation and booking, using position.CostSpec instead of position.Cost on each lot, and The resolved and booked list of directives with booking algorithms applied to select matching lots, and interpolated values filled in, as well as transformations having been applied by the various plugins. These two lists of directives are really quite distinct in purpose, though they share many common data structures, and for the most part, the first list appears mostly in the parser module. There have been cases where it was confusing, even to me, which of the lists I was manipulating. Part of the reason is due to how I'm using mostly the same Python data structures for both, that allow me to bend the rules on typing. Perhaps more importantly is that because plugins run after booking and interpolation, and are required to put out fully interpolated and booked transactions, a plugin that wants to extend transactions that would run as invalid in the input syntax is difficult. See #541 for an example. The next iteration will see both the intermediate parser production and final resolved list of directives implemented as protocol buffer messages, with strictly distinct data types. This will replace beancount.core.data . The distinction between these two streams will be made very clear, and I will try to hide the former as much as possible. The goal is to avoid plugin writers to ever even see the intermediate list. It should become a hidden detail of the implementation of the core. Furthermore, there may be two types of plugins: a plugin that runs on the uninterpolated, unbooked output of the parser, and a plugin that runs on the resolved and booked stream. This would allow more creative use of partial input that might be invalid under the limitations of interpolation and booking. Updates: We could convert the plugin system to one that runs at booking/interpolation time. We should attempt to make the booking/interpolation atomic, in that one could write a Python loop with an accumulator and invoke it independently, so that in theory, booking could be used in the importers (like I do for Ameritrade). Make Rewriting the Input First Class \uf0c1 Added in Dec 2020 after comments and #586 . A number of frequently asked questions have to do with how to process the input data itself. Usually, a new user will attempt to load the contents of the ledger, modify the data structures, and print to update their file, not realizing that the printer includes all the interpolations, booking data, and modifications from plugins, so this cannot work. However, since we're rewriting the parser and ensuring a clean separation between intermediate ASI-like data and processed and finalized directives, we can implement a special printer for the AST intermediate data, so that users could run just the parser, modify the intermediate directives, and print them back out, perhaps losing just some of the formatting and whitespace. This formatting loss can be leveraged to reimplement bean-format more naturally: the output of that printer should always be formatted neatly. This would avoid users having to write ad-hoc parsers on their input file, sed-like conversions, and so on. They could do it properly by modifying the data structure instead. What's more, in order for this to work accurately, we'd have to delay processing of the arithmetic operations post-parsing, so that we can render them back out. This offers another advantage: if we process the calculations after parsing, we can afford to provide an option to let the user specify the precision configuration to use for mpdecimal. I really like that idea, because it avoids hard-coding calculation precision and better defines the outcome of these options, potentially opening the door to a more rational way to remove extra digits that often get rendered out. Finally, if a nice library function can be made to process transactions in-place and output them back out, preserving all comments around them, this can become another way\u2014perhaps the preferential way\u2014for us to clean payees and somesuch. At the moment, the solution is to write a plugin that will clean up the data, but the input file remains a bit of a mess. Making it easy to automatically clean up the input file is an appealing alternative and potentially will add an important new dimension to the Beancount workflow. I want to make all changes necessary to make this possible and well supported (I'm imagining my ledger file all cleaned up right now and it's appealing). I think it's not very much work, it involves: Storing begin/end line information on everything. Adding AST constructs for representing arithmetic calculations. Adding comments parsing to the renderer. Implementing a new renderer that can reproduce the AST, including handling missing data. Implementing a library to make modification of a file in-place as easy as writing plugins, while preserving all non-directive data in the file as is. Contributions \uf0c1 For most of the development of Beancount, I've been pretty reluctant to accept contributions. It has been a closely held pet project of mine since it has so much impact on my personal financial arrangements and I dread unplanned breakage. The main reservations I've had over contributions are two-fold: Not enough testing. Proposed changes that did not include enough testing, or none at all, sometimes even the kind testing that would prevent basic breakage. When I'd accept some proposals and commit to writing the tests myself it could sometimes take me down the rabbit hole for hours (if not days). This wasn't practical. Cascading design impact. Some of the proposals did not take into account broader design considerations that would affect other parts of the code, which I may not have communicated or documented well. I've had to reject some ideas in the interest of keeping things coherent and \"tight.\" Part of this is my fault: putting a high bar on contributions hasn't allowed potential contributors to acquire enough context and familiarity with the codebase to make changes compatible with its design. Allowing more contributions. Starting in Vnext I'd like to restructure the project so that more people are able to get involved directly, and to closely work 1-on-1 with some contributors on particular features. Clearly Beancount benefits from direct input from more people. The recent move to GitHub only compounds the urgency for making that easier. To this effect, I'd like to implement a few strategies: Break down the code in parts. This is a natural evolution that has been seen in many other projects; I'm no Linus nor Guido, but like their respective projects, as they grew in popularity, the original authors narrowed their focus on the core part and let other people expand on adjacent but also important functionality. I'd like to focus more of my time only on core functionality that will impact things like support for settlement dates, currency accounts, split transactions, trade reporting, etc. Letting other people deal with adding or updating price sources, making improvements to the ingestion framework, and making beautiful renderings and presentations of the data would be ideal. In time, I may eventually break down these libraries to separate repositories with looser contribution guidelines and/or add ACLs for others to push directly to those repos. Acquire \"lieutenants.\" I need to leave more space for trusted and frequent contributors to chip in more liberally. For example, Martin Michlmayr now has direct edit access to most documents and has been making numerous helpful contributions and updates to the docs. Kirill Goncharov's conversion of the documentation out of Google Docs is simply beautiful. RedStreet and many others are regularly pitching in answers on the mailing-list. Stefano Zacchiroli and Martin have built a standalone conversion tool from Ledger. Daniele Nicolodi is proposing some low-level changes to the scanner and parser. And of course, Dominik Aumayr and Jakob Schnitzer continue developing the Fava project adjacent to Beancount. There are many more people, there is a slowly-but-surely growing list of familiar recurring names. The question in my mind is: Is there a way to communicate with regular faces so that we're aligned in terms of design and can coordinate our efforts in the same direction? Does the newly acquired familiarity with video-conference meetings (thanks to the coronavirus crisis) afford us a new channel for coordination that wasn't there before? The Python community has thrived in no small part due to the annual face-to-face interactions between its participants and a number of core developers being in the same place for some time. Can we achieve the same thing online, as we are a smaller community? If so, better coordination may make it easier to accept proposed changes. I wonder if I should propose a monthly \"team\" meeting. 20% projects. I should provide a list of \"20% projects\" that are well aligned with the direction of the project for others to take up if they want to, and add profuse guidance and detail of downstream side-effects from those proposed features. The idea is to make it possible for newcomers to contribute changes that are likely to fit well and be easily integrated and accepted on the codebase.: Proposals. Beancount's equivalents of Python's \" PEPs \" are essentially the Google Docs proposal documents I started from threads where many others comment and add suggestions. A central list of those should be shared to a folder, identified as such, and allow others to write similar proposals. Maybe a little more structure and discipline around those would be useful. Restructuring the Code \uf0c1 At the very coarse level, the code restructuring for Vnext looks like this: C++ core, parser, and built-in plugins. The Beancount core, parser, booking algorithm and plugins get rewritten in simple C++, outputting its parsed and booked contents as a stream of protobuf objects. Query engine. Beancount query/SQL gets forked to a separate project operating on arbitrary data schemas, with a much broader scope than Beancount. See section below. The rest. Most of the rest gets cut up into separate projects or at least, at first, in a distinct place within the repository (until the core is rewritten). Note that because the core outputs the stream of directives as proto objects, any language supported by protobufs should be able to read those. This extends the reach of Beancount. Here's a simplified diagram showing how this might look: Here is a detailed breakdown of the various parts of the codebase today and what I think will happen to them: Core. This is the part of Beancount's code which will get rewritten in C++ and output a sequence of messages to a stream of directives. I'll continue keeping a tight focus on that part with a conservative eye toward stability, but in Vnext will be adding desired new capabilities that have been lacking so far as described in the next section of this document. The core will include the following packages: beancount/core beancount/ops beancount/parser beancount/utils beancount/loader.py beancount/plugins (some, see below) beancount/utils (most) Query. The query language will be factored out into a completely separate repo with a broader application domain (and hooks for customizing for Beancount). I suspect that over time that project will acquire a much broader range of contributors, many of which will not even be Beancount users. This includes the code from these packages: beancount/query beancount/tools Prices. This is a simple library and tool that helps users fetch prices from external sources. This should definitely move to another repo and I'd welcome a new owner building a competing solution. People are sending me patches for new price sources and I have too little time to maintain them over time, as the upstream sources change or even disappear. This requires very little from Beancount itself (in theory you could just print() the directives for output, without even loading library code) but I think the Beancount core should include and functions to enumerate a list of required date/instrument pairs at a particular date from a given ledger (and I'm happy to support that). Note that the internal price database core will remain in the core, because it's needed there. The affected packages are: beancount/prices Improvements should be made to this library after it moves out of the Beancount repository: we should isolate the Beancount code to just a few modules, and turn the scope of this project to something larger than Beancount: it's three things, really: a) an up-to-date Python library of price fetchers with unit tests and maintained by the community (i.e., when sources break, we update the library) and a common API interface (needs to be improved from what's there TBH, the API should support fetching time series in a single call); b) an accompanying command-line tool (currency \"bean-price\") for fetching those prices from the command-line. This requires the specification of \"a price in a particular currency from a particular source\" as a string. I'd like to improve that spec to make the USD: prefix optional, and maybe eliminate the chain of prices in the spec, which hasn't found much use in practice and move that upstream. c) Make the interfaces to fetch ledger-related information (e.g., list of missing/required prices and lists of instruments) onto modules: beancount v2, beancount Vnext, ledger, hledger, and rendering output formats to any of these. In other words, this library should be able to fetch prices even if Beancount isn't installed. To turn this project into something that can run independent of beancount. Ingest. The importers library will probably move to another repo and eventually could even find another owner. I think the most interesting part of it has been the establishment of clear phases: the identify, extract and file tasks, and a regression testing framework which works on real input files checking against expected converted outputs, which has worked well to minimize the pain of upgrading importers when they break (as they do break regularly, is a SNAFU). In the past I've had to pull some tricks to make command-line tools provided by the project support an input configuration as Python code but also possible to integrate in a script; I will remove the generic programs and users will be required to turn their configuration itself into a script that will just provide subcommands when run; the change will be very easy for existing users: it will require only a single line-of-code at the bottom of their existing files. The Bazel build may add some minor difficulties in loading a Python extension module built from within a Bazel workspace from an otherwise machine-wide Python installation, but I'm confident we'll figure it out. I'd also be happy for someone else to eventually take ownership of this framework, as long as the basic functionality and API remains stable. The example csv and ofx importers should be removed from it and live in their own repos, perhaps: ofx. the OFX importer should be replaced by something using ofxtools (the one I built is pretty bad), and the CSV importer really needs a thorough rewrite with lots of unit testing for the large list of options it now supports (these tests are sorely missing). csv. Furthermore, I think the CSV importer could be enhanced to be smarter and more flexible, to automatically detect from the column headers and inferred data types in the files which column should convert into which field. I'm not going to do that (I don't have time). Someone with the urge to make the ultimate automatic CSV parser ought to create a separate repository for that. The affected packages are: beancount/ingest : could eventually move to another repo. beancount/ingest/importers: someone could revive a repository of importer implementations, like what LedgerHub once aimed to become, and swallow those codes. See this document for details on what's to happen with the ingestion code. Custom reports and bean-web should be removed: the underlying bottle library seems unmaintained at this point, Fava subsumes bean-web, and I never liked the custom reports code anyway (they're a pain to modify). I never use them myself anymore (other than through bean-web). I really think it's possible to replace those with filters on top enhanced SQL query results. The conversion to Ledger and HLedger from Beancount now seems largely useless, I'm not sure anyone's using those. I'll probably move these to another repo, where they would eventually rot, or if someone cares, adopt them and maintain or evolve them. beancount/web : will be deleted or moved to another repo. beancount/reports : will be deleted or moved to another repo. Note that this includes deprecating beancount/scripts/bake , which depends heavily on bean-web. I have no substitute for bean-bake, but I think I'd like to eventually build something better, a tool that would directly render a user-provided list of specific SQL queries to PDF files and collate them, something you can print. Jupyter notebook support. A replacement for the lightweight interface bean-web used to provide could be Jupyter Notebook integration of the query engine, so that users can run SQL queries from cells and have them rendered as tables, or perhaps a super light web application which only supports rendering general SQL queries to tables. Built-in Plugins. Beancount provides a list of internal plugins under beancount/plugins . It's not indicated clearly, but there have evolved two groups of plugins in there: stable plugins used by the core, and experimental plugins showcasing ideas, which are often incomplete implementations of something that was proposed from a thread on the mailing-list. The former group will be ported to C++, and the latter group should probably move to another location with much looser acceptance constraints. First, there are \"meta-plugins\" which only include groups of other plugins: Only one of those should remain, and maybe be enabled by default (making Beancount pedantic by default): auto pedantic The following plugins should remain in the core and be ported to C++: auto_accounts check_closing check_commodity close_tree commodity_attr check_average_cost coherent_cost currency_accounts implicit_prices leafonly noduplicates nounused onecommodity sellgains unique_prices The following are the experimental implementations of ideas that should move to a dedicated repo where other people can chip in other plugin implementations: book_conversions divert_expenses exclude_tag fill_account fix_payees forecast ira_contribs mark_unverified merge_meta split_expenses tag_pending unrealized Because it's a really common occurrence, the new transfer_lots plugin should be part of the built-in ones. Projects. The beancount/projects directory contains the export script and a project to produce data for a will. The will script will be moved outside the core of Beancount, I'm not sure anyone's using that. Maybe the new external plugins repo could include that script and other scripts I shared under /experimental. The export script should be grouped together with beancount/scripts/sql and other methods to send / share data outside of a ledger; these could remain in the core (I'm using the export script regularly to sync my aggregates and stock exposure to a Google Sheets doc which reflects intraday changes). Scripts. Some of the scripts are completely unrelated to Beancount, they are companions. The scrape validator. The sheets upload. The treeify tool. These should be moved elsewhere. One of the advantages of having all the code in the same repo is that it makes it possible to synchronize API changes across the entire codebase with a single commit. As such, I may keep some of the codes in the same repo until the new C++ core has stabilized, and properly separate them only when Vnext releases. Universal Lightweight Query Engine (ulque) \uf0c1 The SQL query engine for Beancount was initially a prototype but has grown to become the main way to get data out of it. I've been pretty liberal about adding functionality to it when needed and it's time to clean this up and consider a more polished solution. In Vnext, the query/SQL code gets eventually forked to a separate project (and repo) operating on arbitrary data schemas (via protobufs as a common description for various sources of data) and has support for Beancount integration. Imagine if you could automatically infer a schema from an arbitrary CSV file, and run operations on it, either as a Python library function or as a standalone tool. Furthermore, this tool will support sources and/or sinks to/from Google Sheets, XLS spreadsheets, containers of binary streams of serialized protos, tables from HTML web pages, PDF files, directories of files, and many more. This is going to be a data analysis tool with a scope closer to that of the Pandas library rather than an accounting-focused project, but also a universal converter tool, that will include the functionality of the upload-to-sheets script (which will get removed). One of the lessons from the SQL query engine in Beancount is that with just a little bit of post-processing (such as treeify ), we can do most of the operations in Beancount (journals, balance sheet & income statements) as queries with filters and aggregations. The tool will be made extensible in the ways required to add some of the idiosyncrasies required by Beancount, which are: Native support for a Decimal type . The addition of custom types for aggregators with the semantics of beancount.core.Inventory/Position/Amount . The ability to automatically generate a dynamic column rendering a line-by-line aggregation of another column (or set thereof), that is, a \"balance\" column . The ability to render a \" bottom line \" of aggregates at the end of the results table. Functions for splitting of aggregated columns , for amounts and inventories into multiple columns (e..g, \"123.00 USD\" becomes two columns: (123.00, \"USD\") to be processable in spreadsheets, and also for splitting debits and credits to their own columns. In particular, printing multiple lots accumulated in an account should be made natural from the SQL query, replacing the \"flatten\" feature by a more standard splitting off an array type. Moreover, broadening the focus with a new project definition will make a change to testing it thoroughly (the current one is still in a bit of a prototype stage and does not have nearly the amount of required tests), and also include data type validation (no more exceptions at runtime), by implementing a typed SQL translator. I'll document this elsewhere. This is a much bigger project, but I suspect with the broader scope, it will be easier to test and take on a life of its own. I'm preparing a design doc on this. API Rework \uf0c1 I write a lot of custom scripts, and there are a number of things that bother me about today's Beancount API, which I want to radically improve: Consolidate symbols under \"bn\". The internal API calls for importing the symbols from each package separately, but now that I'll have split off the ingestion and reporting code, all of the public API, or at least the majority of the commonly used objects in the core should be available from a single package, a bit like numpy: import beancount as bn \u2026 bn.Inventory(...) bn.Amount(...) bn.Transaction(...) # etc. I'd like for \"bn\" to become the de-facto two-letter import on top of which we write all the scripts. Default values in constructors. The namedtuple containers are mighty fine, but their constructors never had optional arguments, and it's always a bit of a dance to create those containers with a ton of \"None\" options. I never liked it. We'll make this tidy in the next iteration. No API documentation. While there is a substantial amount of documentation around the project, there is no documentation showing people how to use the Python API, e.g. how to accumulate balances, how to create and use a realization tree, how to pull information out of an accumulated inventory object, etc. I think that documenting some of the most common operations will go a long way towards empowering people to make the most out of Beancount. Some of these operations include: Accumulating lots of an inventory and printing them. Converting to market value, and making corresponding account adjustments. \u2026. add more \u2026 Exposed, usable booking. Booking will be a simple loop that can be invoked from Python with an entry and some accumulated state. Moreover, the Inventory object should begin to implement some of the lower-level operations required for booking, such that iterating over a set of postings and doing e.g., average booking, can be done via method calls on an Inventory object. Inventory should take a more prominent place in the API. Data types. Well defined data types should be provided for all objects to make liberal use of the typing module over all new code. Maybe create a module called \"bn.types\" but they should be available directly from \"bn.*\" so that there is a single short-named import. Terminology. I'd like to stop using \"entries\" and consolidate over the name \"directives\" in Vnext. Realization. I've been using a collections.defaultdict(Inventory) and a \"realization\" interchangeably. Both of these are mappings from an account name (or some other key) to an Inventory state object. I'd like to unify both of these constructs into the realization and make it into a commonly used object, with some helper methods. Parser Rewrite \uf0c1 Since we will now depend on C++, the parser will get to be rewritten. Worry not: the input syntax will remain the same or at least compatible with the existing v2 parser. What will change is: Unicode UTF-8 support. The lexer will get rewritten with RE/flex instead of GNU flex. This scanner generator supports Unicode natively and any of the input tokens will support UTF-8 syntax. This should include account names, an oft-requested feature. Flags. The current scanner limits our ability to support any flag and supports only a small list of them. I think the list has proved sufficient for use, but since I'll be putting some work into a new scanner I'm hoping to clean up that story and support a broader, better defined subset of single-letter flags for transactions. Time. The parser will parse and provide a time field, in addition to the date. The time may be used as an extra key in sorting directives. The details for this are yet to be determined, but this is requested often enough at the very minimum the parser will output it as metadata, and at best, it may become a first-class feature. Caching. The pickle cache will be removed. Until very recently , there weren't great options for disabling it (env vars) and I'd rather remove the only two environment variables that Beancount honors as a side-effect. Since the C++ code should be fast enough, hopefully a cache will not be needed. Tags & links. In practice, those two features occupy a very similar role as that of metadata (used to filter transactions). I'm contemplating unseating the special place taken by tags and links in the favor of turning those into metadata; the input syntax would not be removed, but instead the values would be merged into the metadata fields. I'm not 100% sure yet about doing this and open for discussion. Furthermore, the parser should be made to accept #tag and ^link where metadata is declared today, which would be convenient syntax. Finally, users have expressed a desire for tags on postings. We should contemplate that. Plugins configuration as protos. The options for the various plugins have been loosely defined as eval'ed Python code. This is pretty loose and doesn't provide a great opportunity for plugins to do validation nor document their expected inputs. I'd like to formalize plugin configuration syntax a bit, by supporting text-formatted protos in the input syntax (for a message type which would be provided by the plugins themselves). Parser in C++. The parser will be rewritten in C++. In the process of writing Vnext, I'll try to maintain a single grammar for both for as long as possible by calling out to a C++ driver interface, which will have two distinct implementations: one for the V2 version calling into Python, and one for the Vnext parser generating protos. In the process I may be porting the lexer and grammar Python implementation to C, as discussed in this ticket . Better includes. Current includes fail to recognize options that aren't in the top-level file. This caused many surprises in the past and should be fixed. At the minimum, an error should be raised. Code Quality Improvements \uf0c1 Rename \"augmentation\" and \"reduction\" to \"opening\" and \"closing\" everywhere. This is just more common terminology and will be more familiar and understandable to people outside of our context. Type annotations. The use of mypy or pytype with type annotations in Python 3 is by now a very common sight, and works quite well. As part of Vnext, all of the core libraries will be modified to include type annotations and the build should be running pytype automatically. I'll need to add this to our Bazel rules (Google doesn't currently provide external support for this). While doing this, I may relax some of the Args/Returns documentation convention, because in many cases (but not all) the type annotations are sufficient to get a good interpretation of a function's API. PyLint in build. Similarly, the linter should be run as an integral part of the build. I'd like to find a way to selectively and explicitly have to disable it during development, but otherwise be set up such that lint errors would be equivalent to build failures. Flexible constructors for Python API. The types generated by collections.namedtuple() or typing.NamedTuple don't offer flexible constructors with named parameters. I think all codes that create transaction objects today would benefit from having constructors with default values, and I'll be providing those to create corresponding proto objects. Tolerances & Precision \uf0c1 The story around how precision and tolerances are dealt with hasn't been great, for two reasons: Explicit tolerance option. I've tried to design the tolerance (used for balancing transactions) to be automatic and automatically inferred from statistics from the numbers in the input. The results aren't great. In Vnext I aim to provide an explicit option for setting the tolerance per currency. Precision. There are various places where numbers get rendered in v2: the reports code, the SQL query, and debugging scripts, and the way precision is set hasn't been used consistently. The precision also needs to be explicitly settable by the user. Rounding. There is another quantity that's used during interpolation: the precision used to round calculated numbers. Moreover, there is a need to distinguish between the precision and tolerances for numbers when used as prices vs. when used as units (see here ). One way is to store the display context per currency PAIR, not per currency itself. The distinction between these quantities hasn't been documented well; I'll keep in mind to clearly annotate those codes in Vnext and add suitable docs for this. Mostly the precision will be a rendering concern and a quantity that will be relevant for the new universal SQL query tool. Some prior design documentation exists here . Core Improvements \uf0c1 Some desiderata of new features are discussed below. These are all relevant to the core. Note that the changes should not interfere with current usage much, if at all. I expect that v2 users will be largely unaffected and won't have to change their ledger files. Booking Rules Redesign \uf0c1 Main document One of the current problems with booking is that entering an augmenting leg and a reducing leg have to be different by nature. The augmentation leg has to provide the cost basis via {...} syntax, and the reducing leg has to enter the price in the annotation and not in the cost basis. For example: 2021-02-24 * \"BOT +1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @143.75\" Assets:US:Ameritrade:Futures:Options 1 QNEG21C13100 {2875.00 USD} contract: 143.75 USD ... 2021-02-24 * \"SOLD -1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @149.00\" Assets:US:Ameritrade:Futures:Options -1 QNEG21C13100 {} @ 2980.00 USD contract: 149.00 USD ... Notice how the selling transaction has to be written down differently from the perspective of the user. The thing is, this makes it difficult from the perspective of the importer writer. It also ties the required syntax with the state of the inventory it's applied to, as it assumes something about this inventory. Moreover, this makes it difficult to write an importer that would handle a crossing of the absolute position, like this: 2021-02-19 * \"BOT +1 /NQH21:XCME @13593.00\" Assets:US:Ameritrade:Futures:Contracts 1 NQH21 {271860.00 USD} contract: 13593.00 USD Assets:US:Ameritrade:Futures:Margin -271860.00 USD Expenses:Financial:Commissions 2.25 USD Expenses:Financial:Fees 1.25 USD Assets:US:Ameritrade:Futures:Cash -3.50 USD 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -2 NQH21 {271815.00 USD} contract: 13590.75 USD Assets:US:Ameritrade:Futures:Margin 543630.00 USD Income:US:Ameritrade:Futures:PnL 45.00 USD Expenses:Financial:Commissions 4.50 USD Expenses:Financial:Fees 2.50 USD Assets:US:Ameritrade:Futures:Cash -52.00 USD The issue here is that we're crossing the flat line, in other words, we go from long one to short one. There are only two ways to do that properly right now: Disable booking and use the cost only, as per above. This is not great \u2014 booking is terribly useful. Track the position in your importer and separate the reducing and augmenting legs: 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {271815.00 USD} Both solutions aren't great. So I came up with something new: a complete reevaluation of how the syntax is to be interpreted. In fact, it's a simplification. What we can do is the following: use only the price annotation syntax for both augmentation and reduction and currency conversions, with a new booking rule \u2014 Match lots without cost basis in priority. If the lots have no cost basis, the weight of this posting is simply the converted amount, as before. If a match has been made against a lot with cost basis, the weight of this posting is that implied by the matched lots. Make the {...} used solely for disambiguating lots to match, and nothing else. If you have unambiguous matches, or a flexible booking strategy, e.g. FIFO, you'd pretty much never have to use the cost matching reduction. With this, the futures transaction above would simply use the @ price annotation syntax for both transactions. It would Make importers substantially simpler to write Supports futures naturally Be backward compatible with existing inputs for both currency conversions and investments. It is also more generally consistent and friendlier to Ledger users, without sacrificing any of the tighter constraints Beancount provides. I think it's even simpler to think about. Furthermore, this: Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD would be interpreted as \"match this lot, but only those with a cost basis attached to them.\" One question that remains is to decide whether an augmentation \u2014 now written down simply with @ price annotation \u2014 would store the cost basis in the inventory or not. I think we could make this determination per-commodity, or per-account. This would impose a new constraint: a commodity (or \"in an account\") would always be stored with cost basis, or not. Posting vs. Settlement Dates \uf0c1 When you import a transaction between multiple accounts within a single ledger, e.g. a credit card payment from one's checking account, the dates at which the transaction posts in each account may differ. One side is called the \"transaction date\" or \"posting date\" and the other side the \"settlement date.\" Where the money lives in between is somewhere in limbo (well in practice there is no money at all, just differences in accounting between institutions, things are never reflected instantly). One of the major shortcomings of the current core code is that the ability to insert a single transaction with postings at different dates is missing. Users are recommended to select a single date and fudge the other one. Some prior discussion on this topic exists here . Unfortunately, this method makes it impossible to represent the precise posting history on at least one of the two accounts. A good solution needs to be provided in Vnext, because this is a very common problem and I'd like to provide a system that allows you to precisely mirror your actual account history. The automatic insertion of transfer accounts to hold the commodities can be implemented as a feature, and it should live in the core. One possible idea would be to allow optional posting dates, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD which would result in two transactions behind the scenes, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD Equity:Transfer 2020-01-21 * \"ONLINE PAYMENT - THANK YOU\" \"\" Liabilities:US:Amex:BlueCash 2397.72 USD Equity:Transfer The lack of symmetry here raises the question of whether we should allow a transaction without a date or not: * \"ONLINE PAYMENT - THANK YOU\" \"\" 2020-01-19 Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD I think we can figure this out and the first solution is very doable. Input Split Transactions Some users like to organize their inputs in different files, or in different sections that strictly contain all of an account's transactions in order. This is related in spirit to the posting and settlement dates problem: at the moment the user is required to choose one of the two locations to insert their transaction. This should not be necessary. We should provide a mechanism that would allow users to insert the halves of a transaction into two different locations in their file, and a robust merging mechanism that would ensure that the two related transactions have been matched and merged (so that no unmerged half remains) and otherwise report errors clearly. The two halves could look like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD \u2026 2020-01-21 * \"AMEX EPAYMENT ACH PMT; DEBIT\" Liabilities:US:Amex:BlueCash 2397.72 USD The matching could be done via explicit insertion of special links, or by heuristics to match all such related transactions (perhaps declaring valid account pairs, thresholding on date differences and exactly matching amounts). When impossible to match, an error should be raised. Those merged transactions should be checked for balancing. Note how each of the transactions has a differing date; this would integrate with the transfer account solution proposed in the previous section. I haven't designed something yet, but this should be easy to implement and should be provided as a core feature, since it's so closely related to the input syntax. Currency Accounts instead of a Single Conversion \uf0c1 The current implementation of multiple currency transactions relies on a special \"conversion transaction\" that is automatically inserted at reporting time (when closing the year) to account for the sum total of imbalances between currencies. The goal of this transaction is to ensure that if you just sum up all the postings in the book, the result is purely an empty inventory (and not some residual amount of profit or loss incurred during currency exchange across different rates \u2014 note that we're talking only of the @price syntax, not investments). This is a bit of a kludge (the transaction itself does not balance, it converts to zero amounts of a fictional currency in order to keep itself quietly passing the balance test). What's more, its actual value is dependent on a subset of filtered transactions being summed up so it's a reporting-level construct, see here . There exists a method for dealing with multiple currencies without compromising on the hermeticity of individual transactions, described online, here . Using that method, you can filter any subset of transactions and summing them up will cleanly cancel out all lots. You don't need to insert any extra weight to fix up the balance. Also, you can explicitly book profits against the accrued gains in the currency accounts and zero them out and take advantage of this when you report them (and track them over time). The downside is that any currency conversion would see extra postings being inserted, etc. 2020-06-02 * \"Bought document camera\" Expenses:Work:Conference 59.98 EUR @ USD Liabilities:CreditCard -87.54 USD Equity:CurrencyAccounts:EUR -59.98 EUR Equity:CurrencyAccounts:USD 87.54 USD The problem is that it's a pain to use this method manually, it requires too much extra input. It's possible to have Beancount do that for us behind the scenes, completely automatically. I coded a proof-of-concept implementation here , but it's incomplete . In Vnext: The insertion of the kludgey conversions transactions should be removed. The currency accounts should become the norm. The fact that the two streams of directives will be very clearly separated should help, by distinguishing even more clearly between the parsing representation and the fully booked one, which will show these extra legs on transactions The prototype should be completed and issues fixed completely (not that much work involved). Strict Payees \uf0c1 I'm not sure if this makes sense yet, but I'd like to clean up the mess that payee strings are today. Payees are free-form, and if the user does not take care to clean them up\u2014and I'm one of those who doesn't\u2014the memos from imported sources are messy. It could be interesting to create a new directive to declare payee names ahead of time and an optional model that would require payees to be found in the list of declared payee names. Payees would have to have open and close dates, dates which would define the valid duration of the relationship with that payee (thereby adding more error verification capability). Price Inference from Database \uf0c1 Interpolation from price database. One of the oft-requested features is the ability to automatically interpolate prices from the internal price database history. I think that this should be doable unambiguously and deterministically and added to the interpolation algorithm. Price validation. Since a lot of the conversions at price (i.e., using \"@\") are inferred by leaving out one number, we should validate that the effective price is within some tolerance of a pre-existing price point near the date. This would provide yet another level of checking. Quantizing Operators \uf0c1 Another useful addition to the syntax would be operators that automatically quantize their result to a precision that depends on the particular target currency. For example, 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 / 1.19 EUR Expenses:Food:Taxes 2.13 / 1.19 * 0.19 EUR ; for example to calculate tax Assets:Cash That would become: 1970-01-01 * \"coffee\" Expenses:Food:Net 1.789915966386555 EUR Expenses:Food:Taxes 0.340084033613445 EUR Assets:Cash If instead an operator like this were provided, it would fix the issue: 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 /. 1.19 EUR Expenses:Food:Taxes (2.13 / 1.19 * 0.19). EUR Assets:Cash Or somesuch. Or maybe we'll want to add an option such that every evaluation of an arithmetic expression is automatically quantized as such. Constraints System & Budgeting \uf0c1 Beancount does not support budgeting constraints explicitly, but I think it would be possible to extend the balance assertion semantics to cover this. The current balance assertions check (a) a single commodity, and (b) that the amount is precisely equal to an expected one. Balance assertions should be extended to support inequalities, e.g., 2020-06-02 balance Liabilities:CreditCard > 1000.00 USD and perhaps we could check for the total inventory like this 2020-06-02 balanceall Assets:Cash 200.00 USD, 300.00 CAD I'd be curious to hear what would work best from users who do budgeting and design a minimalistic expression language to support that use case (though I'd try to keep it as simple as possible to avoid feature creep). Also, if the syntax is getting changed, adding a syntax that allows checking multiple currencies at once, and possibly a complete assertion that checks there aren't other commodities in the account could also make sense. Average Cost Booking \uf0c1 Average cost booking has been discussed and a good solution sketched out a very long time ago. Vnext should sport that method natively; a lot of users want to have this feature for dealing with their tax-deferred accounts. It takes a bit of work to handle the precision of the various automated conversions right. The way it would work is by automatically merging all related lots of the same commodity on a reduction, and optionally on an augmentation. Some constraints may be required (e.g. only a single commodity in that account). Trade Matching & Reporting \uf0c1 A few core tasks related to P/L and trading still need to be implemented. Trade list. A problem that I've really been wanting to solve for a very long time but never found the time for is to save crumbs from the booking process so that a correct list of trade pairs could be easily extracted from the list of directives. I wrote some notes here and here a long time ago. Essentially, for each booked reduction, insert a reference to the corresponding augmenting posting. I've prototyped this as metadata but it should be made something more official. A single linear scan can pick up these references, build a mapping and recover the (buy, sell) pairs to produce a table of trades. There's a precedent I once wrote in a plugin . Needless to say, producing a list of trades is a pretty basic function that Beancount does not provide out of the box today; it really should. Right now users write their own scripts. This needs to be supported out-of-the-box. Harmonize balance and gains validation. Checking that transactions balance and that income from gains balance with a transaction's prices (the sellgains plugin) are done in completely separate places. Those two codes occupy similar roles, and should be implemented next to each other. Commissions in P/L. Properly counting profits & losses by taking off the fraction of buying commission of an original lot and the selling commission into account is not possible at the moment. I think it could be done with a plugin that moves some of the (computed) income leg into a separate negative income account to do this properly for reporting purposes. Self-Reductions \uf0c1 Currently the application of reductions operates on the inventory preceding the transaction. This prevents the common case of self-reductions, and both I and some users have come across this problem before, e.g. this recent thread ( ticket ). This comes off as unintuitive to some users and ought to have a better solution than requiring splitting of transactions. Since we're rewriting the booking code entirely in Vnext, contemplate a new definition that would provide a well-defined behavior in this case. I remember from prior experiments attempting to implement this that it wasn't a trivial thing to define. Revisit. This would be a nice improvement. Stock Splits \uf0c1 Some discussion and perhaps a strategy for handling stock splits should be devised in Vnext. Right now, Beancount ignores the issue. At the minimum this could be just adding the information to the price database. See this document for more details. Multipliers \uf0c1 Options have a standard contract size of 100. Futures have a contract size that depends on the particular instrument (e.g., /NQ with a multiplier of 20). I've been handling this for options by multiplying the units by 100, and for futures by multiplying the contract size by the per-contract multipliers (ditto for options on futures). I do this in the importers. For options, it works and it isn't too bad (e.g. positions of -300 instead of -3), but for futures, it's ugly. The result is not representative of the actual transaction. I'd like to add a per-currency multiplier, as well as a global dictionary of regexps-to-multiplier to apply, and for this to be applied everywhere consistently. One challenge is that everywhere there's a cost or price calculation, this has to be applied. In the current version, those are just multiplications so in many parts of the codebase these haven't been wrapped up in a function that could easily be modified. This needs to happen in a big rewrite \u2014 this is the opportunity to do this. Example here. Returns Calculations \uf0c1 If you look at investment brokers out there, no one calculates returns correctly. Brokers provide one of two features: No cash transfers. Total value of account today vs. total value of account at some point in the past (i.e., account inception or beginning of the year). This isn't very useful because they never account for the addition or removal of cash to the account. For example, say you open an account with $100,000 and invest, and mid-year add another $20,000, say the original investments are now worth $95,000, the report would show a gain of $15,000, whereas you really incurred a loss. Better brokers like Vanguard will show a plot that includes two overlaid bars, one with cash added and profit overlaid, like this: Lack of interest or dividends. Other brokers will report P/L over time from the investments, but they fail to account for actual interest or dividends received (they only look at the price of the underlying) so that's not useful for bonds or for stocks with significant dividends, or when grouping them, they fail to account for the addition of new positions over time. Counterfactual performance. Finally, all of them fail to compare your actual annualized performance with that of a benchmark portfolio with equivalent cash infusions. For example, instead of your actual investments made, compare with the performance you would have obtained if you had invested in some standardized portfolio of investments over that particular time period, given the actual historical prices of those instruments. Ideally one should be able to define any alternative portfolio to compare against using their particular cash transfers. More fancy analyses aren't even contemplated, e.g., what would have been the impact of changing my rebalancing strategy (or actually implementing a more strict one)? There are well known methods for both time-based and value-based returns reporting. The right thing to do is to extract a time-series of cash flows and compute the annualized or IRR returns, or value-weights. I started this work at some point and ran against some difficulties and eventually removed it. The results remain here. I'd really love to build this, and eventually perhaps this could grow into its own project, with associated support functions in the Beancount core. This will possibly be a project of its own, but this requires similar support for enumerating instruments and price sources as that which is needed for fetching prices, as well as functions for isolating cash flows for specific subsets of accounts; these should probably live in the core. UPDATE September 2020: This has mostly been implemented. See this document for details. Unsigned Debits and Credits \uf0c1 A useful idea that's nearly trivial to implement is to allow users to input all positive unit numbers and to automatically flip the signs on input, and to output them all as positive numbers as well splitting them up between Debit and Credit columns. This would make Beancount's rendering a lot easier to grok for people with an existing background in accounting. This feature will introduce no complexity, easy to add for free. See TODO here . Holdings \uf0c1 One of the libraries I built at some point was this notion of a \"holding\", here . At the time I wasn't sure if that data would contain much more than what's in a posting, but as it turns out, representing holdings as just the posting is good enough, all you need is the market value, and compute the total values from the units is trivial. In fact, I've haven't been using that code for years, I've been using the export script instead, which writes out a table that gets uploaded to a Google Sheets doc. This proves to me aggregating the positions in an Inventory is plenty sufficient in practice, along with a mapping of latest market prices. I'm going to delete that code. It's only been used in the reports code anyway, which will be removed anyway, and in the experimental \" unrealized gains \" plug, which was only a proof-of-concept that convinced me booking such gains as transactions is not a good idea and which will get removed and live in a separate experimental repository anyway. Tooling for Debugging \uf0c1 Context should recover erroneous transactions. One of the major annoyances of error recovery is that if a transaction involves some types of errors, the postings aren't produced in the stream of directives. This problem is related to the lack of clarity between the merely parsed data structure and the fully resolved one. In particular, this means that the \"bean-doctor context\" debugging command often cannot provide useful context around a failing transaction. This really needs to be fixed, to improve debuggability. Document debugging tools. In general, I should write a better exposition of how to use the various transaction debugging tools; a lot of the questions on the mailing-list would disappear if users knew better how to leverage those. Interactive context in Emacs. If the performance allows it, we could build an Emacs mode which renders the context around a partially written transaction, including inventories of the affected accounts before and after the transaction, as well as interpolated values, to a different buffer updated interactively. This would make it much more fun to input data and provide immediate feedback about the newly inserted transaction. Documentation Improvements \uf0c1 Remove dependency on furius.ca. The current Google Docs based documentation links to other documents via a global redirect (the definition is found here ). While it does not happen often that my web server goes down (perhaps a few times per year), when it does it takes a few days to rectify the situation. That server is hosted graciously in the company of some friends of mine. Kirill has proved that it would be possible to replace all the links to redirects on github, that would look like this: beancount.github.io/ instead of furius.ca/beancount/doc/ . In order to do this, I'll need to run a script using the Docs API on all the Google Docs to change them automatically. Conclusion \uf0c1 There are other items in the TODO file . These are just the main, big issues that I think matter the most and I'd like to address them in a Vnext rewrite. Development branches will look like this: v2 : Current master will be branched to \"v2\", which will track the stable current version. That branch will build with both the current setup.py system and Bazel. Fixes will be implemented on that branch where possible, and merged to Vnext. master : Current master will become Vnext. Only the Bazel build will be supported on that branch. Any comments appreciated. Appendix \uf0c1 More core ideas for Vnext that came about during discussions after the fact. Customizable Booking \uf0c1 For transfer lots with cost basis\u2026 an idea would be to create a new kind of hook, one that is registered from a plugin, e.g. a callback of yours invoked by the booking code itself, and whose results applied to a transaction are immediately reflected on the state of the affected inventories. Maybe this is the right place to provide custom algorithms so that their impact is affecting the subsequent inventories correctly and immediately. Now, imagine generalizing this further to provide and implement all of the current booking mechanisms that are currently built in the core. Call this \"customizable booking.\" ( thread ). Ugly Little Things \uf0c1 print_entry() uses buffering that makes it impossible to use regular print() interspersed with the regular stdout without providing file= option. Fix this, make this regular instead, that's just annoying, just print to regular stdout. The default format for __str__ for inventories puts () around the rendering. When there's a single position, that looks like a negative number. That's dumb. Use {} instead, or something else. Add a flag to bean-check to make it run --auto plugins by default. This is great for imported files, which may not have a complete ledger to feed in. Incremental Booking/ Beancount Server / Emacs Companion \uf0c1 In order to make recomputation fast, the idea of creating a standalone \"Beancount server\" starts to make sense. The expensive part of the Beancout calculations on a large file is the booking and interpolation. The key to making things fast is thus to keep all the original unprocessed transactions in memory along with the booked and interpolated ones, and on a change, reparse the modified files and scan all the transactions, updating only the ones whose accounts have been affected. This could be problematic in theory: some plugins may rely on non-local effects in a way that affects what they output. I believe in practice it would work 99% of the time. But I think it may be worth a prototype. On the other hand, Vnext may turn out to be fast enough recomputing everything from scratch every single time (my own file went from 4s -> 0.3ms for the parsing stage of the largest file), so maybe this is irrelevant overall. Such a server would be a perfect companion to a running Emacs. We could build an Emacs mode which communicates with the server. Tags & Links Merge with MetaData \uf0c1 TODO(blais): Add colon syntax","title":"Goals & Design"},{"location":"beancount_v3.html#beancount-vnext-goals-design","text":"Martin Blais , July 2020 http://furius.ca/beancount/doc/Vnext","title":"Beancount Vnext: Goals & Design"},{"location":"beancount_v3.html#motivation","text":"It's time to give Beancount a refresh and to put down a concrete plan for what the next iteration of it ought to be. I've had these thoughts in the back of my mind for a long while\u2014at least a year\u2014and I'm putting these in writing partly to share a vision of what the product we all use to organize our finances can become, partly to solicit feedback, and partly to organize my thoughts and to prioritize well on the stuff that matters. Current status. The current state of Beancount is that development has been static for a while now, for a number of reasons. The software is in a state that's far from perfect (and I'll be enumerating the main problems in this document) but I've been resisting making too many changes in order to provide myself and others a really stable base to work from. More importantly, while I used to be able to spend a significant amount of weekend time on its development, life changes and a focus on my career of late has made it difficult for me to justify or find the extra time (it has been 10 years after all). A multitude of ideas have aged to this TODO file but it's too detailed to grok and a bit of a dump, this document should be more useful. Why rewrites happen. When I wrote version 2 of Beancount (a full rewrite of the first version), it was because of a confluence of ideas for improving my first draft; I resisted for a while, but eventually it made so much sense to me that it became simply impossible not to write it. Many of the ideas driving the redesign at the time are still axioms in today's design: removing order dependence, normalizing the syntax to be well-defined with a BNF grammar, converting custom processing to a sequence of plugins off of a simple stream of directives, the current design of booking selection and how cost basis works, and all the directives beyond \"Transaction\". These ideas largely shape what a lot of people like about using Beancount today. Goals. Now is the time for yet another wave of evolution for Beancount, and similarly, a set of new ideas I'm going to lay down in this document form as potent a change as the v1 to v2 transition. The vision I have for Vnext will simplify Beancount, by factoring into simpler, more isolated, more reusable, better defined parts, and not merely by adding new features on top of what's there. In many ways, Vnext will be a distillation of the current system. It will also make space to finally implement some of the core features most often desired by users. And those changes will enhance some organizational aspects: allow for more contributions, and also trim down the part that I'm handling myself to less code, so I can more effectively focus on just the core features.","title":"Motivation"},{"location":"beancount_v3.html#current-problems","text":"","title":"Current Problems"},{"location":"beancount_v3.html#performance","text":"My personal ledger, and I know that the ledgers of many users, are simply too large to process instantly. My current file takes 6 seconds on the souped-up NUC I use for a desktop at home\u2014but that's just too long. I'm really quite attached to the idea of processing the entire set of inputs every time, instead of forcing users to cut-up their ledgers into multiple files with \"closing\" transitions at arbitrary points in the year, but I really do want that \"instant\" feeling that you get when you run two-letter UNIX programs, that it runs in well under half a second . It makes it a lot more interactive and fun to use. C++ rewrite. One of the reasons for the slow performance right now is the fact that Beancount is implemented in Python, even at the level of the parser (C code calling back into a Python driver). An obvious solution is to rewrite the core of the software in a language closer to the metal, and that will be C++. I'm selecting C++ for its control and because the current slate of tools around it is mature and widespread enough that it should be easy for most to build without too many problems, and I can leverage C libraries that I will need. Using a functional language could have been fun but many of the libraries I want simply would not be available or it would be too difficult for mere mortals to build. Simple, portable C++. It's important to mention that the C++ code I have in mind is not in the style of template-heavy modern C++ code you'd find in something like Boost. Rather, it's a lot more like the conservative \"almost C without exceptions\" subset of C++ that Google uses , with a base on Abseil-Cpp (for example and flavor, see tips ). The reasons for this are stability and portability, and while this rewrite is for faster performance, I believe that it will not be necessary to pull template tricks to make it run fast enough; just a straightforward port to avoid the Python runtime will likely be sufficient. Above all I want to keep the new code simple and \"functional-ish\" as much as possible (no classes if I can avoid it), relying on a trusted set of stable dependencies , built hermetically using the Bazel build tool. Python API. It's also important that the Python API remains for plugins and scripts, and that the full suite of unit tests be carried over to the newer version of the code. After all, the ability to write custom scripts using all that personal finance data is one of the most attractive features of the text-based approach. Code beyond the new core implementation will remain in Python, and existing code built on top of the Python API should be very easily portable to Vnext. This can be achieved by exposing the directives with wrappers written in pybind11. Other languages. The resolved output of the Beancount core will be a stream of protocol buffer objects, so processing from other languages (e.g., Go) will have first-class support. Processing model. An additional algorithmic improvement to performance, should it be necessary, would be to define plugins processing in terms of iterator functions that cascade and interleave the processing of the directive stream without making entirely disjoint passes over the full list of directives. While the freedom to have each plugin process all the directives on their own has been instrumental in keeping the system free of synchronized application state and has allowed me to isolate behavior of the plugins from each other, there are opportunities to join together multiple transformations in a single pass. Fewer passes = faster.","title":"Performance"},{"location":"beancount_v3.html#intermediate-parsed-data-vs-final-list-of-directives","text":"In Beancount v2, I was never too careful to clearly distinguish between The list of directives coming out of the parser , missing interpolation and booking, using position.CostSpec instead of position.Cost on each lot, and The resolved and booked list of directives with booking algorithms applied to select matching lots, and interpolated values filled in, as well as transformations having been applied by the various plugins. These two lists of directives are really quite distinct in purpose, though they share many common data structures, and for the most part, the first list appears mostly in the parser module. There have been cases where it was confusing, even to me, which of the lists I was manipulating. Part of the reason is due to how I'm using mostly the same Python data structures for both, that allow me to bend the rules on typing. Perhaps more importantly is that because plugins run after booking and interpolation, and are required to put out fully interpolated and booked transactions, a plugin that wants to extend transactions that would run as invalid in the input syntax is difficult. See #541 for an example. The next iteration will see both the intermediate parser production and final resolved list of directives implemented as protocol buffer messages, with strictly distinct data types. This will replace beancount.core.data . The distinction between these two streams will be made very clear, and I will try to hide the former as much as possible. The goal is to avoid plugin writers to ever even see the intermediate list. It should become a hidden detail of the implementation of the core. Furthermore, there may be two types of plugins: a plugin that runs on the uninterpolated, unbooked output of the parser, and a plugin that runs on the resolved and booked stream. This would allow more creative use of partial input that might be invalid under the limitations of interpolation and booking. Updates: We could convert the plugin system to one that runs at booking/interpolation time. We should attempt to make the booking/interpolation atomic, in that one could write a Python loop with an accumulator and invoke it independently, so that in theory, booking could be used in the importers (like I do for Ameritrade).","title":"Intermediate Parsed Data vs. Final List of Directives"},{"location":"beancount_v3.html#make-rewriting-the-input-first-class","text":"Added in Dec 2020 after comments and #586 . A number of frequently asked questions have to do with how to process the input data itself. Usually, a new user will attempt to load the contents of the ledger, modify the data structures, and print to update their file, not realizing that the printer includes all the interpolations, booking data, and modifications from plugins, so this cannot work. However, since we're rewriting the parser and ensuring a clean separation between intermediate ASI-like data and processed and finalized directives, we can implement a special printer for the AST intermediate data, so that users could run just the parser, modify the intermediate directives, and print them back out, perhaps losing just some of the formatting and whitespace. This formatting loss can be leveraged to reimplement bean-format more naturally: the output of that printer should always be formatted neatly. This would avoid users having to write ad-hoc parsers on their input file, sed-like conversions, and so on. They could do it properly by modifying the data structure instead. What's more, in order for this to work accurately, we'd have to delay processing of the arithmetic operations post-parsing, so that we can render them back out. This offers another advantage: if we process the calculations after parsing, we can afford to provide an option to let the user specify the precision configuration to use for mpdecimal. I really like that idea, because it avoids hard-coding calculation precision and better defines the outcome of these options, potentially opening the door to a more rational way to remove extra digits that often get rendered out. Finally, if a nice library function can be made to process transactions in-place and output them back out, preserving all comments around them, this can become another way\u2014perhaps the preferential way\u2014for us to clean payees and somesuch. At the moment, the solution is to write a plugin that will clean up the data, but the input file remains a bit of a mess. Making it easy to automatically clean up the input file is an appealing alternative and potentially will add an important new dimension to the Beancount workflow. I want to make all changes necessary to make this possible and well supported (I'm imagining my ledger file all cleaned up right now and it's appealing). I think it's not very much work, it involves: Storing begin/end line information on everything. Adding AST constructs for representing arithmetic calculations. Adding comments parsing to the renderer. Implementing a new renderer that can reproduce the AST, including handling missing data. Implementing a library to make modification of a file in-place as easy as writing plugins, while preserving all non-directive data in the file as is.","title":"Make Rewriting the Input First Class"},{"location":"beancount_v3.html#contributions","text":"For most of the development of Beancount, I've been pretty reluctant to accept contributions. It has been a closely held pet project of mine since it has so much impact on my personal financial arrangements and I dread unplanned breakage. The main reservations I've had over contributions are two-fold: Not enough testing. Proposed changes that did not include enough testing, or none at all, sometimes even the kind testing that would prevent basic breakage. When I'd accept some proposals and commit to writing the tests myself it could sometimes take me down the rabbit hole for hours (if not days). This wasn't practical. Cascading design impact. Some of the proposals did not take into account broader design considerations that would affect other parts of the code, which I may not have communicated or documented well. I've had to reject some ideas in the interest of keeping things coherent and \"tight.\" Part of this is my fault: putting a high bar on contributions hasn't allowed potential contributors to acquire enough context and familiarity with the codebase to make changes compatible with its design. Allowing more contributions. Starting in Vnext I'd like to restructure the project so that more people are able to get involved directly, and to closely work 1-on-1 with some contributors on particular features. Clearly Beancount benefits from direct input from more people. The recent move to GitHub only compounds the urgency for making that easier. To this effect, I'd like to implement a few strategies: Break down the code in parts. This is a natural evolution that has been seen in many other projects; I'm no Linus nor Guido, but like their respective projects, as they grew in popularity, the original authors narrowed their focus on the core part and let other people expand on adjacent but also important functionality. I'd like to focus more of my time only on core functionality that will impact things like support for settlement dates, currency accounts, split transactions, trade reporting, etc. Letting other people deal with adding or updating price sources, making improvements to the ingestion framework, and making beautiful renderings and presentations of the data would be ideal. In time, I may eventually break down these libraries to separate repositories with looser contribution guidelines and/or add ACLs for others to push directly to those repos. Acquire \"lieutenants.\" I need to leave more space for trusted and frequent contributors to chip in more liberally. For example, Martin Michlmayr now has direct edit access to most documents and has been making numerous helpful contributions and updates to the docs. Kirill Goncharov's conversion of the documentation out of Google Docs is simply beautiful. RedStreet and many others are regularly pitching in answers on the mailing-list. Stefano Zacchiroli and Martin have built a standalone conversion tool from Ledger. Daniele Nicolodi is proposing some low-level changes to the scanner and parser. And of course, Dominik Aumayr and Jakob Schnitzer continue developing the Fava project adjacent to Beancount. There are many more people, there is a slowly-but-surely growing list of familiar recurring names. The question in my mind is: Is there a way to communicate with regular faces so that we're aligned in terms of design and can coordinate our efforts in the same direction? Does the newly acquired familiarity with video-conference meetings (thanks to the coronavirus crisis) afford us a new channel for coordination that wasn't there before? The Python community has thrived in no small part due to the annual face-to-face interactions between its participants and a number of core developers being in the same place for some time. Can we achieve the same thing online, as we are a smaller community? If so, better coordination may make it easier to accept proposed changes. I wonder if I should propose a monthly \"team\" meeting. 20% projects. I should provide a list of \"20% projects\" that are well aligned with the direction of the project for others to take up if they want to, and add profuse guidance and detail of downstream side-effects from those proposed features. The idea is to make it possible for newcomers to contribute changes that are likely to fit well and be easily integrated and accepted on the codebase.: Proposals. Beancount's equivalents of Python's \" PEPs \" are essentially the Google Docs proposal documents I started from threads where many others comment and add suggestions. A central list of those should be shared to a folder, identified as such, and allow others to write similar proposals. Maybe a little more structure and discipline around those would be useful.","title":"Contributions"},{"location":"beancount_v3.html#restructuring-the-code","text":"At the very coarse level, the code restructuring for Vnext looks like this: C++ core, parser, and built-in plugins. The Beancount core, parser, booking algorithm and plugins get rewritten in simple C++, outputting its parsed and booked contents as a stream of protobuf objects. Query engine. Beancount query/SQL gets forked to a separate project operating on arbitrary data schemas, with a much broader scope than Beancount. See section below. The rest. Most of the rest gets cut up into separate projects or at least, at first, in a distinct place within the repository (until the core is rewritten). Note that because the core outputs the stream of directives as proto objects, any language supported by protobufs should be able to read those. This extends the reach of Beancount. Here's a simplified diagram showing how this might look: Here is a detailed breakdown of the various parts of the codebase today and what I think will happen to them: Core. This is the part of Beancount's code which will get rewritten in C++ and output a sequence of messages to a stream of directives. I'll continue keeping a tight focus on that part with a conservative eye toward stability, but in Vnext will be adding desired new capabilities that have been lacking so far as described in the next section of this document. The core will include the following packages: beancount/core beancount/ops beancount/parser beancount/utils beancount/loader.py beancount/plugins (some, see below) beancount/utils (most) Query. The query language will be factored out into a completely separate repo with a broader application domain (and hooks for customizing for Beancount). I suspect that over time that project will acquire a much broader range of contributors, many of which will not even be Beancount users. This includes the code from these packages: beancount/query beancount/tools Prices. This is a simple library and tool that helps users fetch prices from external sources. This should definitely move to another repo and I'd welcome a new owner building a competing solution. People are sending me patches for new price sources and I have too little time to maintain them over time, as the upstream sources change or even disappear. This requires very little from Beancount itself (in theory you could just print() the directives for output, without even loading library code) but I think the Beancount core should include and functions to enumerate a list of required date/instrument pairs at a particular date from a given ledger (and I'm happy to support that). Note that the internal price database core will remain in the core, because it's needed there. The affected packages are: beancount/prices Improvements should be made to this library after it moves out of the Beancount repository: we should isolate the Beancount code to just a few modules, and turn the scope of this project to something larger than Beancount: it's three things, really: a) an up-to-date Python library of price fetchers with unit tests and maintained by the community (i.e., when sources break, we update the library) and a common API interface (needs to be improved from what's there TBH, the API should support fetching time series in a single call); b) an accompanying command-line tool (currency \"bean-price\") for fetching those prices from the command-line. This requires the specification of \"a price in a particular currency from a particular source\" as a string. I'd like to improve that spec to make the USD: prefix optional, and maybe eliminate the chain of prices in the spec, which hasn't found much use in practice and move that upstream. c) Make the interfaces to fetch ledger-related information (e.g., list of missing/required prices and lists of instruments) onto modules: beancount v2, beancount Vnext, ledger, hledger, and rendering output formats to any of these. In other words, this library should be able to fetch prices even if Beancount isn't installed. To turn this project into something that can run independent of beancount. Ingest. The importers library will probably move to another repo and eventually could even find another owner. I think the most interesting part of it has been the establishment of clear phases: the identify, extract and file tasks, and a regression testing framework which works on real input files checking against expected converted outputs, which has worked well to minimize the pain of upgrading importers when they break (as they do break regularly, is a SNAFU). In the past I've had to pull some tricks to make command-line tools provided by the project support an input configuration as Python code but also possible to integrate in a script; I will remove the generic programs and users will be required to turn their configuration itself into a script that will just provide subcommands when run; the change will be very easy for existing users: it will require only a single line-of-code at the bottom of their existing files. The Bazel build may add some minor difficulties in loading a Python extension module built from within a Bazel workspace from an otherwise machine-wide Python installation, but I'm confident we'll figure it out. I'd also be happy for someone else to eventually take ownership of this framework, as long as the basic functionality and API remains stable. The example csv and ofx importers should be removed from it and live in their own repos, perhaps: ofx. the OFX importer should be replaced by something using ofxtools (the one I built is pretty bad), and the CSV importer really needs a thorough rewrite with lots of unit testing for the large list of options it now supports (these tests are sorely missing). csv. Furthermore, I think the CSV importer could be enhanced to be smarter and more flexible, to automatically detect from the column headers and inferred data types in the files which column should convert into which field. I'm not going to do that (I don't have time). Someone with the urge to make the ultimate automatic CSV parser ought to create a separate repository for that. The affected packages are: beancount/ingest : could eventually move to another repo. beancount/ingest/importers: someone could revive a repository of importer implementations, like what LedgerHub once aimed to become, and swallow those codes. See this document for details on what's to happen with the ingestion code. Custom reports and bean-web should be removed: the underlying bottle library seems unmaintained at this point, Fava subsumes bean-web, and I never liked the custom reports code anyway (they're a pain to modify). I never use them myself anymore (other than through bean-web). I really think it's possible to replace those with filters on top enhanced SQL query results. The conversion to Ledger and HLedger from Beancount now seems largely useless, I'm not sure anyone's using those. I'll probably move these to another repo, where they would eventually rot, or if someone cares, adopt them and maintain or evolve them. beancount/web : will be deleted or moved to another repo. beancount/reports : will be deleted or moved to another repo. Note that this includes deprecating beancount/scripts/bake , which depends heavily on bean-web. I have no substitute for bean-bake, but I think I'd like to eventually build something better, a tool that would directly render a user-provided list of specific SQL queries to PDF files and collate them, something you can print. Jupyter notebook support. A replacement for the lightweight interface bean-web used to provide could be Jupyter Notebook integration of the query engine, so that users can run SQL queries from cells and have them rendered as tables, or perhaps a super light web application which only supports rendering general SQL queries to tables. Built-in Plugins. Beancount provides a list of internal plugins under beancount/plugins . It's not indicated clearly, but there have evolved two groups of plugins in there: stable plugins used by the core, and experimental plugins showcasing ideas, which are often incomplete implementations of something that was proposed from a thread on the mailing-list. The former group will be ported to C++, and the latter group should probably move to another location with much looser acceptance constraints. First, there are \"meta-plugins\" which only include groups of other plugins: Only one of those should remain, and maybe be enabled by default (making Beancount pedantic by default): auto pedantic The following plugins should remain in the core and be ported to C++: auto_accounts check_closing check_commodity close_tree commodity_attr check_average_cost coherent_cost currency_accounts implicit_prices leafonly noduplicates nounused onecommodity sellgains unique_prices The following are the experimental implementations of ideas that should move to a dedicated repo where other people can chip in other plugin implementations: book_conversions divert_expenses exclude_tag fill_account fix_payees forecast ira_contribs mark_unverified merge_meta split_expenses tag_pending unrealized Because it's a really common occurrence, the new transfer_lots plugin should be part of the built-in ones. Projects. The beancount/projects directory contains the export script and a project to produce data for a will. The will script will be moved outside the core of Beancount, I'm not sure anyone's using that. Maybe the new external plugins repo could include that script and other scripts I shared under /experimental. The export script should be grouped together with beancount/scripts/sql and other methods to send / share data outside of a ledger; these could remain in the core (I'm using the export script regularly to sync my aggregates and stock exposure to a Google Sheets doc which reflects intraday changes). Scripts. Some of the scripts are completely unrelated to Beancount, they are companions. The scrape validator. The sheets upload. The treeify tool. These should be moved elsewhere. One of the advantages of having all the code in the same repo is that it makes it possible to synchronize API changes across the entire codebase with a single commit. As such, I may keep some of the codes in the same repo until the new C++ core has stabilized, and properly separate them only when Vnext releases.","title":"Restructuring the Code"},{"location":"beancount_v3.html#universal-lightweight-query-engine-ulque","text":"The SQL query engine for Beancount was initially a prototype but has grown to become the main way to get data out of it. I've been pretty liberal about adding functionality to it when needed and it's time to clean this up and consider a more polished solution. In Vnext, the query/SQL code gets eventually forked to a separate project (and repo) operating on arbitrary data schemas (via protobufs as a common description for various sources of data) and has support for Beancount integration. Imagine if you could automatically infer a schema from an arbitrary CSV file, and run operations on it, either as a Python library function or as a standalone tool. Furthermore, this tool will support sources and/or sinks to/from Google Sheets, XLS spreadsheets, containers of binary streams of serialized protos, tables from HTML web pages, PDF files, directories of files, and many more. This is going to be a data analysis tool with a scope closer to that of the Pandas library rather than an accounting-focused project, but also a universal converter tool, that will include the functionality of the upload-to-sheets script (which will get removed). One of the lessons from the SQL query engine in Beancount is that with just a little bit of post-processing (such as treeify ), we can do most of the operations in Beancount (journals, balance sheet & income statements) as queries with filters and aggregations. The tool will be made extensible in the ways required to add some of the idiosyncrasies required by Beancount, which are: Native support for a Decimal type . The addition of custom types for aggregators with the semantics of beancount.core.Inventory/Position/Amount . The ability to automatically generate a dynamic column rendering a line-by-line aggregation of another column (or set thereof), that is, a \"balance\" column . The ability to render a \" bottom line \" of aggregates at the end of the results table. Functions for splitting of aggregated columns , for amounts and inventories into multiple columns (e..g, \"123.00 USD\" becomes two columns: (123.00, \"USD\") to be processable in spreadsheets, and also for splitting debits and credits to their own columns. In particular, printing multiple lots accumulated in an account should be made natural from the SQL query, replacing the \"flatten\" feature by a more standard splitting off an array type. Moreover, broadening the focus with a new project definition will make a change to testing it thoroughly (the current one is still in a bit of a prototype stage and does not have nearly the amount of required tests), and also include data type validation (no more exceptions at runtime), by implementing a typed SQL translator. I'll document this elsewhere. This is a much bigger project, but I suspect with the broader scope, it will be easier to test and take on a life of its own. I'm preparing a design doc on this.","title":"Universal Lightweight Query Engine (ulque)"},{"location":"beancount_v3.html#api-rework","text":"I write a lot of custom scripts, and there are a number of things that bother me about today's Beancount API, which I want to radically improve: Consolidate symbols under \"bn\". The internal API calls for importing the symbols from each package separately, but now that I'll have split off the ingestion and reporting code, all of the public API, or at least the majority of the commonly used objects in the core should be available from a single package, a bit like numpy: import beancount as bn \u2026 bn.Inventory(...) bn.Amount(...) bn.Transaction(...) # etc. I'd like for \"bn\" to become the de-facto two-letter import on top of which we write all the scripts. Default values in constructors. The namedtuple containers are mighty fine, but their constructors never had optional arguments, and it's always a bit of a dance to create those containers with a ton of \"None\" options. I never liked it. We'll make this tidy in the next iteration. No API documentation. While there is a substantial amount of documentation around the project, there is no documentation showing people how to use the Python API, e.g. how to accumulate balances, how to create and use a realization tree, how to pull information out of an accumulated inventory object, etc. I think that documenting some of the most common operations will go a long way towards empowering people to make the most out of Beancount. Some of these operations include: Accumulating lots of an inventory and printing them. Converting to market value, and making corresponding account adjustments. \u2026. add more \u2026 Exposed, usable booking. Booking will be a simple loop that can be invoked from Python with an entry and some accumulated state. Moreover, the Inventory object should begin to implement some of the lower-level operations required for booking, such that iterating over a set of postings and doing e.g., average booking, can be done via method calls on an Inventory object. Inventory should take a more prominent place in the API. Data types. Well defined data types should be provided for all objects to make liberal use of the typing module over all new code. Maybe create a module called \"bn.types\" but they should be available directly from \"bn.*\" so that there is a single short-named import. Terminology. I'd like to stop using \"entries\" and consolidate over the name \"directives\" in Vnext. Realization. I've been using a collections.defaultdict(Inventory) and a \"realization\" interchangeably. Both of these are mappings from an account name (or some other key) to an Inventory state object. I'd like to unify both of these constructs into the realization and make it into a commonly used object, with some helper methods.","title":"API Rework"},{"location":"beancount_v3.html#parser-rewrite","text":"Since we will now depend on C++, the parser will get to be rewritten. Worry not: the input syntax will remain the same or at least compatible with the existing v2 parser. What will change is: Unicode UTF-8 support. The lexer will get rewritten with RE/flex instead of GNU flex. This scanner generator supports Unicode natively and any of the input tokens will support UTF-8 syntax. This should include account names, an oft-requested feature. Flags. The current scanner limits our ability to support any flag and supports only a small list of them. I think the list has proved sufficient for use, but since I'll be putting some work into a new scanner I'm hoping to clean up that story and support a broader, better defined subset of single-letter flags for transactions. Time. The parser will parse and provide a time field, in addition to the date. The time may be used as an extra key in sorting directives. The details for this are yet to be determined, but this is requested often enough at the very minimum the parser will output it as metadata, and at best, it may become a first-class feature. Caching. The pickle cache will be removed. Until very recently , there weren't great options for disabling it (env vars) and I'd rather remove the only two environment variables that Beancount honors as a side-effect. Since the C++ code should be fast enough, hopefully a cache will not be needed. Tags & links. In practice, those two features occupy a very similar role as that of metadata (used to filter transactions). I'm contemplating unseating the special place taken by tags and links in the favor of turning those into metadata; the input syntax would not be removed, but instead the values would be merged into the metadata fields. I'm not 100% sure yet about doing this and open for discussion. Furthermore, the parser should be made to accept #tag and ^link where metadata is declared today, which would be convenient syntax. Finally, users have expressed a desire for tags on postings. We should contemplate that. Plugins configuration as protos. The options for the various plugins have been loosely defined as eval'ed Python code. This is pretty loose and doesn't provide a great opportunity for plugins to do validation nor document their expected inputs. I'd like to formalize plugin configuration syntax a bit, by supporting text-formatted protos in the input syntax (for a message type which would be provided by the plugins themselves). Parser in C++. The parser will be rewritten in C++. In the process of writing Vnext, I'll try to maintain a single grammar for both for as long as possible by calling out to a C++ driver interface, which will have two distinct implementations: one for the V2 version calling into Python, and one for the Vnext parser generating protos. In the process I may be porting the lexer and grammar Python implementation to C, as discussed in this ticket . Better includes. Current includes fail to recognize options that aren't in the top-level file. This caused many surprises in the past and should be fixed. At the minimum, an error should be raised.","title":"Parser Rewrite"},{"location":"beancount_v3.html#code-quality-improvements","text":"Rename \"augmentation\" and \"reduction\" to \"opening\" and \"closing\" everywhere. This is just more common terminology and will be more familiar and understandable to people outside of our context. Type annotations. The use of mypy or pytype with type annotations in Python 3 is by now a very common sight, and works quite well. As part of Vnext, all of the core libraries will be modified to include type annotations and the build should be running pytype automatically. I'll need to add this to our Bazel rules (Google doesn't currently provide external support for this). While doing this, I may relax some of the Args/Returns documentation convention, because in many cases (but not all) the type annotations are sufficient to get a good interpretation of a function's API. PyLint in build. Similarly, the linter should be run as an integral part of the build. I'd like to find a way to selectively and explicitly have to disable it during development, but otherwise be set up such that lint errors would be equivalent to build failures. Flexible constructors for Python API. The types generated by collections.namedtuple() or typing.NamedTuple don't offer flexible constructors with named parameters. I think all codes that create transaction objects today would benefit from having constructors with default values, and I'll be providing those to create corresponding proto objects.","title":"Code Quality Improvements"},{"location":"beancount_v3.html#tolerances-precision","text":"The story around how precision and tolerances are dealt with hasn't been great, for two reasons: Explicit tolerance option. I've tried to design the tolerance (used for balancing transactions) to be automatic and automatically inferred from statistics from the numbers in the input. The results aren't great. In Vnext I aim to provide an explicit option for setting the tolerance per currency. Precision. There are various places where numbers get rendered in v2: the reports code, the SQL query, and debugging scripts, and the way precision is set hasn't been used consistently. The precision also needs to be explicitly settable by the user. Rounding. There is another quantity that's used during interpolation: the precision used to round calculated numbers. Moreover, there is a need to distinguish between the precision and tolerances for numbers when used as prices vs. when used as units (see here ). One way is to store the display context per currency PAIR, not per currency itself. The distinction between these quantities hasn't been documented well; I'll keep in mind to clearly annotate those codes in Vnext and add suitable docs for this. Mostly the precision will be a rendering concern and a quantity that will be relevant for the new universal SQL query tool. Some prior design documentation exists here .","title":"Tolerances & Precision"},{"location":"beancount_v3.html#core-improvements","text":"Some desiderata of new features are discussed below. These are all relevant to the core. Note that the changes should not interfere with current usage much, if at all. I expect that v2 users will be largely unaffected and won't have to change their ledger files.","title":"Core Improvements"},{"location":"beancount_v3.html#booking-rules-redesign","text":"Main document One of the current problems with booking is that entering an augmenting leg and a reducing leg have to be different by nature. The augmentation leg has to provide the cost basis via {...} syntax, and the reducing leg has to enter the price in the annotation and not in the cost basis. For example: 2021-02-24 * \"BOT +1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @143.75\" Assets:US:Ameritrade:Futures:Options 1 QNEG21C13100 {2875.00 USD} contract: 143.75 USD ... 2021-02-24 * \"SOLD -1 /NQH21:XCME 1/20 FEB 21 (EOM) /QNEG21:XCME 13100 CALL @149.00\" Assets:US:Ameritrade:Futures:Options -1 QNEG21C13100 {} @ 2980.00 USD contract: 149.00 USD ... Notice how the selling transaction has to be written down differently from the perspective of the user. The thing is, this makes it difficult from the perspective of the importer writer. It also ties the required syntax with the state of the inventory it's applied to, as it assumes something about this inventory. Moreover, this makes it difficult to write an importer that would handle a crossing of the absolute position, like this: 2021-02-19 * \"BOT +1 /NQH21:XCME @13593.00\" Assets:US:Ameritrade:Futures:Contracts 1 NQH21 {271860.00 USD} contract: 13593.00 USD Assets:US:Ameritrade:Futures:Margin -271860.00 USD Expenses:Financial:Commissions 2.25 USD Expenses:Financial:Fees 1.25 USD Assets:US:Ameritrade:Futures:Cash -3.50 USD 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -2 NQH21 {271815.00 USD} contract: 13590.75 USD Assets:US:Ameritrade:Futures:Margin 543630.00 USD Income:US:Ameritrade:Futures:PnL 45.00 USD Expenses:Financial:Commissions 4.50 USD Expenses:Financial:Fees 2.50 USD Assets:US:Ameritrade:Futures:Cash -52.00 USD The issue here is that we're crossing the flat line, in other words, we go from long one to short one. There are only two ways to do that properly right now: Disable booking and use the cost only, as per above. This is not great \u2014 booking is terribly useful. Track the position in your importer and separate the reducing and augmenting legs: 2021-02-19 * \"SOLD -2 /NQH21:XCME @13590.75\" Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {271815.00 USD} Both solutions aren't great. So I came up with something new: a complete reevaluation of how the syntax is to be interpreted. In fact, it's a simplification. What we can do is the following: use only the price annotation syntax for both augmentation and reduction and currency conversions, with a new booking rule \u2014 Match lots without cost basis in priority. If the lots have no cost basis, the weight of this posting is simply the converted amount, as before. If a match has been made against a lot with cost basis, the weight of this posting is that implied by the matched lots. Make the {...} used solely for disambiguating lots to match, and nothing else. If you have unambiguous matches, or a flexible booking strategy, e.g. FIFO, you'd pretty much never have to use the cost matching reduction. With this, the futures transaction above would simply use the @ price annotation syntax for both transactions. It would Make importers substantially simpler to write Supports futures naturally Be backward compatible with existing inputs for both currency conversions and investments. It is also more generally consistent and friendlier to Ledger users, without sacrificing any of the tighter constraints Beancount provides. I think it's even simpler to think about. Furthermore, this: Assets:US:Ameritrade:Futures:Contracts -1 NQH21 {} @ 271815.00 USD would be interpreted as \"match this lot, but only those with a cost basis attached to them.\" One question that remains is to decide whether an augmentation \u2014 now written down simply with @ price annotation \u2014 would store the cost basis in the inventory or not. I think we could make this determination per-commodity, or per-account. This would impose a new constraint: a commodity (or \"in an account\") would always be stored with cost basis, or not.","title":"Booking Rules Redesign"},{"location":"beancount_v3.html#posting-vs-settlement-dates","text":"When you import a transaction between multiple accounts within a single ledger, e.g. a credit card payment from one's checking account, the dates at which the transaction posts in each account may differ. One side is called the \"transaction date\" or \"posting date\" and the other side the \"settlement date.\" Where the money lives in between is somewhere in limbo (well in practice there is no money at all, just differences in accounting between institutions, things are never reflected instantly). One of the major shortcomings of the current core code is that the ability to insert a single transaction with postings at different dates is missing. Users are recommended to select a single date and fudge the other one. Some prior discussion on this topic exists here . Unfortunately, this method makes it impossible to represent the precise posting history on at least one of the two accounts. A good solution needs to be provided in Vnext, because this is a very common problem and I'd like to provide a system that allows you to precisely mirror your actual account history. The automatic insertion of transfer accounts to hold the commodities can be implemented as a feature, and it should live in the core. One possible idea would be to allow optional posting dates, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD which would result in two transactions behind the scenes, like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD Equity:Transfer 2020-01-21 * \"ONLINE PAYMENT - THANK YOU\" \"\" Liabilities:US:Amex:BlueCash 2397.72 USD Equity:Transfer The lack of symmetry here raises the question of whether we should allow a transaction without a date or not: * \"ONLINE PAYMENT - THANK YOU\" \"\" 2020-01-19 Assets:US:BofA:Checking -2397.72 USD 2020-01-21 Liabilities:US:Amex:BlueCash 2397.72 USD I think we can figure this out and the first solution is very doable. Input Split Transactions Some users like to organize their inputs in different files, or in different sections that strictly contain all of an account's transactions in order. This is related in spirit to the posting and settlement dates problem: at the moment the user is required to choose one of the two locations to insert their transaction. This should not be necessary. We should provide a mechanism that would allow users to insert the halves of a transaction into two different locations in their file, and a robust merging mechanism that would ensure that the two related transactions have been matched and merged (so that no unmerged half remains) and otherwise report errors clearly. The two halves could look like this: 2020-01-19 * \"ONLINE PAYMENT - THANK YOU\" \"\" Assets:US:BofA:Checking -2397.72 USD \u2026 2020-01-21 * \"AMEX EPAYMENT ACH PMT; DEBIT\" Liabilities:US:Amex:BlueCash 2397.72 USD The matching could be done via explicit insertion of special links, or by heuristics to match all such related transactions (perhaps declaring valid account pairs, thresholding on date differences and exactly matching amounts). When impossible to match, an error should be raised. Those merged transactions should be checked for balancing. Note how each of the transactions has a differing date; this would integrate with the transfer account solution proposed in the previous section. I haven't designed something yet, but this should be easy to implement and should be provided as a core feature, since it's so closely related to the input syntax.","title":"Posting vs. Settlement Dates"},{"location":"beancount_v3.html#currency-accounts-instead-of-a-single-conversion","text":"The current implementation of multiple currency transactions relies on a special \"conversion transaction\" that is automatically inserted at reporting time (when closing the year) to account for the sum total of imbalances between currencies. The goal of this transaction is to ensure that if you just sum up all the postings in the book, the result is purely an empty inventory (and not some residual amount of profit or loss incurred during currency exchange across different rates \u2014 note that we're talking only of the @price syntax, not investments). This is a bit of a kludge (the transaction itself does not balance, it converts to zero amounts of a fictional currency in order to keep itself quietly passing the balance test). What's more, its actual value is dependent on a subset of filtered transactions being summed up so it's a reporting-level construct, see here . There exists a method for dealing with multiple currencies without compromising on the hermeticity of individual transactions, described online, here . Using that method, you can filter any subset of transactions and summing them up will cleanly cancel out all lots. You don't need to insert any extra weight to fix up the balance. Also, you can explicitly book profits against the accrued gains in the currency accounts and zero them out and take advantage of this when you report them (and track them over time). The downside is that any currency conversion would see extra postings being inserted, etc. 2020-06-02 * \"Bought document camera\" Expenses:Work:Conference 59.98 EUR @ USD Liabilities:CreditCard -87.54 USD Equity:CurrencyAccounts:EUR -59.98 EUR Equity:CurrencyAccounts:USD 87.54 USD The problem is that it's a pain to use this method manually, it requires too much extra input. It's possible to have Beancount do that for us behind the scenes, completely automatically. I coded a proof-of-concept implementation here , but it's incomplete . In Vnext: The insertion of the kludgey conversions transactions should be removed. The currency accounts should become the norm. The fact that the two streams of directives will be very clearly separated should help, by distinguishing even more clearly between the parsing representation and the fully booked one, which will show these extra legs on transactions The prototype should be completed and issues fixed completely (not that much work involved).","title":"Currency Accounts instead of a Single Conversion"},{"location":"beancount_v3.html#strict-payees","text":"I'm not sure if this makes sense yet, but I'd like to clean up the mess that payee strings are today. Payees are free-form, and if the user does not take care to clean them up\u2014and I'm one of those who doesn't\u2014the memos from imported sources are messy. It could be interesting to create a new directive to declare payee names ahead of time and an optional model that would require payees to be found in the list of declared payee names. Payees would have to have open and close dates, dates which would define the valid duration of the relationship with that payee (thereby adding more error verification capability).","title":"Strict Payees"},{"location":"beancount_v3.html#price-inference-from-database","text":"Interpolation from price database. One of the oft-requested features is the ability to automatically interpolate prices from the internal price database history. I think that this should be doable unambiguously and deterministically and added to the interpolation algorithm. Price validation. Since a lot of the conversions at price (i.e., using \"@\") are inferred by leaving out one number, we should validate that the effective price is within some tolerance of a pre-existing price point near the date. This would provide yet another level of checking.","title":"Price Inference from Database"},{"location":"beancount_v3.html#quantizing-operators","text":"Another useful addition to the syntax would be operators that automatically quantize their result to a precision that depends on the particular target currency. For example, 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 / 1.19 EUR Expenses:Food:Taxes 2.13 / 1.19 * 0.19 EUR ; for example to calculate tax Assets:Cash That would become: 1970-01-01 * \"coffee\" Expenses:Food:Net 1.789915966386555 EUR Expenses:Food:Taxes 0.340084033613445 EUR Assets:Cash If instead an operator like this were provided, it would fix the issue: 1970-01-01 * \"coffee\" Expenses:Food:Net 2.13 /. 1.19 EUR Expenses:Food:Taxes (2.13 / 1.19 * 0.19). EUR Assets:Cash Or somesuch. Or maybe we'll want to add an option such that every evaluation of an arithmetic expression is automatically quantized as such.","title":"Quantizing Operators"},{"location":"beancount_v3.html#constraints-system-budgeting","text":"Beancount does not support budgeting constraints explicitly, but I think it would be possible to extend the balance assertion semantics to cover this. The current balance assertions check (a) a single commodity, and (b) that the amount is precisely equal to an expected one. Balance assertions should be extended to support inequalities, e.g., 2020-06-02 balance Liabilities:CreditCard > 1000.00 USD and perhaps we could check for the total inventory like this 2020-06-02 balanceall Assets:Cash 200.00 USD, 300.00 CAD I'd be curious to hear what would work best from users who do budgeting and design a minimalistic expression language to support that use case (though I'd try to keep it as simple as possible to avoid feature creep). Also, if the syntax is getting changed, adding a syntax that allows checking multiple currencies at once, and possibly a complete assertion that checks there aren't other commodities in the account could also make sense.","title":"Constraints System & Budgeting"},{"location":"beancount_v3.html#average-cost-booking","text":"Average cost booking has been discussed and a good solution sketched out a very long time ago. Vnext should sport that method natively; a lot of users want to have this feature for dealing with their tax-deferred accounts. It takes a bit of work to handle the precision of the various automated conversions right. The way it would work is by automatically merging all related lots of the same commodity on a reduction, and optionally on an augmentation. Some constraints may be required (e.g. only a single commodity in that account).","title":"Average Cost Booking"},{"location":"beancount_v3.html#trade-matching-reporting","text":"A few core tasks related to P/L and trading still need to be implemented. Trade list. A problem that I've really been wanting to solve for a very long time but never found the time for is to save crumbs from the booking process so that a correct list of trade pairs could be easily extracted from the list of directives. I wrote some notes here and here a long time ago. Essentially, for each booked reduction, insert a reference to the corresponding augmenting posting. I've prototyped this as metadata but it should be made something more official. A single linear scan can pick up these references, build a mapping and recover the (buy, sell) pairs to produce a table of trades. There's a precedent I once wrote in a plugin . Needless to say, producing a list of trades is a pretty basic function that Beancount does not provide out of the box today; it really should. Right now users write their own scripts. This needs to be supported out-of-the-box. Harmonize balance and gains validation. Checking that transactions balance and that income from gains balance with a transaction's prices (the sellgains plugin) are done in completely separate places. Those two codes occupy similar roles, and should be implemented next to each other. Commissions in P/L. Properly counting profits & losses by taking off the fraction of buying commission of an original lot and the selling commission into account is not possible at the moment. I think it could be done with a plugin that moves some of the (computed) income leg into a separate negative income account to do this properly for reporting purposes.","title":"Trade Matching & Reporting"},{"location":"beancount_v3.html#self-reductions","text":"Currently the application of reductions operates on the inventory preceding the transaction. This prevents the common case of self-reductions, and both I and some users have come across this problem before, e.g. this recent thread ( ticket ). This comes off as unintuitive to some users and ought to have a better solution than requiring splitting of transactions. Since we're rewriting the booking code entirely in Vnext, contemplate a new definition that would provide a well-defined behavior in this case. I remember from prior experiments attempting to implement this that it wasn't a trivial thing to define. Revisit. This would be a nice improvement.","title":"Self-Reductions"},{"location":"beancount_v3.html#stock-splits","text":"Some discussion and perhaps a strategy for handling stock splits should be devised in Vnext. Right now, Beancount ignores the issue. At the minimum this could be just adding the information to the price database. See this document for more details.","title":"Stock Splits"},{"location":"beancount_v3.html#multipliers","text":"Options have a standard contract size of 100. Futures have a contract size that depends on the particular instrument (e.g., /NQ with a multiplier of 20). I've been handling this for options by multiplying the units by 100, and for futures by multiplying the contract size by the per-contract multipliers (ditto for options on futures). I do this in the importers. For options, it works and it isn't too bad (e.g. positions of -300 instead of -3), but for futures, it's ugly. The result is not representative of the actual transaction. I'd like to add a per-currency multiplier, as well as a global dictionary of regexps-to-multiplier to apply, and for this to be applied everywhere consistently. One challenge is that everywhere there's a cost or price calculation, this has to be applied. In the current version, those are just multiplications so in many parts of the codebase these haven't been wrapped up in a function that could easily be modified. This needs to happen in a big rewrite \u2014 this is the opportunity to do this. Example here.","title":"Multipliers"},{"location":"beancount_v3.html#returns-calculations","text":"If you look at investment brokers out there, no one calculates returns correctly. Brokers provide one of two features: No cash transfers. Total value of account today vs. total value of account at some point in the past (i.e., account inception or beginning of the year). This isn't very useful because they never account for the addition or removal of cash to the account. For example, say you open an account with $100,000 and invest, and mid-year add another $20,000, say the original investments are now worth $95,000, the report would show a gain of $15,000, whereas you really incurred a loss. Better brokers like Vanguard will show a plot that includes two overlaid bars, one with cash added and profit overlaid, like this: Lack of interest or dividends. Other brokers will report P/L over time from the investments, but they fail to account for actual interest or dividends received (they only look at the price of the underlying) so that's not useful for bonds or for stocks with significant dividends, or when grouping them, they fail to account for the addition of new positions over time. Counterfactual performance. Finally, all of them fail to compare your actual annualized performance with that of a benchmark portfolio with equivalent cash infusions. For example, instead of your actual investments made, compare with the performance you would have obtained if you had invested in some standardized portfolio of investments over that particular time period, given the actual historical prices of those instruments. Ideally one should be able to define any alternative portfolio to compare against using their particular cash transfers. More fancy analyses aren't even contemplated, e.g., what would have been the impact of changing my rebalancing strategy (or actually implementing a more strict one)? There are well known methods for both time-based and value-based returns reporting. The right thing to do is to extract a time-series of cash flows and compute the annualized or IRR returns, or value-weights. I started this work at some point and ran against some difficulties and eventually removed it. The results remain here. I'd really love to build this, and eventually perhaps this could grow into its own project, with associated support functions in the Beancount core. This will possibly be a project of its own, but this requires similar support for enumerating instruments and price sources as that which is needed for fetching prices, as well as functions for isolating cash flows for specific subsets of accounts; these should probably live in the core. UPDATE September 2020: This has mostly been implemented. See this document for details.","title":"Returns Calculations"},{"location":"beancount_v3.html#unsigned-debits-and-credits","text":"A useful idea that's nearly trivial to implement is to allow users to input all positive unit numbers and to automatically flip the signs on input, and to output them all as positive numbers as well splitting them up between Debit and Credit columns. This would make Beancount's rendering a lot easier to grok for people with an existing background in accounting. This feature will introduce no complexity, easy to add for free. See TODO here .","title":"Unsigned Debits and Credits"},{"location":"beancount_v3.html#holdings","text":"One of the libraries I built at some point was this notion of a \"holding\", here . At the time I wasn't sure if that data would contain much more than what's in a posting, but as it turns out, representing holdings as just the posting is good enough, all you need is the market value, and compute the total values from the units is trivial. In fact, I've haven't been using that code for years, I've been using the export script instead, which writes out a table that gets uploaded to a Google Sheets doc. This proves to me aggregating the positions in an Inventory is plenty sufficient in practice, along with a mapping of latest market prices. I'm going to delete that code. It's only been used in the reports code anyway, which will be removed anyway, and in the experimental \" unrealized gains \" plug, which was only a proof-of-concept that convinced me booking such gains as transactions is not a good idea and which will get removed and live in a separate experimental repository anyway.","title":"Holdings"},{"location":"beancount_v3.html#tooling-for-debugging","text":"Context should recover erroneous transactions. One of the major annoyances of error recovery is that if a transaction involves some types of errors, the postings aren't produced in the stream of directives. This problem is related to the lack of clarity between the merely parsed data structure and the fully resolved one. In particular, this means that the \"bean-doctor context\" debugging command often cannot provide useful context around a failing transaction. This really needs to be fixed, to improve debuggability. Document debugging tools. In general, I should write a better exposition of how to use the various transaction debugging tools; a lot of the questions on the mailing-list would disappear if users knew better how to leverage those. Interactive context in Emacs. If the performance allows it, we could build an Emacs mode which renders the context around a partially written transaction, including inventories of the affected accounts before and after the transaction, as well as interpolated values, to a different buffer updated interactively. This would make it much more fun to input data and provide immediate feedback about the newly inserted transaction.","title":"Tooling for Debugging"},{"location":"beancount_v3.html#documentation-improvements","text":"Remove dependency on furius.ca. The current Google Docs based documentation links to other documents via a global redirect (the definition is found here ). While it does not happen often that my web server goes down (perhaps a few times per year), when it does it takes a few days to rectify the situation. That server is hosted graciously in the company of some friends of mine. Kirill has proved that it would be possible to replace all the links to redirects on github, that would look like this: beancount.github.io/ instead of furius.ca/beancount/doc/ . In order to do this, I'll need to run a script using the Docs API on all the Google Docs to change them automatically.","title":"Documentation Improvements"},{"location":"beancount_v3.html#conclusion","text":"There are other items in the TODO file . These are just the main, big issues that I think matter the most and I'd like to address them in a Vnext rewrite. Development branches will look like this: v2 : Current master will be branched to \"v2\", which will track the stable current version. That branch will build with both the current setup.py system and Bazel. Fixes will be implemented on that branch where possible, and merged to Vnext. master : Current master will become Vnext. Only the Bazel build will be supported on that branch. Any comments appreciated.","title":"Conclusion"},{"location":"beancount_v3.html#appendix","text":"More core ideas for Vnext that came about during discussions after the fact.","title":"Appendix"},{"location":"beancount_v3.html#customizable-booking","text":"For transfer lots with cost basis\u2026 an idea would be to create a new kind of hook, one that is registered from a plugin, e.g. a callback of yours invoked by the booking code itself, and whose results applied to a transaction are immediately reflected on the state of the affected inventories. Maybe this is the right place to provide custom algorithms so that their impact is affecting the subsequent inventories correctly and immediately. Now, imagine generalizing this further to provide and implement all of the current booking mechanisms that are currently built in the core. Call this \"customizable booking.\" ( thread ).","title":"Customizable Booking"},{"location":"beancount_v3.html#ugly-little-things","text":"print_entry() uses buffering that makes it impossible to use regular print() interspersed with the regular stdout without providing file= option. Fix this, make this regular instead, that's just annoying, just print to regular stdout. The default format for __str__ for inventories puts () around the rendering. When there's a single position, that looks like a negative number. That's dumb. Use {} instead, or something else. Add a flag to bean-check to make it run --auto plugins by default. This is great for imported files, which may not have a complete ledger to feed in.","title":"Ugly Little Things"},{"location":"beancount_v3.html#incremental-booking-beancount-server-emacs-companion","text":"In order to make recomputation fast, the idea of creating a standalone \"Beancount server\" starts to make sense. The expensive part of the Beancout calculations on a large file is the booking and interpolation. The key to making things fast is thus to keep all the original unprocessed transactions in memory along with the booked and interpolated ones, and on a change, reparse the modified files and scan all the transactions, updating only the ones whose accounts have been affected. This could be problematic in theory: some plugins may rely on non-local effects in a way that affects what they output. I believe in practice it would work 99% of the time. But I think it may be worth a prototype. On the other hand, Vnext may turn out to be fast enough recomputing everything from scratch every single time (my own file went from 4s -> 0.3ms for the parsing stage of the largest file), so maybe this is irrelevant overall. Such a server would be a perfect companion to a running Emacs. We could build an Emacs mode which communicates with the server.","title":"Incremental Booking/ Beancount Server / Emacs Companion"},{"location":"beancount_v3.html#tags-links-merge-with-metadata","text":"TODO(blais): Add colon syntax","title":"Tags & Links Merge with MetaData"},{"location":"beancount_v3_dependencies.html","text":"Beancount C++ version: Dependencies \uf0c1 Martin Blais , June 2020 Beancount is going to get rewritten in C++, here is the set of dependencies I've tested and that I'm comfortable maintaining in the long run: Base environment \uf0c1 Bazel build ( https://github.com/bazelbuild/bazel ): The Google build system is the most stable approach to build that I know of, much better than SCons and certainly much better than CMake. It allows you to pin down a specific set of dependencies by explicitly referencing other repositories and git repos at specific commit revisions (including non-released ones), and the sometimes annoying constraints it imposes results in hermetically reproducible builds like no other build system can do. This minimizes surprises and hopefully the number of platform-dependent portability issues. It also minimizes the amount of pre-installed packages we assume your system has (e.g. it'll download and compile its own Bison, for example). It runs fast, computes the minimal set of tests and targets to rebuild, and is highly configurable. The downside of choosing Bazel is the same of other Google-issued open source projects: the original version of that product is internal and as a result there are a lot of strange idiosyncrasies to deal with (e.g. //external, the @bazel_tools repo, etc.), many of which are poorly documented outside the company and with a good number of unresolved tickets. However, at this stage I've already managed to create a working build with most of the dependencies described in this section. C++14 with GCC and Clang/LLVM : Both compilers will be supported. Clang provides a much better front-end and stdlib implementation but is a little slower to build. GCC is more commonly present in the wild but the error messages are\u2026 well, we all got used to this I suppose. Note that despite requiring C++14, I will refrain from using exotic features of the language (including classes). There may be questions about Windows support. Abseil-Cpp base library ( https://github.com/abseil/abseil-cpp ): The base library of functions is issued from Google's own gigantic codebase and has been battle-hardened and tested like no other\u2014this is what the Google products run on. This provides a most stable API to work with (it's unlikely to change much given how much code depends on it), one which complements stdc++ well, and whose existing contact surfaces are bound to remain pretty static. It's simpler and more stable than Boost, and doesn't offer a myriad of libraries we're not going to need anyway (plus, I love Titus' approach to C++).. This fills in a lot of the basic string manipulation functions you get for free in Python but crave in C++ (e.g. absl::StrCat). Google Test ( https://github.com/google/googletest ): This is the widely used C++ testing framework I'm already familiar with, which supports matchers and mocking. Data representation \uf0c1 Protocol Buffers ( https://github.com/protocolbuffers/protobuf ): I will maintain a functional style in this C++ rewrite and I need a replacement for Python's nametuples to represent directives. This means creating a lot of simple naked structured data that will need to be created dynamically from within tests (there's a good text-format parser) and also serialized to disk as the boundary between the core and query language will become a file of protobuf messages. Protobuf provides a good hierarchical data structure with repeated fields that is supported in many languages (this opens the door potentially to plugins written in e.g., Go), and it's possible to provide Python bindings for them. It will also become the interface between the Beancount's core and the input to the query language. We will be using proto3 with version >=3.12 in order to have support for optional presence fields (null values). Riegeli (https://github.com/google/riegeli) : An efficient and compressed binary format for storing sequences of protobuf messages to files. I think the Beancount core will output this; it's compact and reads fast. It's also another Googlething that ought to receive more attention than it does and supports both C++ and Python and protobufs. mpdecimal ( https://www.bytereef.org/mpdecimal/ ) : This is the same C-level library used by Python's implementation of Decimal numbers. Using this library will allow to easily operate between the C++ core and Python's runtime. I need to represent decimal numbers in C++ memory with minimal functionality and reasonably small range (BigNum classes are typically more than what we need). We don't need much of the scope for decimal\u2026. basic arithmetic operations + quantizing, mainly. There are other libraries out there: GMP , decNumber . There is some information on this thread: ( https://stackoverflow.com/questions/14096026/c-decimal-data-types . For on-disk representation, I will need a protobuf message definition for those, and I'm thinking of defining a union of string (nice to read but lots of conversions from string to decimal) with some more efficient exponent + mantissa decimal equivalent. Parser \uf0c1 RE/flex lexer ( https://github.com/Genivia/RE-flex ): This modern regexp-based scanner generator supports Unicode natively and is very fast and well documented. It provides a great alternative to the aging GNU flex which made it difficult to support non-ASCII characters outside of string literals (i.e.., for account names). I've had success using it on other projects. Many users want account names in their home language; this will make it easy to provide a UTF-8 parser for the entire file. GNU Bison ( https://git.savannah.gnu.org/git/bison.git ): We will stick with GNU Bison, but instead use the C++ complete modes it supports. I'm hesitating continuing with this parser generator as it's showing its age but it's pretty stable and I can't quite justify the extra work to upgrade to ANTLR. We will have to pull some tricks to support the same grammar for generating C code for v2 and C++ code for the next version; the parser code could be provided with a dispatch table of functions, which would be static C functions in v2, and methods in a C++ version. Some of the generation parameters (% directives) will be different (see here for an example). I nternational Components for Unicode (ICU) ( https://github.com/unicode-org/icu.git ): This is the standard library to depend on for Unicode support. Our C++ will not use std::wstring/std::wchar, but rather regular std::string and function calls to this library where necessary. Python \uf0c1 Python3 (https://www.python.org/) : Not much to say. I will keep using the latest version. Python is a tank of an extension language and no plans to change that. pybind11 ( https://github.com/pybind/pybind11 ): I want to provide a Python API nearly identical to the current one in Beancount, or better (which means simpler). One of the requirements I've had is to make it cheap to pass a list of protobuf objects for the directives to a Python callback, without copying (serializing and deserializing) between C++ and Python\u2014for plugins. I've investigated multiple libraries to interoperate between Python and C++: Cython, CLIF, SWIG, etc. and serialization is a problem (see this partial solution ). The one that seems to have the most momentum at the moment is pybind11, a pure header library which is an evolution from Boost::Python, that offers the most control over the generated API. It also works well with protocol buffer targets built with fast_cpp_protos: only pointers are passed through, so plugins passing in and out the full list of directives should be possible. I also happen to be familiar with Boost::Python having used it 20 years ago, it's really quite similar actually (but does away with the rest of Boost). Type annotations , PyType (or MyPy ?): I've already been compliant to a custom configuration of PyLint for Python but the codebase does not use the increasingly ubiquitous type annotations . In the rewritten subset of the code that will remain, I'd like to have all functions annotated and to replace the sometimes redundant Args/Returns docstrings with a more free-form documentation (the types may be sufficient to avoid the formalism of Args/Returns blocks). I'll have to see how this affects the auto-generated docs . An important addition is that I want to start not only annotating, but running one of the type checkers automatically as part of the build. I'm already familiar with Google's pytype, but perhaps mypy is a good alternative. In any case, the only hurdle for that is to craft Bazel rules that invoke these automatically across the entire codebase, as part of py_library() and py_binary() rules. I'll also attempt to make pylint run in the same way (as part of the build) with a custom flag to disable it during development, instead of having a separate lint target. Subpar ( https://github.com/google/subpar ): It's not clear to me yet how to perform a pip-compatible setup.py for a Bazel build, but surely we can find a way to build wheels for PyPI using the binaries built by Bazel. For packaging a self-contained binary of Python + extensions, the \"subpar\" Bazel rules is supposed to handle that. However, at the moment it does not support C extensions .","title":"Dependencies"},{"location":"beancount_v3_dependencies.html#beancount-c-version-dependencies","text":"Martin Blais , June 2020 Beancount is going to get rewritten in C++, here is the set of dependencies I've tested and that I'm comfortable maintaining in the long run:","title":"Beancount C++ version: Dependencies"},{"location":"beancount_v3_dependencies.html#base-environment","text":"Bazel build ( https://github.com/bazelbuild/bazel ): The Google build system is the most stable approach to build that I know of, much better than SCons and certainly much better than CMake. It allows you to pin down a specific set of dependencies by explicitly referencing other repositories and git repos at specific commit revisions (including non-released ones), and the sometimes annoying constraints it imposes results in hermetically reproducible builds like no other build system can do. This minimizes surprises and hopefully the number of platform-dependent portability issues. It also minimizes the amount of pre-installed packages we assume your system has (e.g. it'll download and compile its own Bison, for example). It runs fast, computes the minimal set of tests and targets to rebuild, and is highly configurable. The downside of choosing Bazel is the same of other Google-issued open source projects: the original version of that product is internal and as a result there are a lot of strange idiosyncrasies to deal with (e.g. //external, the @bazel_tools repo, etc.), many of which are poorly documented outside the company and with a good number of unresolved tickets. However, at this stage I've already managed to create a working build with most of the dependencies described in this section. C++14 with GCC and Clang/LLVM : Both compilers will be supported. Clang provides a much better front-end and stdlib implementation but is a little slower to build. GCC is more commonly present in the wild but the error messages are\u2026 well, we all got used to this I suppose. Note that despite requiring C++14, I will refrain from using exotic features of the language (including classes). There may be questions about Windows support. Abseil-Cpp base library ( https://github.com/abseil/abseil-cpp ): The base library of functions is issued from Google's own gigantic codebase and has been battle-hardened and tested like no other\u2014this is what the Google products run on. This provides a most stable API to work with (it's unlikely to change much given how much code depends on it), one which complements stdc++ well, and whose existing contact surfaces are bound to remain pretty static. It's simpler and more stable than Boost, and doesn't offer a myriad of libraries we're not going to need anyway (plus, I love Titus' approach to C++).. This fills in a lot of the basic string manipulation functions you get for free in Python but crave in C++ (e.g. absl::StrCat). Google Test ( https://github.com/google/googletest ): This is the widely used C++ testing framework I'm already familiar with, which supports matchers and mocking.","title":"Base environment"},{"location":"beancount_v3_dependencies.html#data-representation","text":"Protocol Buffers ( https://github.com/protocolbuffers/protobuf ): I will maintain a functional style in this C++ rewrite and I need a replacement for Python's nametuples to represent directives. This means creating a lot of simple naked structured data that will need to be created dynamically from within tests (there's a good text-format parser) and also serialized to disk as the boundary between the core and query language will become a file of protobuf messages. Protobuf provides a good hierarchical data structure with repeated fields that is supported in many languages (this opens the door potentially to plugins written in e.g., Go), and it's possible to provide Python bindings for them. It will also become the interface between the Beancount's core and the input to the query language. We will be using proto3 with version >=3.12 in order to have support for optional presence fields (null values). Riegeli (https://github.com/google/riegeli) : An efficient and compressed binary format for storing sequences of protobuf messages to files. I think the Beancount core will output this; it's compact and reads fast. It's also another Googlething that ought to receive more attention than it does and supports both C++ and Python and protobufs. mpdecimal ( https://www.bytereef.org/mpdecimal/ ) : This is the same C-level library used by Python's implementation of Decimal numbers. Using this library will allow to easily operate between the C++ core and Python's runtime. I need to represent decimal numbers in C++ memory with minimal functionality and reasonably small range (BigNum classes are typically more than what we need). We don't need much of the scope for decimal\u2026. basic arithmetic operations + quantizing, mainly. There are other libraries out there: GMP , decNumber . There is some information on this thread: ( https://stackoverflow.com/questions/14096026/c-decimal-data-types . For on-disk representation, I will need a protobuf message definition for those, and I'm thinking of defining a union of string (nice to read but lots of conversions from string to decimal) with some more efficient exponent + mantissa decimal equivalent.","title":"Data representation"},{"location":"beancount_v3_dependencies.html#parser","text":"RE/flex lexer ( https://github.com/Genivia/RE-flex ): This modern regexp-based scanner generator supports Unicode natively and is very fast and well documented. It provides a great alternative to the aging GNU flex which made it difficult to support non-ASCII characters outside of string literals (i.e.., for account names). I've had success using it on other projects. Many users want account names in their home language; this will make it easy to provide a UTF-8 parser for the entire file. GNU Bison ( https://git.savannah.gnu.org/git/bison.git ): We will stick with GNU Bison, but instead use the C++ complete modes it supports. I'm hesitating continuing with this parser generator as it's showing its age but it's pretty stable and I can't quite justify the extra work to upgrade to ANTLR. We will have to pull some tricks to support the same grammar for generating C code for v2 and C++ code for the next version; the parser code could be provided with a dispatch table of functions, which would be static C functions in v2, and methods in a C++ version. Some of the generation parameters (% directives) will be different (see here for an example). I nternational Components for Unicode (ICU) ( https://github.com/unicode-org/icu.git ): This is the standard library to depend on for Unicode support. Our C++ will not use std::wstring/std::wchar, but rather regular std::string and function calls to this library where necessary.","title":"Parser"},{"location":"beancount_v3_dependencies.html#python","text":"Python3 (https://www.python.org/) : Not much to say. I will keep using the latest version. Python is a tank of an extension language and no plans to change that. pybind11 ( https://github.com/pybind/pybind11 ): I want to provide a Python API nearly identical to the current one in Beancount, or better (which means simpler). One of the requirements I've had is to make it cheap to pass a list of protobuf objects for the directives to a Python callback, without copying (serializing and deserializing) between C++ and Python\u2014for plugins. I've investigated multiple libraries to interoperate between Python and C++: Cython, CLIF, SWIG, etc. and serialization is a problem (see this partial solution ). The one that seems to have the most momentum at the moment is pybind11, a pure header library which is an evolution from Boost::Python, that offers the most control over the generated API. It also works well with protocol buffer targets built with fast_cpp_protos: only pointers are passed through, so plugins passing in and out the full list of directives should be possible. I also happen to be familiar with Boost::Python having used it 20 years ago, it's really quite similar actually (but does away with the rest of Boost). Type annotations , PyType (or MyPy ?): I've already been compliant to a custom configuration of PyLint for Python but the codebase does not use the increasingly ubiquitous type annotations . In the rewritten subset of the code that will remain, I'd like to have all functions annotated and to replace the sometimes redundant Args/Returns docstrings with a more free-form documentation (the types may be sufficient to avoid the formalism of Args/Returns blocks). I'll have to see how this affects the auto-generated docs . An important addition is that I want to start not only annotating, but running one of the type checkers automatically as part of the build. I'm already familiar with Google's pytype, but perhaps mypy is a good alternative. In any case, the only hurdle for that is to craft Bazel rules that invoke these automatically across the entire codebase, as part of py_library() and py_binary() rules. I'll also attempt to make pylint run in the same way (as part of the build) with a custom flag to disable it during development, instead of having a separate lint target. Subpar ( https://github.com/google/subpar ): It's not clear to me yet how to perform a pip-compatible setup.py for a Bazel build, but surely we can find a way to build wheels for PyPI using the binaries built by Bazel. For packaging a self-contained binary of Python + extensions, the \"subpar\" Bazel rules is supposed to handle that. However, at the moment it does not support C extensions .","title":"Python"},{"location":"beangulp.html","text":"Beangulp \uf0c1 Martin Blais , Jan 2021 Importing data for Beancount has been supported originally by the LedgerHub project along with a library of importers, then reintegrated as a pure framework library (with some examples) in the Beancount repo as beancount.ingest, and now we're splitting up that repository again and will factor out the importing framework to another repo for easier maintenance and evolution. This document lays out some of the changes desired in this new version. New Repo \uf0c1 The new repository will be located at http://github.com/beancount/beangulp Beangulp will target compatibility with the latest beancount release from the v3 branch only. Beancount v3 is expected to evolve rapidly at the beginning, thus, to make the life of early adopters less painful, careful use of version number increments and versioned dependencies should be employed. Ideally, Beangulp should depend on the current minor version of Beancount only, for example, if Beancount 3.0.0 is released, Beangulp should declare install_requires: beancount >3.0, <3.1 See setuptools doc and PEP-440 . Status \uf0c1 As of Jan 2022, most of this proposal is complete and implemented. (Thanks to Daniele Nicolodi for doing most of the work.) Changes \uf0c1 Library Only \uf0c1 The current implementation allows one to use the bean-identify, bean-extract and bean-file tools on a \"config file\" which is evaluated Python, or create a script and call a single endpoint that will implement the subcommands. In order to make this work, a really convoluted trampoline is used to bounce the evaluation to the same code. I'll admit it trumps even me who wrote it whenever I have to go in there and edit that code. It also makes it inconvenient to let users add custom before/after customizations to their import process. The next version will support only (2). The bean-identify, bean-extract, bean-file programs will all be removed. The user will be expected to write their own script. One File \uf0c1 Right now, each importer consists of two files: the implementation file and an associated test file, e.g. soandso_bank.py soandso_bank_test.py The test file is small and usually calls out to a library function to find model files and expected outputs. Since there's hardly any real test code, we'd like to be able to have a single Python file that contains its test invocation. A new endpoint will be added to define the tests in the importer implementation. Self-Running \uf0c1 Moreover, that new function should also be the same one as that which is used to put together the configuration script. In other words, an importer's main() function should be equivalent to an importer's invocation with a single configured importer, one that is configured with the configuration used for testing. This will allow users to just \"run the importer\" on a specific set of files without having to define a configuration, by using the test configuration, like this: soandso_bank.py extract ~/Downloads/transactions.csv Having this option makes it super convenient for people to share the one file and test it out immediately, without having to create a configuration nor a main program. I don\u2019t think there is an easy clean way to implement this other than having something like if __name__ == \u2018__main__\u2019: main = Ingest([SoAndSoBankImporter()]) main() in the importer definition file. This should work right now without changes. Although, it is often the case now that importers require a bit of configuration to work (I am not sure, I don\u2019t use any of the importers distributed with Beancount or widely used). Test S ubcommand & Generate \uf0c1 Since the importers are runnable and define their test cases for pytest to run over, we should also add a subcommand \"test\" to complete \"identify\", \"extract\" and \"file\". That venue is also a great place to replace the --generate option which required ugly injection of the pytestconfig, and instead, implement it ourselves and add a second subcommand: \"genexpect\" to generate the expected file for testing. The interface becomes: soandso_bank.py identify ~/Downloads/ soandso_bank.py extract ~/Downloads/ soandso_bank.py file ~/Downloads/ ~/documents soandso_bank.py test ~/documents/testdocs soandso_bank.py generate ~/Downloads/transactions.csv This way we can remove the pytestconfig dep injection and also simplify the logic of unit tests, which had to handle both the check vs. generate scenarios. This should result in simpler code. One Expected File \uf0c1 Expected outputs from an importer are stored in multiple files with suffixes .extract, .file_name, .file_date, etc. If we had all the output in one file, the \"genexpect\" subcommand could generate everything to stdout. This is convenient. myimporter.py myimporter.beancount Leverage the Beancount syntax to store the expected values for \"file_name()\", \"file_date()\" and other outputs. Store those in Event or Custom directives and have the testing code assert on them. The new contents of a test directory should be simple pairs of (a) original downloaded file and (b) expected output, containing the transactions but also all the other method outputs. Duplicates Identification \uf0c1 This has never really worked properly. I think if this was implemented separately based on each importer \u2014 in other words, letting each importer define how to identify duplicates, e.g., if a unique transaction ID can be assumed having been inserted as a link to disambiguate \u2014 we could do this a lot cleaner. It would be ideal if each importer could specify duplicate id detection, in the importer. It could call on a more general but less reliable method, and that code should live in Beangulp. CSV Utils \uf0c1 I have a lot of really convenient utilities for slicing and dicing CSV files from ugly CSV downloads. CSV downloads often are used to house multiple tables and types of data, and a higher-level of processing is often needed on top of these files. I have code like this spread all over. This deserves a nice library. What's more, I have a nice table processing library under the Baskets project, which hasn't been given the proper quality treatment yet. Clean up my little table library and merge it with all the CSV utils. Put this in beangulp, or even contemplate making this its own project, including a CSV importer. \"CSV world.\" Not sure this matters as much after discovering petl. We could depend on petl. Caching \uf0c1 When running conversion jobs on large files, it would be nice to have a cache and avoid running those more than once. The (small) converted data could be cached and loaded back up in order to avoid running the expensive conversion more than once. One difficulty is that the conversions required to be run depend on the importers configuration, and each importer is unaware of the other ones. All the command-line arguments and at least the head of the file contents should be hashed in. This library could be pretty independent from Beancount. API Changes \uf0c1 \"file_date()\" is not clear; \"get_filing_date()\" would be. The extra argument on extract() is irregular compared to all the other methods. Find a better way? I'm not 100% sure I like my little memoizing file wrapper (\"cache\") with cache. Replace it with a disk-only one. Automatic Insertion \uf0c1 A really convenient and easily built feature that the new code should have is the automatic insertions of the extracted output to an existing ledger, before the point of a special string token in the file. Make this part of the library, as an alternative for storing the output of the importer, e.g. $ ./myimport.py extract | bean-insert ledger.beancount Amex This could also be a flag to \"extract\" $ ./myimport.py extract -f ledger.beancount -F Amex","title":"Beangulp"},{"location":"beangulp.html#beangulp","text":"Martin Blais , Jan 2021 Importing data for Beancount has been supported originally by the LedgerHub project along with a library of importers, then reintegrated as a pure framework library (with some examples) in the Beancount repo as beancount.ingest, and now we're splitting up that repository again and will factor out the importing framework to another repo for easier maintenance and evolution. This document lays out some of the changes desired in this new version.","title":"Beangulp"},{"location":"beangulp.html#new-repo","text":"The new repository will be located at http://github.com/beancount/beangulp Beangulp will target compatibility with the latest beancount release from the v3 branch only. Beancount v3 is expected to evolve rapidly at the beginning, thus, to make the life of early adopters less painful, careful use of version number increments and versioned dependencies should be employed. Ideally, Beangulp should depend on the current minor version of Beancount only, for example, if Beancount 3.0.0 is released, Beangulp should declare install_requires: beancount >3.0, <3.1 See setuptools doc and PEP-440 .","title":"New Repo"},{"location":"beangulp.html#status","text":"As of Jan 2022, most of this proposal is complete and implemented. (Thanks to Daniele Nicolodi for doing most of the work.)","title":"Status"},{"location":"beangulp.html#changes","text":"","title":"Changes"},{"location":"beangulp.html#library-only","text":"The current implementation allows one to use the bean-identify, bean-extract and bean-file tools on a \"config file\" which is evaluated Python, or create a script and call a single endpoint that will implement the subcommands. In order to make this work, a really convoluted trampoline is used to bounce the evaluation to the same code. I'll admit it trumps even me who wrote it whenever I have to go in there and edit that code. It also makes it inconvenient to let users add custom before/after customizations to their import process. The next version will support only (2). The bean-identify, bean-extract, bean-file programs will all be removed. The user will be expected to write their own script.","title":"Library Only"},{"location":"beangulp.html#one-file","text":"Right now, each importer consists of two files: the implementation file and an associated test file, e.g. soandso_bank.py soandso_bank_test.py The test file is small and usually calls out to a library function to find model files and expected outputs. Since there's hardly any real test code, we'd like to be able to have a single Python file that contains its test invocation. A new endpoint will be added to define the tests in the importer implementation.","title":"One File"},{"location":"beangulp.html#self-running","text":"Moreover, that new function should also be the same one as that which is used to put together the configuration script. In other words, an importer's main() function should be equivalent to an importer's invocation with a single configured importer, one that is configured with the configuration used for testing. This will allow users to just \"run the importer\" on a specific set of files without having to define a configuration, by using the test configuration, like this: soandso_bank.py extract ~/Downloads/transactions.csv Having this option makes it super convenient for people to share the one file and test it out immediately, without having to create a configuration nor a main program. I don\u2019t think there is an easy clean way to implement this other than having something like if __name__ == \u2018__main__\u2019: main = Ingest([SoAndSoBankImporter()]) main() in the importer definition file. This should work right now without changes. Although, it is often the case now that importers require a bit of configuration to work (I am not sure, I don\u2019t use any of the importers distributed with Beancount or widely used).","title":"Self-Running"},{"location":"beangulp.html#test-subcommand-generate","text":"Since the importers are runnable and define their test cases for pytest to run over, we should also add a subcommand \"test\" to complete \"identify\", \"extract\" and \"file\". That venue is also a great place to replace the --generate option which required ugly injection of the pytestconfig, and instead, implement it ourselves and add a second subcommand: \"genexpect\" to generate the expected file for testing. The interface becomes: soandso_bank.py identify ~/Downloads/ soandso_bank.py extract ~/Downloads/ soandso_bank.py file ~/Downloads/ ~/documents soandso_bank.py test ~/documents/testdocs soandso_bank.py generate ~/Downloads/transactions.csv This way we can remove the pytestconfig dep injection and also simplify the logic of unit tests, which had to handle both the check vs. generate scenarios. This should result in simpler code.","title":"Test Subcommand & Generate"},{"location":"beangulp.html#one-expected-file","text":"Expected outputs from an importer are stored in multiple files with suffixes .extract, .file_name, .file_date, etc. If we had all the output in one file, the \"genexpect\" subcommand could generate everything to stdout. This is convenient. myimporter.py myimporter.beancount Leverage the Beancount syntax to store the expected values for \"file_name()\", \"file_date()\" and other outputs. Store those in Event or Custom directives and have the testing code assert on them. The new contents of a test directory should be simple pairs of (a) original downloaded file and (b) expected output, containing the transactions but also all the other method outputs.","title":"One Expected File"},{"location":"beangulp.html#duplicates-identification","text":"This has never really worked properly. I think if this was implemented separately based on each importer \u2014 in other words, letting each importer define how to identify duplicates, e.g., if a unique transaction ID can be assumed having been inserted as a link to disambiguate \u2014 we could do this a lot cleaner. It would be ideal if each importer could specify duplicate id detection, in the importer. It could call on a more general but less reliable method, and that code should live in Beangulp.","title":"Duplicates Identification"},{"location":"beangulp.html#csv-utils","text":"I have a lot of really convenient utilities for slicing and dicing CSV files from ugly CSV downloads. CSV downloads often are used to house multiple tables and types of data, and a higher-level of processing is often needed on top of these files. I have code like this spread all over. This deserves a nice library. What's more, I have a nice table processing library under the Baskets project, which hasn't been given the proper quality treatment yet. Clean up my little table library and merge it with all the CSV utils. Put this in beangulp, or even contemplate making this its own project, including a CSV importer. \"CSV world.\" Not sure this matters as much after discovering petl. We could depend on petl.","title":"CSV Utils"},{"location":"beangulp.html#caching","text":"When running conversion jobs on large files, it would be nice to have a cache and avoid running those more than once. The (small) converted data could be cached and loaded back up in order to avoid running the expensive conversion more than once. One difficulty is that the conversions required to be run depend on the importers configuration, and each importer is unaware of the other ones. All the command-line arguments and at least the head of the file contents should be hashed in. This library could be pretty independent from Beancount.","title":"Caching"},{"location":"beangulp.html#api-changes","text":"\"file_date()\" is not clear; \"get_filing_date()\" would be. The extra argument on extract() is irregular compared to all the other methods. Find a better way? I'm not 100% sure I like my little memoizing file wrapper (\"cache\") with cache. Replace it with a disk-only one.","title":"API Changes"},{"location":"beangulp.html#automatic-insertion","text":"A really convenient and easily built feature that the new code should have is the automatic insertions of the extracted output to an existing ledger, before the point of a special string token in the file. Make this part of the library, as an alternative for storing the output of the importer, e.g. $ ./myimport.py extract | bean-insert ledger.beancount Amex This could also be a flag to \"extract\" $ ./myimport.py extract -f ledger.beancount -F Amex","title":"Automatic Insertion"},{"location":"calculating_portolio_returns.html","text":"Calculating Portfolio Returns \uf0c1 Martin Blais , Sept 2020 http://furius.ca/beancount/doc/returns This document describes how to compute portfolio returns from a Beancount ledger. Motivation \uf0c1 You will be surprised to find that discount brokers typically do not provide accurate and complete returns calculations for your investments based on your specific cash flows. They tend to report other measures of performance: Change in value. The simplest they provide is a snapshot of the account value at the beginning and end of the period (or year). The problem with this method is that it does not reflect your infusions or removal of cash as such, nor your changes in positions. For example, if you had an account with $50,000 at the beginning of the year and you've added in $30,000 in August, reporting a difference of $37,000 at the end of December is just not useful (you'd have to mentally discount for new invested cash during the year, and what if you want to break it down and compare the returns from different instruments in the account?). Underlying performance. They might report the growth of the underlying asset in isolation, disregarding your specific positions. It's not very useful to say \"HDV grew by 8.2% over the last year\" if you invested only (or even mostly) in the second half of the year. What I'd like is to know how much my specific investments grew, given my specific changes in positions and timings. In other words, how did I do? No dividends. Another issue is that performance is typically only reporting capital appreciation due to the change in price of the investment. Ideally one would like to break down the performance between capital appreciation and dividends and returns for each of those components, as well as overall performance, so you can compare returns from stocks and bonds to each other. You can sometimes find out that information from the Yield field in the summary, but for uneven distributions, this won't help me find out what my actual returns from dividends were, over any period. Commissions and management expenses. Some brokers charge management expenses on a monthly basis, based on the value of the account. I would like to know what my portfolio return is net of those expenses. I want accurate returns reporting, based on my actual cash flows and changes of positions over time. If I maintain your investment information in a Beancount ledger, in theory it contains all the data I need in order to compute your true returns, based on the specific timings of my own savings (cash infusions) and which positions I held at which time. It's just not in the simplest format required to do it\u2014 Beancount transactions are much more flexible than one might want and a simpler series of cash flows needs to be extracted from it. This document explains how I finally did this from my own Ledger. And how we might generalize this to yours, based on some simple rules. Most of this text is dedicated to the pedestrian details of extracting the right data. The source code can be found here . In addition, a fair and honest comparison to other investments scenarios should be possible, based on those same cash flows. For instance, you should be able to produce data that looks like \"My investments in ZZZ have returned 8.2%, 1.1% of which were from dividends, and if I'd invested in a 60/40 portfolio of broad stocks and bonds it would have returned 7.2% instead.\" In other words, I want to assess my performance relative to a number of common alternatives. (Finally, note that if all you need is a snapshot of your current positions, that's already handled by the export script .) History \uf0c1 In 2014, I made a brief attempt to break down information from my ledger to do this. At the time I got bogged down in details when some of the time-series I was extracting weren't producing what looked like sensible results (some with outliers). I got too detailed too fast. Sometimes it's better to just get the whole job done and come back for the details. I hadn't logged enough debugging information and I didn't have enough confidence in its output to use it. I never actually finished the work at the time, and eventually moved the scripts to experiments and shrugged. \"Later.\" In August 2020, I sat down to do this again, this time with a less ambitious goal of just getting a good approximation and producing lots of debug output, boiling down extraction to pull out just the cash flows and to get the job complete over all my accounts, even if it meant making some adjustments to my input file. It turned out to be the right decision: I managed to complete the task, and this document presents my journey, the method, its assumptions, quirks, and some results. Overview of Method \uf0c1 The method I'll use is to extract cash flow information for each investment. What I call \" an investment \" in this context is a kind of financial instrument , e.g., shares of \"VTI\" for the Vanguard Total Stock Market ETF, invested in a particular account , e.g. a Vanguard 401k. A list of cash flows is a dated list of positive or negative currency being invested or withdrawn as proceeds to/from that investment. So I have a list of cash flows per account, and each account records only one financial instrument. For reporting I'll group those per-account series in logical units of \" reports \", for example lumping together ones for the same instrument bought in different accounts / brokers, or different instruments that represent the same underlying by merging their cash flows. Or even to group them by \"strategy\". Basically, by merging the cash flows of multiple accounts, I have a generic way to group any subsets of accounts and compute their returns. Using the cash flows, I will then run a simple root finding routine to calculate the average annual rate those flows would have to grow in order to result in their final market value. This provides me with overall returns. This is similar to calculating the Internal Rate of Return . I will do that for the entire time series, but also for sub-intervals within the time series to compute, e.g. calendar returns (i.e., each year or quarter) or cumulative returns for trailing periods. Since cash flows are flagged as dividends or not, I can separate the returns from appreciation from the returns from dividends. Reports with plots are produced for each of the groups. Here's a diagram that shows how the \"configure\", \"compute_returns\" and \"download_prices\" scripts work together: These will be further detailed below. Configuration \uf0c1 First, a configuration needs to be created to define a list of investments, and groups of those, for which reports will be produced. This configuration is provided as a text-formatted protocol buffer message. Investment. An investment corresponds to a particular instrument stored in a particular account. It also involves other transactions that don't directly involve that particular account. We want to provide a few set of account names: Asset account. The name of the account holding the commodities for that investment. Matching accounts. A list of additional accounts which will select transactions with postings to them. Cash accounts. A list of accounts external to the investment, which will be used to flag inflows and outflows to and from the investment. Report. While we can compute returns for each investment, the more general and useful case is to compute returns on groups of them. A report is simply a list of investment names. A PDF file will be produced for each report. Here's an example of 6 investments and 3 reports: investments { # Accounts at BTrade. investment { currency: \"GLD\" asset_account: \"Assets:US:BTrade:GLD\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"AMZN\" asset_account: \"Assets:US:BTrade:AMZN\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"QQQ\" asset_account: \"Assets:US:BTrade:QQQ\" dividend_accounts: \"Income:US:BTrade:QQQ:Dividend\" cash_accounts: \"Assets:US:BTrade:Cash\" } # Accounts at IBKR. investment { currency: \"IAU\" asset_account: \"Assets:US:IBKR:IAU\" cash_accounts: \"Assets:US:IBKR:Cash\" } investment { currency: \"SLV\" asset_account: \"Assets:US:IBKR:SLV\" cash_accounts: \"Assets:US:IBKR:Cash\" } # Accounts at Schwab. investment { currency: \"GOOG\" asset_account: \"Assets:US:Schwab:GOOG\" cash_accounts: \"Assets:US:Schwab:Cash\" cash_accounts: \"Assets:AccountsReceivable\" cash_accounts: \"Assets:US:GoogleInc:GSURefund\" } } groups { group { name: \"strategy.gold\" investment: \"Assets:US:BTrade:GLD\" investment: \"Assets:US:IBKR:IAU\" } group { name: \"strategy.tech\" investment: \"Assets:US:BTrade:QQQ\" investment: \"Assets:US:BTrade:FB\" investment: \"Assets:US:Schwab:GOOG\" } group { name: \"all\" investment: \"Assets:US:*\" currency: \"USD\" } group { name: \"accounts.BTrade\" investment: \"Assets:US:BTrade:*\" currency: \"USD\" } } Different reports can include the same investment. References to accounts and investment names support simple UNIX-style globbing patterns in the configuration. These are expanded to full account names at runtime and stored in the output. A \" configure.py \" script can automatically infer a working basic configuration from an existing Beancount ledger. A report will be generated for each unique instrument and the same metadata fields honored by the \"export\" script (\"assetcls\", \"strategy\") will also generate reports. I recommend you run this on your ledger and then custom tailor the configuration manually. Finding Accounts \uf0c1 This script needs to figure out the list of available investments to report on. By convention I keep pairs of dedicated leaf accounts for each commodity type in my ledger, one to contain the actual positions (assets) and one to receive dividends, e.g., for \"VTI\" held in broker \"BTrade\", accounts like 2012-03-01 open Assets:US:BTrade:VTI VTI 2012-03-01 open Income:US:BTrade:VTI:Dividend USD This has two consequences: (a) it makes it easy to find the list of accounts that contain investments (any account with a leaf account name that is also one of the commodities found in the ledger), and (b) it nicely isolates all the activity related to each of those investments by finding all the transactions with a posting to that account . I recommend you follow the same convention in your chart of accounts. The result is a list of accounts like \"Assets:US:BTrade:VTI\", specific to each (instrument, institution). I further filter down this list to the subset of accounts which were still open up to 15 years ago (I close my accounts\u2014using Beancount's Close directive\u2014when they're done). In my particular case, I didn't have much savings back then, and there's no point in bothering to do the work to normalize those crumbs in my investing history for that far back. Extracting Cash Flow Data \uf0c1 This section describes the various steps I took to extract relevant data from my ledger. Extracting Relevant Transactions \uf0c1 For each of the identified asset accounts, we want to pull out from the ledger's transactions the list of transactions affecting that account. We simply run through the entire ledger's transactions keeping transactions with at least one posting to the investment's asset account, dividend income accounts, or to any of the other \"match accounts\" defined for it. For instance, transactions for cash dividend payments will not show an asset posting so if dividends are paid; a typical dividend payment would contain only the dividend income posting and a cash posting (for the deposit): 2019-11-27 * \"Dividend\" Income:US:BTrade:VTI:Dividend -123.45 USD Assets:US:BTrade:Cash 123.45 USD So it is necessary to include the dividend account in the configuration to include those transactions, because they typically do not involve the asset account. Account Categorization \uf0c1 The next step is to generalize transactions to templates : we record the full set of accounts involved in the extracted transactions of each investment, and assign them to general categories based on their role in the transaction. For example, if I inspect my \"VTI\" trades, I will encounter the following accounts: Assets:US:BTrade:Cash Assets:US:BTrade:VTI Expenses:Financial:Commissions Income:US:BTrade:VTI:Dividend I map each account to one of several generic categories (I could probably simplify this now): ASSET # The account holding the commodity. CASH # Cash accounts, employer matches, contributions. DIVIDEND # Dividend income account. EXPENSES # Commissions, fees and other expenses. INCOME # Non-dividend income, P/L, gains, or other. OTHERASSET # Other assets than the primary asset for this investment. OTHER # Any other account. Like this: 'Assets:US:BTrade:Cash': CASH 'Assets:US:BTrade:VTI': ASSET 'Expenses:Financial:Commissions': EXPENSES 'Income:US:BTrade:VTI:Dividend': DIVIDEND In this way, I can compare similar transactions to each other across instruments and extract information from them with the same code. For example, a transaction that involves a dividend account and a cash account is a cash dividend payment, and I can write a generic handler to extract the cash flows for this. The categorization was originally prototyped with a set of ad-hoc rules, but the configuration now serves to provide the categorization. Note: In the process of doing this, I noticed many irregularities in how I named my accounts. For example, I used \":Dividend\" and \":Dividends\" sometimes. I went through my ledger and had to make some changes to name accounts coherently, and iterated until all my accounts were categorized correctly. You may have to review some of your data entry as well. Handling Transactions using the Signature \uf0c1 Using the account-category mappings from the previous section, I was able to derive a unique \"signature\" for each transaction. For example, a transaction like this: 2020-03-12 * \"(DOI) ORDINARY DIVIDEND\" Income:US:BTrade:VTI:Dividend -1312.31 USD Assets:US:BTrade:Cash 1312.31 USD would have a signature of CASH_DIVIDEND which is hopefully always a dividend payment. Beancount has a pretty flexible syntax and does not enforce that your transactions follow particular templates like this, so it wasn't clear to me when I started this project what patterns I would find in my ledger of 12 years of ad-hoc data entry\u2026 I wasn't certain that this categorization and these signatures would be sufficient to correctly handle a correct conversion to cash flows. So I had my script produce two sets of files for debugging: Investment details. A file for each investment , rendering a list of all the transactions that were extracted for it, decorated with metadata showing the categorizations inferred on each posting, as well as a categorization map of all the accounts encountered. I inspected these files visually to ensure that the account/patterns from the configuration were extracting the full and correct set of transactions involved in that investment. Signature transactions. A file for each unique signature , with the full list of transactions matching that signature across all investments. By inspecting these files, I made sure that all the transactions matching the same signature were indeed playing the same role, so that a single handler per signature is sufficient. At this point, I had a limited list of unique signatures, each with clear unique roles: ASSET_CASH : Purchase or sale ASSET_CASH_EXPENSES : Purchase or sale with commission ASSET_CASH_INCOME : Purchase or sale with profit ASSET_CASH_INCOME_EXPENSES : Purchase or sale with commission and profit ASSET_EXPENSES : Fee paid from liquidation ASSET_INCOME : Cost basis adjustment (with P/L) ASSET_INCOME_EXPENSES : Fee from liquidation (with P/L) ASSET : Stock splits ASSET_DIVIDEND : Dividend reinvested CASH_DIVIDEND : Dividend payment CASH_INCOME_DIVIDEND : Dividend payment and gains distribution ASSET_OTHERASSET : Exchange of stock/symbol \u2026 Note that the specific list really depends on the particular contents of your ledger and you should inspect the files produced for correctness. I then wrote specific handlers to produce the cash flows corresponding to each of those transaction signatures, reasoning about each of those cases in isolation. This allowed me to correctly produce a full list of cash flows per investment. Note: In practice I encountered 3 or 4 more signatures types that were a bit exotic and by fixing up my ledger I managed to either correct or break apart these transactions to equivalent but simpler ones. In particular, one of my importers was lumping together trades occurring on the same day, and I went back and fixed it and those transactions manually. The ASSET_OTHERASSET signature, in particular, is an exchange of stock (Google -> GOOG,GOOGL). Doing something like this brings up idiosyncrasies in your bookkeeping technique. Being consistent and using fewer templates is helpful. It would be a valuable idea for an accompanying plugin to restrict the possible set of templates to a select few, so that data entry is constrained to work well with this returns production code. Generalizing Production of Cash Flows \uf0c1 After inspecting each of my signature handlers, I tried to generalize them to a single unified handler that would work across all transactions. It turns out that, at least with my existing ledger's transactions, it's possible. Essentially, recording inflows or outflows to cash accounts or other assets is sufficient. In a transaction like this: 2013-09-18 * \"Buy shares of HOOL\" Assets:US:BTrade:Cash -818.55 USD flow: CASH Assets:US:BTrade:HOOL 8 HOOL {101.20 USD} flow: ASSET Expenses:Financial:Commissions 8.95 USD flow: EXPENSES The \"CASH\" posting is a sufficient incoming cash flow, so we record -818.55 USD. In a cash dividend payment: 2013-12-17 * \"Cash Dividend payment\" Assets:US:BTrade:Cash 38.50 USD flow: CASH Income:US:BTrade:HOOL:Dividends -38.50 USD flow: DIVIDEND Similarly the 38.50 is a sufficient outgoing cash flow, so we record +38.50 USD. On the other hand a reinvested asset dividend, as you would find in some mutual funds, does not generate any cash flow; it simply remains in the investment and increases its total value: 2013-12-30 * \"Reinvested dividend\" Assets:US:BTrade:HOOL 0.356 {103.41} USD flow: ASSET Income:US:BTrade:HOOL:Dividends -36.81 USD flow: DIVIDEND This rule seems sufficient to handle all the contents of my ledger correctly. In the end, I implemented both methods: I use the general rule to produce the list of cash flows, but I also call out to the explicit handlers and cross-check that the extracted cash flows are identical, just to be sure. This is enabled by a flag in compute_returns.py ( --check-explicit-flows ). This forces me to ensure that I've analyzed all the possible transaction templates. Note: If in using this script you find cases from your ledger that aren't handled by using a series of cash accounts, please let me know (on the mailing-list). Cash Flows \uf0c1 The handlers described in the previous section each produced a list of cash flows per transaction, and together for the account, they are essentially a list of: (Date, Amount, IsDividend) Now, this is a simpler model to work from. For each account, we now have a sorted series of dated cash flows. Note that Amount includes its cost currency (I have both USD and CAD), IsDividend is a flag identifying the cash flow as being a dividend payment or not (to compute returns without the dividends). These series of cash flows can be easily merged between accounts, and truncated over time by inserting initial or final cash flows corresponding to the market value at those dates. Rendered, they might look like this (because of the scale, rendering the log brings up detail that is otherwise difficult to see; dividends are rendered in green): Note that since many transactions do not generate cash flows, the list of cash flows of an investment is insufficient by itself to compute the value of the investment over time. When truncating for a time interval, the market value of the investment is derived using the list of transactions. Finally, the list of cash flows for each group of investments reported can be trivially merged by concatenating them. Computing Returns \uf0c1 Calculating the Average Growth Rate \uf0c1 For each series of cash flows, the cash flows are merged together. I use scipy.optimize.fsolve to calculate the rate that satisfies net present value: c f i /(1\u2005+\u2005 r ) t i = 0 where cf i are the signed cash flow amounts and t i are the times from today for each cash flow (in years). We solve for r . To compute the returns without dividends, we just exclude cash flows returned from dividends. The difference tells us how much of our returns was due solely to dividend income. It's important to note that if the corresponding positions are still invested, you have to insert a final negative cash flow for the market value at the latest date to zero it out. You're essentially simulating a sale. If significant transaction costs are going to be involved, you might want to simulate those as well (e.g. if you're doing this for a home, in particular). Here's the beauty in this: nowhere was the underlying price used , except for marking the current position value to market value. We did not read any external measure of returns. These returns are computed from cash going in and coming out . These are actual realized returns. They do not lie. Intervals. To compute calendar returns, e.g., returns for years 2016, 2017, 2018, 2019, 2020, I truncate the cash flows to keep only those inside the interval, e.g. 2018-01-01 to 2018-12-31 for year 2018, and if there was an existing position at the beginning of the interval, insert a negative cash flow at the start of the series equivalent to the market value at those dates. I do the same thing at the end of the interval, with a positive cash flow, as described previously. Ideally I'd like to look at different sets of intervals: Lifetime of investment. Total returns over the entire lifetime of the positions, to boil it all down to a single number. Calendar. Annual or quarterly returns over the last 10 or 15 years, to witness variation in returns over time. Cumulative. Cumulative returns over the last 10 or 15 years, aligned on calendar periods, to get a sense of whether things are improving or worsening, and how well my strategies are doing in more recent periods (e.g. last 3 years). High frequency cumulative. Cumulative returns over the last 12 months, aligned with monthly or weekly dates, to assess the impact of decisions in the short-term. These can be rendered in tables, and investments can be compared to each other in that way. Filling in Missing Price Points \uf0c1 The calculations of returns over various intervals require marking positions to market value at the beginning and end of the interval dates. Beancount being designed hermetically, i.e., it does not fetch any price externally, only uses what's in the ledger, its price database lookup will automatically produce the last available price point and its recording date before the requested date. Depending on your recording discipline, some of those prices might be out-of-date and introduce inaccuracies. This is especially important since the amounts converted at the end of periods (i.e. to estimate the value of current positions) can be large and meaningfully influence even the lifetime returns number. So it's important to have relatively fresh price points in the ledger's price database. Now the question is, given a changing set of positions over time, for a given set of interval dates, which price entries are required to produce accurate results? Because this depends heavily on the particular inputs of the returns script, in order to solve this problem I simply wrapped the price database with a facade that collects all the (instrument, date) pairs for requested conversions during the production of the reports, and filter those down by some age threshold (e.g., price not older than 3 days). These are essentially the price points missing from the file. At the end of the script, I output these to a file with Price directives, and another program (download_prices.py) can read that file and fetch historical rates for those. It produces updated rates which you can paste to your ledger file as a one-off adjustment and then recompute more accurate returns. Pulling data from Yahoo! Finance worked for 90% of my positions, but some of my older instruments were quite old or even retired, or not available (e.g., some retirement funds), so I had to find them by browsing and manually entering some of these price points (I had something like 30\u2026 no big deal). Rolling windows. One important point is that going forward, it will be easier to align reporting intervals to some calendar-based interval (e.g., monthly), so that I don't have to regenerate price data every time I produce my returns. Aligning to months is probably good enough for my time horizon. Stock Splits. Beancount does not explicitly make adjustments of prices for stocks that split, so your price source should return the pre-split price of the stock at the time if you are recording it as such. You should be able to check your price time series for errors using Fava. Currency Conversion \uf0c1 Another important detail is that each investment has its own quote currency. I used to live in Canada, and some of my older investments are denominated in CAD. So the question arises: do I compute the returns in local (CAD) currency, or in terms of my reference currency (USD)? It's convenient that Beancount's Inventory object has functions that can easily perform those conversions where needed. And since the cash flows I extracted are stored using Beancount's Amount object, I already have the quote currencies correctly in my extracted dataset. In any group, if all the instruments have the same quote currency, I report returns in that currency. If the group includes a mix of quote currencies, I further convert everything to USD (so I get USD returns). Reporting \uf0c1 Grouping Accounts \uf0c1 Returns are calculated jointly for each group of accounts in each \"report\", as defined in the configuration. Here are some example groupings that are sensible to define: Same instrument in different accounts. If you buy the same stock in different accounts, it makes sense to report on the returns for that stock jointly, across all your accounts. Same underlying. Some instruments represent the same stock, e.g. GOOG and GOOGL (different share class, same company). Also, IAU and GLD (Gold) are different ETFs whose values are both derived from physical gold reserves (located in bank basements in London). Same asset class. Instruments from the same asset class, e.g. \"metals\", which would include IAU, GLD, SLV, COPX, etc., or \"REITs\", which would include VNQ, VGSLX, etc. Or \"all stocks\" vs. \"all bonds\". By Strategy. In my portfolio investment method, I have a multi-headed approach where I define specific broad strategies and then select a list of instruments to implement it. For instance, I have a \"tech sector\" strategy, which includes FAANG companies. Or a \"growth stock\" strategy, which might include different indexes like VUG, IWO and RGAGX. I can report how well those strategies are doing, across brokers. Or geographically, \"developed APAC\", including EWY, EWT, EWS, EWA. By Broker. I can report returns by broker or broker account. In particular, this can be an easy way to separate realized profits by tax treatment (e.g., 401k is tax-deferred). Asset type. Comparing all index funds to all managed funds (e.g. mutual funds). Note that different reports can include the same investments. Groupings aren't exclusive. You define what makes most sense for your situation. For reference, I use more than 20 reporting groups. Running the Code \uf0c1 Simply call ./experiments/returns/compute_returns.py to generate all reports and debugging files, where is in the format shown in \u201cConfiguration\u201d . It's a little slow \u2014 some performance improvements are possible \u2014 but if you supply a list of report names after the final argument, only those investments and reports will be processed, so you can iterate faster that way. See flags with --help for details, and config.proto for the configuration input documentation. Results Rendered \uf0c1 For each reporting group, I currently produce: A plot of the cash flows over time , and a smaller plot of log (cash flows). Dividend payments are typically dwarfed by cash flows for the principal, so the log chart allows me to see timings. This is mainly used to get an overview of activity over time, and for debugging. A plot of cumulative value, where I render two curves: Cumulative cash flows , with a growth curve matching that returns I regressed over. The result should be a plot with gentle slope between cash flows (corresponding to the total returns growth), with a final drop to zero. Market value over time : A curve of the mark-to-market value of the portfolio over time. This allows me to make some sense of calendar returns, by witnessing how the asset value moves based on price. A table of total returns , returns without dividends, and returns from dividends only. A table of calendar returns for each year, also broken down by total, ex-dividends, dividends-only. (I'll probably render this as a plot in the future.) A table of trailing cumulative returns . This is going to get refined and augmented as I'm actively working on this code [September 2020]. Example \uf0c1 Here's an example report, for a subset of accounts with a \"growth\" focus, held at different brokers. I produce one of these for each reporting group. (I greyed out parts for privacy.) Interpretation Gotchas \uf0c1 A few notes are in order: All rates are annualized . This makes it easy to compare numbers to each other, but it also means that positions held for a short amount of time will produce numbers that are unrealistic for long term extrapolation. In particular, new positions entered only a few months ago may be subject to high growth or a big drop, both of which when extrapolated to an entire year may show eye-popping percentages. Do keep this in mind, especially when looking at recent positions added to your portfolio. Taxes aren't factored in. Returns from taxable accounts and tax-deferred accounts should be evaluated differently and if the difference in tax is large, they can't be compared so readily. Do keep in mind that in most countries gains are only taxed on realization (sale) so in effect, long held investments behave more or less like tax-deferred ones. Just don't sell so much. This is a great advantage in holding broadly diversified ETFs (and usually unquantified, as people's attention is still overly focused on those benefits from registered accounts, e.g., 401k plan). Cost basis. Note that nowhere in our calculations was the cost basis used or factored in, so don't confuse it with market value. The cost basis is only useful for tax-related effects. Other Instruments Types \uf0c1 Note that computing returns in this manner isn't limited to only stocks and bonds. Using the same methodology, we can include other types of instruments: P2P Lending \uf0c1 I've used LendingClub before all the hedge funds got involved to pick up the low-hanging fruit, and eventually let all the bonds I'd invested in expire. It was easy to apply the same methodology to compute returns from those investments. Originally, I had the discipline to record this investment from monthly PDF statements using transactions like this: 2016-10-31 * \"2016-10-31.Monthly_Statement.pdf\" Assets:US:LendingClub:FundsLent -451.52 LENCLUB {1 USD} Assets:US:LendingClub:Cash 451.52 USD Income:US:LendingClub:LoanInterest -21.68 USD Income:US:LendingClub:Recoveries -5.92 USD Expenses:Financial:Fees 1.08 USD ;; Recovery fees Expenses:Financial:Fees 4.71 USD ;; Service fees Expenses:Financial:Fees 0.45 USD ;; Collection fees Assets:US:LendingClub:Cash Assets:US:LendingClub:FundsLent -23.05 LENCLUB {1 USD} Income:US:LendingClub:ChargedOff 23.05 USD And later on, after the principal bonds expired, transactions like this: 2018-11-30 * \"2018-11-30.Monthly_Statement.pdf\" Income:US:LendingClub:Recoveries -2.73 USD Expenses:Financial:Fees 0.49 USD ;; Recovery fees Assets:US:LendingClub:Cash All it took to compute returns is this configuration for the investment: investment { currency: \"LENCLUB\" asset_account: \"Assets:US:LendingClub:FundsLent\" match_accounts: \"Income:US:LendingClub:Recoveries\" cash_accounts: \"Assets:US:LendingClub:Cash\" } Note that the match account configuration was necessary to pick up crumbs from later transactions with only recoveries (and no posting to the asset account). For what it's worth, I was able to generate a 6.75% return over this investment. Meh. Real Estate \uf0c1 Cash flows can be extracted from all transactions for a home, so you can compute the returns for all the money poured into your home, as if it was done purely for investment purposes. Usually buying a home is done for other reasons \u2014 stability for children, the ability to make your own improvements, the forced savings involved in regular principal payments, and oftentimes just a sense of \"having a home\" \u2014 but in the vast majority of the cases, home ownership is more of a cost center and returns would have been better invested elsewhere (see this book for a great expos\u00e9 of the pros & cons). Personally, I have better things to do than fix toilets and worry about leaky windows in the winter so I went back to renting, but I went through the experience once and it was quite worthwhile, as a learning experience but also to experience the \"joy\" of having my own place. Through the exercise is useful to calculate how much having your own home is actually costing you, and how much you might have made by putting the very same cash flows into the market instead. It's a little more involved, because, You will have to have discipline to segregate expenses you wouldn't have had if you'd have rented to accounts specific for your home. You will need to account for equivalent rent received that you didn't have to pay because you lived in the home. If you still own the home, you will have to simulate a substantial agent fee and other closing costs (by inserting a transaction). Most places have a large tax break when selling a property (sometimes the full capital appreciation), so that really needs to be factored in. It's not always simple to calculate, especially if you rented your property for some of its ownership lifetime, in which case you might only be able to deduct a part of the capital gains. For some, there is great value in the optionality of being able to move easily and at low cost (e..g, accepting a new job at the other side of the country) and that personal value will be difficult to estimate. This will require me to do more categorization work, and it will be best documented as a separate project, though using the same code. I did manage to create a simple configuration and extract a 5%-ish number out of it, but I think I'll need a bit more work to declare victory. More to come. Options \uf0c1 I sometimes hedge some of my portfolio downrisk with long-dated OTM PUT positions. I've sold OTM PUTs and CALLs in the near term to finance it. The method I described for stocks in this document works equally well for options, that is, extracting cash flows from cash. The main differences are that: Currency names. Instrument names are specific to each contract\u2014they include the expiration date and strike price\u2014so I don't store the options to an account whose name includes the instrument name at the leaf. I just use a generic leaf account name, such as \"Options\" or \"Hedging\" which I select when I enter/exit positions. Prices for options. Prices for options aren't as easily fetchable programmatically. I'm having to use a private API for this. Perhaps more importantly, declining theta, varying vol and non-linearity near the strike means I do need to have to have pretty recent price estimates in my ledger in order to compute returns on unrealized gains/losses. I think the effect is strong enough that I'd want to eventually have some code to always update the prices just before generating the reports. Future Work \uf0c1 This section describes desired future improvements on the returns code, which I'm likely to implement, and the corresponding challenges. Note: As I'm writing this in September 2020, I'm actively working on this code and probably will continue over the next few months' weekends. It's possible this document falls slightly out-of-date and that some of the proposals below become implemented. Refer to the source code for the latest. Relative Size over Time \uf0c1 Another useful bit of data I could easily add to reports is a stack plot of market values over time, as well as relative fractions of each investment in a report group. This would be useful for monitoring growth of particular instruments in a group to help with rebalancing. Comparison Against a Benchmark \uf0c1 One of the important barometers of success in running your own portfolio is a comparison to an equivalent investment of capital in a simple portfolio of diversified stocks and bonds rebalanced regularly. After all, if my returns aren't beating that, in a normal environment, one could make the argument I shouldn't bother with a more complicated allocation. Assuming that my access to capital is the same (which it is, because in this project I'm looking at savings from income), I could simply replace my cash flows by cash flows to this simulated portfolio, in other words, use the same timings to simulate buying other assets. I would have to exclude the dividend payments because those are specific to the particular instruments I used, and at the same time generate simulated dividends from similarly sized positions in the benchmark portfolio. It should be possible to do this without modifying my ledger. One issue is that I will require a set of accurate prices for the benchmark, at the dates of my historical cash flows. Like my script already does for aging price points, this could easily be stored in a file for fetching. Perhaps more complicated is the fact that Beancount does not currently support a database of per-share dividend distributions. This could be added without introducing new syntax by attaching and honoring metadata on Price directives, such as 2020-09-01 price LQD 136.16 USD distribution: 0.295 USD Overall, it wouldn't be too difficult to implement this. Including Uninvested Cash \uf0c1 One of the problems I'm facing in investing my own portfolio is the lack of discipline around timely investing of available cash, especially when times are uncertain and difficult decisions have to be made. In order to account for the drag on performance from that, I should include an \"investment\" that's purely reflecting the total amount of uninvested cash over time. Because this varies a lot, a good approximation can be obtained by sampling total amount of investable cash every month and synthesizing cash flows from differences. If that cash flow series gets included as part of a portfolio, it'll suitably drag its returns by diluting them. After-Tax Value \uf0c1 At the moment I export all my holdings to a Google Sheets doc using the export script , and from that, break it down between long-term and short-term positions vs. pre-tax, after-tax, Roth, and taxable buckets. From those 8 aggregates, I remove estimated taxes and report a single \"estimated after-tax net worth\" and corresponding tax liability. This is a rough estimate. The returns report is however much more detailed, and I could simulate tax payments not only on liquidation, but also at the end of each year (from dividends and sales). I have all the lot dates on each position held so I can automatically figure out short-term vs. long-term lots. Inflation Adjustments \uf0c1 The market value amounts reported on the returns charts and calendar returns do not account for inflation. Over long periods of time, those can make an important difference in returns. It would be useful to discount the returns over time using annual estimates for the CPI (or some other estimate of inflation; you could even make up your own, from the expenses in your ledger), so that we can look at a curve of real growth and not just nominal growth. Sales Commission \uf0c1 The configuration could easily be improved to let the user specify expected commissions on sales of investments, either in absolute or relative (%) amounts. This would be used to mark positions with more realistic liquidation values. This could make a difference on investments with either small amounts or large commissions (i.e., working through a regular broker, or on real estate). Risk Estimation & Beta \uf0c1 A perhaps more advanced topic would be to compute an estimate of the variance from the specific portfolio composition in order to calculate and report some measurement of risk, such as Sharpe Ratio . This would require a sufficient number of regularly spaced price points. Variation of the measures over time could be fun too, as well as calculating the current portfolio's specific sensitivity to market as a whole (beta). Conclusion \uf0c1 I had expected it would be possible to produce a clear picture of returns from Beancount data, and having done it I'm more satisfied with the level of detail and clarity I was to produce from my ledger than I had expected. This goes well beyond plotting of net worth over time, this actually works really well, and I can use it to compare the performance of different investments fairly. I hope at least some Beancount users will be able to run it on their ledgers and I'm looking forward to hearing some feedback from those who set it up. Perhaps most importantly, I was very surprised to see results on my own portfolio returns. I'm one of those people who would normally shrug and guess an underwhelming ballpark number if asked what I thought my returns were, such as \"meh, 6% or something, not super happy.\" Doing this work was originally driven by not having the right answer to this question. It turns out, over the last 15 years, that I've generated an almost 12% average annual return, and 14% annual return in the last 5 years. I haven't yet made the benchmark comparison, and for sure these numbers should be put side-by-side with the market for a fair assessment. Nevertheless, going through the exercise has provided me with renewed confidence and hope about the future, and I hope the clarity it will bring to other Beancount users' own investments will be similarly enlightening.","title":"Calculating Portfolio Returns"},{"location":"calculating_portolio_returns.html#calculating-portfolio-returns","text":"Martin Blais , Sept 2020 http://furius.ca/beancount/doc/returns This document describes how to compute portfolio returns from a Beancount ledger.","title":"Calculating Portfolio Returns"},{"location":"calculating_portolio_returns.html#motivation","text":"You will be surprised to find that discount brokers typically do not provide accurate and complete returns calculations for your investments based on your specific cash flows. They tend to report other measures of performance: Change in value. The simplest they provide is a snapshot of the account value at the beginning and end of the period (or year). The problem with this method is that it does not reflect your infusions or removal of cash as such, nor your changes in positions. For example, if you had an account with $50,000 at the beginning of the year and you've added in $30,000 in August, reporting a difference of $37,000 at the end of December is just not useful (you'd have to mentally discount for new invested cash during the year, and what if you want to break it down and compare the returns from different instruments in the account?). Underlying performance. They might report the growth of the underlying asset in isolation, disregarding your specific positions. It's not very useful to say \"HDV grew by 8.2% over the last year\" if you invested only (or even mostly) in the second half of the year. What I'd like is to know how much my specific investments grew, given my specific changes in positions and timings. In other words, how did I do? No dividends. Another issue is that performance is typically only reporting capital appreciation due to the change in price of the investment. Ideally one would like to break down the performance between capital appreciation and dividends and returns for each of those components, as well as overall performance, so you can compare returns from stocks and bonds to each other. You can sometimes find out that information from the Yield field in the summary, but for uneven distributions, this won't help me find out what my actual returns from dividends were, over any period. Commissions and management expenses. Some brokers charge management expenses on a monthly basis, based on the value of the account. I would like to know what my portfolio return is net of those expenses. I want accurate returns reporting, based on my actual cash flows and changes of positions over time. If I maintain your investment information in a Beancount ledger, in theory it contains all the data I need in order to compute your true returns, based on the specific timings of my own savings (cash infusions) and which positions I held at which time. It's just not in the simplest format required to do it\u2014 Beancount transactions are much more flexible than one might want and a simpler series of cash flows needs to be extracted from it. This document explains how I finally did this from my own Ledger. And how we might generalize this to yours, based on some simple rules. Most of this text is dedicated to the pedestrian details of extracting the right data. The source code can be found here . In addition, a fair and honest comparison to other investments scenarios should be possible, based on those same cash flows. For instance, you should be able to produce data that looks like \"My investments in ZZZ have returned 8.2%, 1.1% of which were from dividends, and if I'd invested in a 60/40 portfolio of broad stocks and bonds it would have returned 7.2% instead.\" In other words, I want to assess my performance relative to a number of common alternatives. (Finally, note that if all you need is a snapshot of your current positions, that's already handled by the export script .)","title":"Motivation"},{"location":"calculating_portolio_returns.html#history","text":"In 2014, I made a brief attempt to break down information from my ledger to do this. At the time I got bogged down in details when some of the time-series I was extracting weren't producing what looked like sensible results (some with outliers). I got too detailed too fast. Sometimes it's better to just get the whole job done and come back for the details. I hadn't logged enough debugging information and I didn't have enough confidence in its output to use it. I never actually finished the work at the time, and eventually moved the scripts to experiments and shrugged. \"Later.\" In August 2020, I sat down to do this again, this time with a less ambitious goal of just getting a good approximation and producing lots of debug output, boiling down extraction to pull out just the cash flows and to get the job complete over all my accounts, even if it meant making some adjustments to my input file. It turned out to be the right decision: I managed to complete the task, and this document presents my journey, the method, its assumptions, quirks, and some results.","title":"History"},{"location":"calculating_portolio_returns.html#overview-of-method","text":"The method I'll use is to extract cash flow information for each investment. What I call \" an investment \" in this context is a kind of financial instrument , e.g., shares of \"VTI\" for the Vanguard Total Stock Market ETF, invested in a particular account , e.g. a Vanguard 401k. A list of cash flows is a dated list of positive or negative currency being invested or withdrawn as proceeds to/from that investment. So I have a list of cash flows per account, and each account records only one financial instrument. For reporting I'll group those per-account series in logical units of \" reports \", for example lumping together ones for the same instrument bought in different accounts / brokers, or different instruments that represent the same underlying by merging their cash flows. Or even to group them by \"strategy\". Basically, by merging the cash flows of multiple accounts, I have a generic way to group any subsets of accounts and compute their returns. Using the cash flows, I will then run a simple root finding routine to calculate the average annual rate those flows would have to grow in order to result in their final market value. This provides me with overall returns. This is similar to calculating the Internal Rate of Return . I will do that for the entire time series, but also for sub-intervals within the time series to compute, e.g. calendar returns (i.e., each year or quarter) or cumulative returns for trailing periods. Since cash flows are flagged as dividends or not, I can separate the returns from appreciation from the returns from dividends. Reports with plots are produced for each of the groups. Here's a diagram that shows how the \"configure\", \"compute_returns\" and \"download_prices\" scripts work together: These will be further detailed below.","title":"Overview of Method"},{"location":"calculating_portolio_returns.html#configuration","text":"First, a configuration needs to be created to define a list of investments, and groups of those, for which reports will be produced. This configuration is provided as a text-formatted protocol buffer message. Investment. An investment corresponds to a particular instrument stored in a particular account. It also involves other transactions that don't directly involve that particular account. We want to provide a few set of account names: Asset account. The name of the account holding the commodities for that investment. Matching accounts. A list of additional accounts which will select transactions with postings to them. Cash accounts. A list of accounts external to the investment, which will be used to flag inflows and outflows to and from the investment. Report. While we can compute returns for each investment, the more general and useful case is to compute returns on groups of them. A report is simply a list of investment names. A PDF file will be produced for each report. Here's an example of 6 investments and 3 reports: investments { # Accounts at BTrade. investment { currency: \"GLD\" asset_account: \"Assets:US:BTrade:GLD\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"AMZN\" asset_account: \"Assets:US:BTrade:AMZN\" cash_accounts: \"Assets:US:BTrade:Cash\" } investment { currency: \"QQQ\" asset_account: \"Assets:US:BTrade:QQQ\" dividend_accounts: \"Income:US:BTrade:QQQ:Dividend\" cash_accounts: \"Assets:US:BTrade:Cash\" } # Accounts at IBKR. investment { currency: \"IAU\" asset_account: \"Assets:US:IBKR:IAU\" cash_accounts: \"Assets:US:IBKR:Cash\" } investment { currency: \"SLV\" asset_account: \"Assets:US:IBKR:SLV\" cash_accounts: \"Assets:US:IBKR:Cash\" } # Accounts at Schwab. investment { currency: \"GOOG\" asset_account: \"Assets:US:Schwab:GOOG\" cash_accounts: \"Assets:US:Schwab:Cash\" cash_accounts: \"Assets:AccountsReceivable\" cash_accounts: \"Assets:US:GoogleInc:GSURefund\" } } groups { group { name: \"strategy.gold\" investment: \"Assets:US:BTrade:GLD\" investment: \"Assets:US:IBKR:IAU\" } group { name: \"strategy.tech\" investment: \"Assets:US:BTrade:QQQ\" investment: \"Assets:US:BTrade:FB\" investment: \"Assets:US:Schwab:GOOG\" } group { name: \"all\" investment: \"Assets:US:*\" currency: \"USD\" } group { name: \"accounts.BTrade\" investment: \"Assets:US:BTrade:*\" currency: \"USD\" } } Different reports can include the same investment. References to accounts and investment names support simple UNIX-style globbing patterns in the configuration. These are expanded to full account names at runtime and stored in the output. A \" configure.py \" script can automatically infer a working basic configuration from an existing Beancount ledger. A report will be generated for each unique instrument and the same metadata fields honored by the \"export\" script (\"assetcls\", \"strategy\") will also generate reports. I recommend you run this on your ledger and then custom tailor the configuration manually.","title":"Configuration"},{"location":"calculating_portolio_returns.html#finding-accounts","text":"This script needs to figure out the list of available investments to report on. By convention I keep pairs of dedicated leaf accounts for each commodity type in my ledger, one to contain the actual positions (assets) and one to receive dividends, e.g., for \"VTI\" held in broker \"BTrade\", accounts like 2012-03-01 open Assets:US:BTrade:VTI VTI 2012-03-01 open Income:US:BTrade:VTI:Dividend USD This has two consequences: (a) it makes it easy to find the list of accounts that contain investments (any account with a leaf account name that is also one of the commodities found in the ledger), and (b) it nicely isolates all the activity related to each of those investments by finding all the transactions with a posting to that account . I recommend you follow the same convention in your chart of accounts. The result is a list of accounts like \"Assets:US:BTrade:VTI\", specific to each (instrument, institution). I further filter down this list to the subset of accounts which were still open up to 15 years ago (I close my accounts\u2014using Beancount's Close directive\u2014when they're done). In my particular case, I didn't have much savings back then, and there's no point in bothering to do the work to normalize those crumbs in my investing history for that far back.","title":"Finding Accounts"},{"location":"calculating_portolio_returns.html#extracting-cash-flow-data","text":"This section describes the various steps I took to extract relevant data from my ledger.","title":"Extracting Cash Flow Data"},{"location":"calculating_portolio_returns.html#extracting-relevant-transactions","text":"For each of the identified asset accounts, we want to pull out from the ledger's transactions the list of transactions affecting that account. We simply run through the entire ledger's transactions keeping transactions with at least one posting to the investment's asset account, dividend income accounts, or to any of the other \"match accounts\" defined for it. For instance, transactions for cash dividend payments will not show an asset posting so if dividends are paid; a typical dividend payment would contain only the dividend income posting and a cash posting (for the deposit): 2019-11-27 * \"Dividend\" Income:US:BTrade:VTI:Dividend -123.45 USD Assets:US:BTrade:Cash 123.45 USD So it is necessary to include the dividend account in the configuration to include those transactions, because they typically do not involve the asset account.","title":"Extracting Relevant Transactions"},{"location":"calculating_portolio_returns.html#account-categorization","text":"The next step is to generalize transactions to templates : we record the full set of accounts involved in the extracted transactions of each investment, and assign them to general categories based on their role in the transaction. For example, if I inspect my \"VTI\" trades, I will encounter the following accounts: Assets:US:BTrade:Cash Assets:US:BTrade:VTI Expenses:Financial:Commissions Income:US:BTrade:VTI:Dividend I map each account to one of several generic categories (I could probably simplify this now): ASSET # The account holding the commodity. CASH # Cash accounts, employer matches, contributions. DIVIDEND # Dividend income account. EXPENSES # Commissions, fees and other expenses. INCOME # Non-dividend income, P/L, gains, or other. OTHERASSET # Other assets than the primary asset for this investment. OTHER # Any other account. Like this: 'Assets:US:BTrade:Cash': CASH 'Assets:US:BTrade:VTI': ASSET 'Expenses:Financial:Commissions': EXPENSES 'Income:US:BTrade:VTI:Dividend': DIVIDEND In this way, I can compare similar transactions to each other across instruments and extract information from them with the same code. For example, a transaction that involves a dividend account and a cash account is a cash dividend payment, and I can write a generic handler to extract the cash flows for this. The categorization was originally prototyped with a set of ad-hoc rules, but the configuration now serves to provide the categorization. Note: In the process of doing this, I noticed many irregularities in how I named my accounts. For example, I used \":Dividend\" and \":Dividends\" sometimes. I went through my ledger and had to make some changes to name accounts coherently, and iterated until all my accounts were categorized correctly. You may have to review some of your data entry as well.","title":"Account Categorization"},{"location":"calculating_portolio_returns.html#handling-transactions-using-the-signature","text":"Using the account-category mappings from the previous section, I was able to derive a unique \"signature\" for each transaction. For example, a transaction like this: 2020-03-12 * \"(DOI) ORDINARY DIVIDEND\" Income:US:BTrade:VTI:Dividend -1312.31 USD Assets:US:BTrade:Cash 1312.31 USD would have a signature of CASH_DIVIDEND which is hopefully always a dividend payment. Beancount has a pretty flexible syntax and does not enforce that your transactions follow particular templates like this, so it wasn't clear to me when I started this project what patterns I would find in my ledger of 12 years of ad-hoc data entry\u2026 I wasn't certain that this categorization and these signatures would be sufficient to correctly handle a correct conversion to cash flows. So I had my script produce two sets of files for debugging: Investment details. A file for each investment , rendering a list of all the transactions that were extracted for it, decorated with metadata showing the categorizations inferred on each posting, as well as a categorization map of all the accounts encountered. I inspected these files visually to ensure that the account/patterns from the configuration were extracting the full and correct set of transactions involved in that investment. Signature transactions. A file for each unique signature , with the full list of transactions matching that signature across all investments. By inspecting these files, I made sure that all the transactions matching the same signature were indeed playing the same role, so that a single handler per signature is sufficient. At this point, I had a limited list of unique signatures, each with clear unique roles: ASSET_CASH : Purchase or sale ASSET_CASH_EXPENSES : Purchase or sale with commission ASSET_CASH_INCOME : Purchase or sale with profit ASSET_CASH_INCOME_EXPENSES : Purchase or sale with commission and profit ASSET_EXPENSES : Fee paid from liquidation ASSET_INCOME : Cost basis adjustment (with P/L) ASSET_INCOME_EXPENSES : Fee from liquidation (with P/L) ASSET : Stock splits ASSET_DIVIDEND : Dividend reinvested CASH_DIVIDEND : Dividend payment CASH_INCOME_DIVIDEND : Dividend payment and gains distribution ASSET_OTHERASSET : Exchange of stock/symbol \u2026 Note that the specific list really depends on the particular contents of your ledger and you should inspect the files produced for correctness. I then wrote specific handlers to produce the cash flows corresponding to each of those transaction signatures, reasoning about each of those cases in isolation. This allowed me to correctly produce a full list of cash flows per investment. Note: In practice I encountered 3 or 4 more signatures types that were a bit exotic and by fixing up my ledger I managed to either correct or break apart these transactions to equivalent but simpler ones. In particular, one of my importers was lumping together trades occurring on the same day, and I went back and fixed it and those transactions manually. The ASSET_OTHERASSET signature, in particular, is an exchange of stock (Google -> GOOG,GOOGL). Doing something like this brings up idiosyncrasies in your bookkeeping technique. Being consistent and using fewer templates is helpful. It would be a valuable idea for an accompanying plugin to restrict the possible set of templates to a select few, so that data entry is constrained to work well with this returns production code.","title":"Handling Transactions using the Signature"},{"location":"calculating_portolio_returns.html#generalizing-production-of-cash-flows","text":"After inspecting each of my signature handlers, I tried to generalize them to a single unified handler that would work across all transactions. It turns out that, at least with my existing ledger's transactions, it's possible. Essentially, recording inflows or outflows to cash accounts or other assets is sufficient. In a transaction like this: 2013-09-18 * \"Buy shares of HOOL\" Assets:US:BTrade:Cash -818.55 USD flow: CASH Assets:US:BTrade:HOOL 8 HOOL {101.20 USD} flow: ASSET Expenses:Financial:Commissions 8.95 USD flow: EXPENSES The \"CASH\" posting is a sufficient incoming cash flow, so we record -818.55 USD. In a cash dividend payment: 2013-12-17 * \"Cash Dividend payment\" Assets:US:BTrade:Cash 38.50 USD flow: CASH Income:US:BTrade:HOOL:Dividends -38.50 USD flow: DIVIDEND Similarly the 38.50 is a sufficient outgoing cash flow, so we record +38.50 USD. On the other hand a reinvested asset dividend, as you would find in some mutual funds, does not generate any cash flow; it simply remains in the investment and increases its total value: 2013-12-30 * \"Reinvested dividend\" Assets:US:BTrade:HOOL 0.356 {103.41} USD flow: ASSET Income:US:BTrade:HOOL:Dividends -36.81 USD flow: DIVIDEND This rule seems sufficient to handle all the contents of my ledger correctly. In the end, I implemented both methods: I use the general rule to produce the list of cash flows, but I also call out to the explicit handlers and cross-check that the extracted cash flows are identical, just to be sure. This is enabled by a flag in compute_returns.py ( --check-explicit-flows ). This forces me to ensure that I've analyzed all the possible transaction templates. Note: If in using this script you find cases from your ledger that aren't handled by using a series of cash accounts, please let me know (on the mailing-list).","title":"Generalizing Production of Cash Flows"},{"location":"calculating_portolio_returns.html#cash-flows","text":"The handlers described in the previous section each produced a list of cash flows per transaction, and together for the account, they are essentially a list of: (Date, Amount, IsDividend) Now, this is a simpler model to work from. For each account, we now have a sorted series of dated cash flows. Note that Amount includes its cost currency (I have both USD and CAD), IsDividend is a flag identifying the cash flow as being a dividend payment or not (to compute returns without the dividends). These series of cash flows can be easily merged between accounts, and truncated over time by inserting initial or final cash flows corresponding to the market value at those dates. Rendered, they might look like this (because of the scale, rendering the log brings up detail that is otherwise difficult to see; dividends are rendered in green): Note that since many transactions do not generate cash flows, the list of cash flows of an investment is insufficient by itself to compute the value of the investment over time. When truncating for a time interval, the market value of the investment is derived using the list of transactions. Finally, the list of cash flows for each group of investments reported can be trivially merged by concatenating them.","title":"Cash Flows"},{"location":"calculating_portolio_returns.html#computing-returns","text":"","title":"Computing Returns"},{"location":"calculating_portolio_returns.html#calculating-the-average-growth-rate","text":"For each series of cash flows, the cash flows are merged together. I use scipy.optimize.fsolve to calculate the rate that satisfies net present value: c f i /(1\u2005+\u2005 r ) t i = 0 where cf i are the signed cash flow amounts and t i are the times from today for each cash flow (in years). We solve for r . To compute the returns without dividends, we just exclude cash flows returned from dividends. The difference tells us how much of our returns was due solely to dividend income. It's important to note that if the corresponding positions are still invested, you have to insert a final negative cash flow for the market value at the latest date to zero it out. You're essentially simulating a sale. If significant transaction costs are going to be involved, you might want to simulate those as well (e.g. if you're doing this for a home, in particular). Here's the beauty in this: nowhere was the underlying price used , except for marking the current position value to market value. We did not read any external measure of returns. These returns are computed from cash going in and coming out . These are actual realized returns. They do not lie. Intervals. To compute calendar returns, e.g., returns for years 2016, 2017, 2018, 2019, 2020, I truncate the cash flows to keep only those inside the interval, e.g. 2018-01-01 to 2018-12-31 for year 2018, and if there was an existing position at the beginning of the interval, insert a negative cash flow at the start of the series equivalent to the market value at those dates. I do the same thing at the end of the interval, with a positive cash flow, as described previously. Ideally I'd like to look at different sets of intervals: Lifetime of investment. Total returns over the entire lifetime of the positions, to boil it all down to a single number. Calendar. Annual or quarterly returns over the last 10 or 15 years, to witness variation in returns over time. Cumulative. Cumulative returns over the last 10 or 15 years, aligned on calendar periods, to get a sense of whether things are improving or worsening, and how well my strategies are doing in more recent periods (e.g. last 3 years). High frequency cumulative. Cumulative returns over the last 12 months, aligned with monthly or weekly dates, to assess the impact of decisions in the short-term. These can be rendered in tables, and investments can be compared to each other in that way.","title":"Calculating the Average Growth Rate"},{"location":"calculating_portolio_returns.html#filling-in-missing-price-points","text":"The calculations of returns over various intervals require marking positions to market value at the beginning and end of the interval dates. Beancount being designed hermetically, i.e., it does not fetch any price externally, only uses what's in the ledger, its price database lookup will automatically produce the last available price point and its recording date before the requested date. Depending on your recording discipline, some of those prices might be out-of-date and introduce inaccuracies. This is especially important since the amounts converted at the end of periods (i.e. to estimate the value of current positions) can be large and meaningfully influence even the lifetime returns number. So it's important to have relatively fresh price points in the ledger's price database. Now the question is, given a changing set of positions over time, for a given set of interval dates, which price entries are required to produce accurate results? Because this depends heavily on the particular inputs of the returns script, in order to solve this problem I simply wrapped the price database with a facade that collects all the (instrument, date) pairs for requested conversions during the production of the reports, and filter those down by some age threshold (e.g., price not older than 3 days). These are essentially the price points missing from the file. At the end of the script, I output these to a file with Price directives, and another program (download_prices.py) can read that file and fetch historical rates for those. It produces updated rates which you can paste to your ledger file as a one-off adjustment and then recompute more accurate returns. Pulling data from Yahoo! Finance worked for 90% of my positions, but some of my older instruments were quite old or even retired, or not available (e.g., some retirement funds), so I had to find them by browsing and manually entering some of these price points (I had something like 30\u2026 no big deal). Rolling windows. One important point is that going forward, it will be easier to align reporting intervals to some calendar-based interval (e.g., monthly), so that I don't have to regenerate price data every time I produce my returns. Aligning to months is probably good enough for my time horizon. Stock Splits. Beancount does not explicitly make adjustments of prices for stocks that split, so your price source should return the pre-split price of the stock at the time if you are recording it as such. You should be able to check your price time series for errors using Fava.","title":"Filling in Missing Price Points"},{"location":"calculating_portolio_returns.html#currency-conversion","text":"Another important detail is that each investment has its own quote currency. I used to live in Canada, and some of my older investments are denominated in CAD. So the question arises: do I compute the returns in local (CAD) currency, or in terms of my reference currency (USD)? It's convenient that Beancount's Inventory object has functions that can easily perform those conversions where needed. And since the cash flows I extracted are stored using Beancount's Amount object, I already have the quote currencies correctly in my extracted dataset. In any group, if all the instruments have the same quote currency, I report returns in that currency. If the group includes a mix of quote currencies, I further convert everything to USD (so I get USD returns).","title":"Currency Conversion"},{"location":"calculating_portolio_returns.html#reporting","text":"","title":"Reporting"},{"location":"calculating_portolio_returns.html#grouping-accounts","text":"Returns are calculated jointly for each group of accounts in each \"report\", as defined in the configuration. Here are some example groupings that are sensible to define: Same instrument in different accounts. If you buy the same stock in different accounts, it makes sense to report on the returns for that stock jointly, across all your accounts. Same underlying. Some instruments represent the same stock, e.g. GOOG and GOOGL (different share class, same company). Also, IAU and GLD (Gold) are different ETFs whose values are both derived from physical gold reserves (located in bank basements in London). Same asset class. Instruments from the same asset class, e.g. \"metals\", which would include IAU, GLD, SLV, COPX, etc., or \"REITs\", which would include VNQ, VGSLX, etc. Or \"all stocks\" vs. \"all bonds\". By Strategy. In my portfolio investment method, I have a multi-headed approach where I define specific broad strategies and then select a list of instruments to implement it. For instance, I have a \"tech sector\" strategy, which includes FAANG companies. Or a \"growth stock\" strategy, which might include different indexes like VUG, IWO and RGAGX. I can report how well those strategies are doing, across brokers. Or geographically, \"developed APAC\", including EWY, EWT, EWS, EWA. By Broker. I can report returns by broker or broker account. In particular, this can be an easy way to separate realized profits by tax treatment (e.g., 401k is tax-deferred). Asset type. Comparing all index funds to all managed funds (e.g. mutual funds). Note that different reports can include the same investments. Groupings aren't exclusive. You define what makes most sense for your situation. For reference, I use more than 20 reporting groups.","title":"Grouping Accounts"},{"location":"calculating_portolio_returns.html#running-the-code","text":"Simply call ./experiments/returns/compute_returns.py to generate all reports and debugging files, where is in the format shown in \u201cConfiguration\u201d . It's a little slow \u2014 some performance improvements are possible \u2014 but if you supply a list of report names after the final argument, only those investments and reports will be processed, so you can iterate faster that way. See flags with --help for details, and config.proto for the configuration input documentation.","title":"Running the Code"},{"location":"calculating_portolio_returns.html#results-rendered","text":"For each reporting group, I currently produce: A plot of the cash flows over time , and a smaller plot of log (cash flows). Dividend payments are typically dwarfed by cash flows for the principal, so the log chart allows me to see timings. This is mainly used to get an overview of activity over time, and for debugging. A plot of cumulative value, where I render two curves: Cumulative cash flows , with a growth curve matching that returns I regressed over. The result should be a plot with gentle slope between cash flows (corresponding to the total returns growth), with a final drop to zero. Market value over time : A curve of the mark-to-market value of the portfolio over time. This allows me to make some sense of calendar returns, by witnessing how the asset value moves based on price. A table of total returns , returns without dividends, and returns from dividends only. A table of calendar returns for each year, also broken down by total, ex-dividends, dividends-only. (I'll probably render this as a plot in the future.) A table of trailing cumulative returns . This is going to get refined and augmented as I'm actively working on this code [September 2020].","title":"Results Rendered"},{"location":"calculating_portolio_returns.html#example","text":"Here's an example report, for a subset of accounts with a \"growth\" focus, held at different brokers. I produce one of these for each reporting group. (I greyed out parts for privacy.)","title":"Example"},{"location":"calculating_portolio_returns.html#interpretation-gotchas","text":"A few notes are in order: All rates are annualized . This makes it easy to compare numbers to each other, but it also means that positions held for a short amount of time will produce numbers that are unrealistic for long term extrapolation. In particular, new positions entered only a few months ago may be subject to high growth or a big drop, both of which when extrapolated to an entire year may show eye-popping percentages. Do keep this in mind, especially when looking at recent positions added to your portfolio. Taxes aren't factored in. Returns from taxable accounts and tax-deferred accounts should be evaluated differently and if the difference in tax is large, they can't be compared so readily. Do keep in mind that in most countries gains are only taxed on realization (sale) so in effect, long held investments behave more or less like tax-deferred ones. Just don't sell so much. This is a great advantage in holding broadly diversified ETFs (and usually unquantified, as people's attention is still overly focused on those benefits from registered accounts, e.g., 401k plan). Cost basis. Note that nowhere in our calculations was the cost basis used or factored in, so don't confuse it with market value. The cost basis is only useful for tax-related effects.","title":"Interpretation Gotchas"},{"location":"calculating_portolio_returns.html#other-instruments-types","text":"Note that computing returns in this manner isn't limited to only stocks and bonds. Using the same methodology, we can include other types of instruments:","title":"Other Instruments Types"},{"location":"calculating_portolio_returns.html#p2p-lending","text":"I've used LendingClub before all the hedge funds got involved to pick up the low-hanging fruit, and eventually let all the bonds I'd invested in expire. It was easy to apply the same methodology to compute returns from those investments. Originally, I had the discipline to record this investment from monthly PDF statements using transactions like this: 2016-10-31 * \"2016-10-31.Monthly_Statement.pdf\" Assets:US:LendingClub:FundsLent -451.52 LENCLUB {1 USD} Assets:US:LendingClub:Cash 451.52 USD Income:US:LendingClub:LoanInterest -21.68 USD Income:US:LendingClub:Recoveries -5.92 USD Expenses:Financial:Fees 1.08 USD ;; Recovery fees Expenses:Financial:Fees 4.71 USD ;; Service fees Expenses:Financial:Fees 0.45 USD ;; Collection fees Assets:US:LendingClub:Cash Assets:US:LendingClub:FundsLent -23.05 LENCLUB {1 USD} Income:US:LendingClub:ChargedOff 23.05 USD And later on, after the principal bonds expired, transactions like this: 2018-11-30 * \"2018-11-30.Monthly_Statement.pdf\" Income:US:LendingClub:Recoveries -2.73 USD Expenses:Financial:Fees 0.49 USD ;; Recovery fees Assets:US:LendingClub:Cash All it took to compute returns is this configuration for the investment: investment { currency: \"LENCLUB\" asset_account: \"Assets:US:LendingClub:FundsLent\" match_accounts: \"Income:US:LendingClub:Recoveries\" cash_accounts: \"Assets:US:LendingClub:Cash\" } Note that the match account configuration was necessary to pick up crumbs from later transactions with only recoveries (and no posting to the asset account). For what it's worth, I was able to generate a 6.75% return over this investment. Meh.","title":"P2P Lending"},{"location":"calculating_portolio_returns.html#real-estate","text":"Cash flows can be extracted from all transactions for a home, so you can compute the returns for all the money poured into your home, as if it was done purely for investment purposes. Usually buying a home is done for other reasons \u2014 stability for children, the ability to make your own improvements, the forced savings involved in regular principal payments, and oftentimes just a sense of \"having a home\" \u2014 but in the vast majority of the cases, home ownership is more of a cost center and returns would have been better invested elsewhere (see this book for a great expos\u00e9 of the pros & cons). Personally, I have better things to do than fix toilets and worry about leaky windows in the winter so I went back to renting, but I went through the experience once and it was quite worthwhile, as a learning experience but also to experience the \"joy\" of having my own place. Through the exercise is useful to calculate how much having your own home is actually costing you, and how much you might have made by putting the very same cash flows into the market instead. It's a little more involved, because, You will have to have discipline to segregate expenses you wouldn't have had if you'd have rented to accounts specific for your home. You will need to account for equivalent rent received that you didn't have to pay because you lived in the home. If you still own the home, you will have to simulate a substantial agent fee and other closing costs (by inserting a transaction). Most places have a large tax break when selling a property (sometimes the full capital appreciation), so that really needs to be factored in. It's not always simple to calculate, especially if you rented your property for some of its ownership lifetime, in which case you might only be able to deduct a part of the capital gains. For some, there is great value in the optionality of being able to move easily and at low cost (e..g, accepting a new job at the other side of the country) and that personal value will be difficult to estimate. This will require me to do more categorization work, and it will be best documented as a separate project, though using the same code. I did manage to create a simple configuration and extract a 5%-ish number out of it, but I think I'll need a bit more work to declare victory. More to come.","title":"Real Estate"},{"location":"calculating_portolio_returns.html#options","text":"I sometimes hedge some of my portfolio downrisk with long-dated OTM PUT positions. I've sold OTM PUTs and CALLs in the near term to finance it. The method I described for stocks in this document works equally well for options, that is, extracting cash flows from cash. The main differences are that: Currency names. Instrument names are specific to each contract\u2014they include the expiration date and strike price\u2014so I don't store the options to an account whose name includes the instrument name at the leaf. I just use a generic leaf account name, such as \"Options\" or \"Hedging\" which I select when I enter/exit positions. Prices for options. Prices for options aren't as easily fetchable programmatically. I'm having to use a private API for this. Perhaps more importantly, declining theta, varying vol and non-linearity near the strike means I do need to have to have pretty recent price estimates in my ledger in order to compute returns on unrealized gains/losses. I think the effect is strong enough that I'd want to eventually have some code to always update the prices just before generating the reports.","title":"Options"},{"location":"calculating_portolio_returns.html#future-work","text":"This section describes desired future improvements on the returns code, which I'm likely to implement, and the corresponding challenges. Note: As I'm writing this in September 2020, I'm actively working on this code and probably will continue over the next few months' weekends. It's possible this document falls slightly out-of-date and that some of the proposals below become implemented. Refer to the source code for the latest.","title":"Future Work"},{"location":"calculating_portolio_returns.html#relative-size-over-time","text":"Another useful bit of data I could easily add to reports is a stack plot of market values over time, as well as relative fractions of each investment in a report group. This would be useful for monitoring growth of particular instruments in a group to help with rebalancing.","title":"Relative Size over Time"},{"location":"calculating_portolio_returns.html#comparison-against-a-benchmark","text":"One of the important barometers of success in running your own portfolio is a comparison to an equivalent investment of capital in a simple portfolio of diversified stocks and bonds rebalanced regularly. After all, if my returns aren't beating that, in a normal environment, one could make the argument I shouldn't bother with a more complicated allocation. Assuming that my access to capital is the same (which it is, because in this project I'm looking at savings from income), I could simply replace my cash flows by cash flows to this simulated portfolio, in other words, use the same timings to simulate buying other assets. I would have to exclude the dividend payments because those are specific to the particular instruments I used, and at the same time generate simulated dividends from similarly sized positions in the benchmark portfolio. It should be possible to do this without modifying my ledger. One issue is that I will require a set of accurate prices for the benchmark, at the dates of my historical cash flows. Like my script already does for aging price points, this could easily be stored in a file for fetching. Perhaps more complicated is the fact that Beancount does not currently support a database of per-share dividend distributions. This could be added without introducing new syntax by attaching and honoring metadata on Price directives, such as 2020-09-01 price LQD 136.16 USD distribution: 0.295 USD Overall, it wouldn't be too difficult to implement this.","title":"Comparison Against a Benchmark"},{"location":"calculating_portolio_returns.html#including-uninvested-cash","text":"One of the problems I'm facing in investing my own portfolio is the lack of discipline around timely investing of available cash, especially when times are uncertain and difficult decisions have to be made. In order to account for the drag on performance from that, I should include an \"investment\" that's purely reflecting the total amount of uninvested cash over time. Because this varies a lot, a good approximation can be obtained by sampling total amount of investable cash every month and synthesizing cash flows from differences. If that cash flow series gets included as part of a portfolio, it'll suitably drag its returns by diluting them.","title":"Including Uninvested Cash"},{"location":"calculating_portolio_returns.html#after-tax-value","text":"At the moment I export all my holdings to a Google Sheets doc using the export script , and from that, break it down between long-term and short-term positions vs. pre-tax, after-tax, Roth, and taxable buckets. From those 8 aggregates, I remove estimated taxes and report a single \"estimated after-tax net worth\" and corresponding tax liability. This is a rough estimate. The returns report is however much more detailed, and I could simulate tax payments not only on liquidation, but also at the end of each year (from dividends and sales). I have all the lot dates on each position held so I can automatically figure out short-term vs. long-term lots.","title":"After-Tax Value"},{"location":"calculating_portolio_returns.html#inflation-adjustments","text":"The market value amounts reported on the returns charts and calendar returns do not account for inflation. Over long periods of time, those can make an important difference in returns. It would be useful to discount the returns over time using annual estimates for the CPI (or some other estimate of inflation; you could even make up your own, from the expenses in your ledger), so that we can look at a curve of real growth and not just nominal growth.","title":"Inflation Adjustments"},{"location":"calculating_portolio_returns.html#sales-commission","text":"The configuration could easily be improved to let the user specify expected commissions on sales of investments, either in absolute or relative (%) amounts. This would be used to mark positions with more realistic liquidation values. This could make a difference on investments with either small amounts or large commissions (i.e., working through a regular broker, or on real estate).","title":"Sales Commission"},{"location":"calculating_portolio_returns.html#risk-estimation-beta","text":"A perhaps more advanced topic would be to compute an estimate of the variance from the specific portfolio composition in order to calculate and report some measurement of risk, such as Sharpe Ratio . This would require a sufficient number of regularly spaced price points. Variation of the measures over time could be fun too, as well as calculating the current portfolio's specific sensitivity to market as a whole (beta).","title":"Risk Estimation & Beta"},{"location":"calculating_portolio_returns.html#conclusion","text":"I had expected it would be possible to produce a clear picture of returns from Beancount data, and having done it I'm more satisfied with the level of detail and clarity I was to produce from my ledger than I had expected. This goes well beyond plotting of net worth over time, this actually works really well, and I can use it to compare the performance of different investments fairly. I hope at least some Beancount users will be able to run it on their ledgers and I'm looking forward to hearing some feedback from those who set it up. Perhaps most importantly, I was very surprised to see results on my own portfolio returns. I'm one of those people who would normally shrug and guess an underwhelming ballpark number if asked what I thought my returns were, such as \"meh, 6% or something, not super happy.\" Doing this work was originally driven by not having the right answer to this question. It turns out, over the last 15 years, that I've generated an almost 12% average annual return, and 14% annual return in the last 5 years. I haven't yet made the benchmark comparison, and for sure these numbers should be put side-by-side with the market for a fair assessment. Nevertheless, going through the exercise has provided me with renewed confidence and hope about the future, and I hope the clarity it will bring to other Beancount users' own investments will be similarly enlightening.","title":"Conclusion"},{"location":"command_line_accounting_cookbook.html","text":"Command-line Accounting Cookbook \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/cookbook Introduction A Note of Caution Account Naming Conventions Choosing an Account Type Choosing Opening Dates How to Deal with Cash Cash Withdrawals Tracking Cash Expenses Salary Income Employment Income Accounts Booking Salary Deposits Vacation Hours 401k Contributions Vesting Stock Grants Other Benefits Points Food Benefits Currency Transfers & Conversions Investing and Trading Accounts Setup Funds Transfers Making a Trade Receiving Dividends Conclusion Introduction \uf0c1 The best way to learn the double-entry method is to look at real-world examples. The method is elegant, but it can seem unintuitive to the newcomer how transactions have to be posted in order to perform the various operations that one needs to do in counting for different types financial events. This is why I wrote this cookbook. It is not meant to be a comprehensive description of all the features supported, but rather a set of practical guidelines to help you solve problems. I think this will likely be the most useful document in the Beancount documentation set! All the examples here apply to any double-entry accounting system: Ledger, GnuCash, or even commercial systems. Some of the details may differ only slightly. This cookbook is written using the syntax and calculation method of the Beancount software. This document also assumes that you are already familiar with the general balancing concepts of the double-entry method and with at least some of the syntax of Beancount which is available from its user\u2019s manual or its cheat sheet . If you haven\u2019t begun writing down your first file, you will want to read Getting Started with Beancount and do that first. Command-line accounting systems are agnostic about the types of things they can count and allow you to get creative with the kinds of units that you can invent to track various kinds of things. For instance, you can count \u201cIRA contribution dollars,\u201d which are not real dollars, but which correspond to \u201cpossible contributions in real dollars,\u201d and you obtain accounts of assets, income and expenses types for them - it works. Please do realize that some of those clever tricks may not be possible in more traditional accounting systems. In addition, some of the operations that would normally require a manual process in these systems can be automated away for us, e.g., \u201cclosing a year\u201d is entirely done by the software at any point in time, and balance assertions provide a safeguard that allow us to change the details of past transactions with little risk, so there is no need to \u201creconcile\u201d by baking the past into a frozen state. More flexibility is at hand. Finally, if you have a transaction entry problem that is not covered in this document, please do leave a comment in the margin, or write up your problem to the Ledger mailing-list . I would like for this document to cover as many realistic scenarios as possible. A Note of Caution \uf0c1 While reading this, please take note that the author is a dilettante: I am a computer scientist, not an accountant. In fact, apart from a general course I took in college and having completed the first year of a CFA program, I have no real training in accounting. Despite this, I do have some practical experience in maintaining three set of books using this software: my personal ledger (8 years worth of full financial data for all accounts), a joint ledger with my spouse, and the books of a contracting and consulting company I used to own. I also used my double-entry system to communicate with my accountant for many years and he made suggestions. Nevertheless\u2026 I may be making fundamental mistakes here and there, and I would appreciate you leaving a comment in the margin if you find anything dubious. Account Naming Conventions \uf0c1 You can define any account name you like, as long as it begins with one of the five categories: Assets, Liabilities, Income, Expenses, or Equity (note that you can customize those names with options - see the Language Syntax document for details). The accounts names are generally defined to have multiple name components, separated by a colon (:), which imply an accounts hierarchy, or \u201c chart of accounts \u201d: Assets:Component1:Component2:Component3:... Over time, I\u2019ve iterated over many ways of defining my account names and I have converged to the following convention for Assets, Liabilities, and Income accounts: Type : Country : Institution : Account : SubAccount What I like about this is that when you render a balance sheet, the tree that gets rendered nicely displays accounts by country first, then by institution. Some example account names: Assets:US:BofA:Savings ; Bank of America \u201cSavings\u201d account Assets:CA:RBC:Checking ; Royal Bank of Canada \u201cChecking\u201d account Liabilities:US:Amex:Platinum ; American Express Platinum credit card Liabilities:CA:RBC:Mortgage ; Mortgage loan account at RBC Income:US:ETrade:Interest ; Interest payments in E*Trade account Income:US:Acme:Salary ; Salary income from ACME corp. Sometimes I use a further sub-account or two, when it makes sense. For example, Vanguard internally keeps separate accounts depending on whether the contributions were from the employee or the employer\u2019s matching amount: Assets:US:Vanguard:Contrib401k:RGAGX ; My contributions to this fund Assets:US:Vanguard:Match401k:RGAGX ; Employer contributions For investment accounts, I tend organize all their contents by storing each particular type of stock in its own sub-account: Assets:US:ETrade:Cash ; The cash contents of the account Assets:US:ETrade:FB ; Shares of Facebook Assets:US:ETrade:AAPL ; Shares of Apple Assets:US:ETrade:MSFT ; Shares of Microsoft \u2026 This automatically organizes the balance sheet by types of shares, which I find really nice. Another convention that I like is to use the same institution component name when I have different related types of accounts. For instance, the E*Trade assets account above has associated income streams that would be booked under similarly named accounts: Income:US:ETrade:Interest ; Interest income from cash deposits Income:US:ETrade:Dividends ; Dividends received in this account Income:US:ETrade:PnL ; Capital gains or losses from trades \u2026 For \u201cExpenses\u201d accounts, I find that there are generally no relevant institutions. For those it makes more sense to treat them as categories and just have a simple hierarchy that corresponds to the kinds of expenses they count, some examples: Expenses:Sports:Scuba ; All matters of diving expenses Expenses:Transport:Train ; Train (mostly Amtrak, but not always) Expenses:Transport:Bus ; Various \u201cchinese bus\u201d companies Expenses:Transport:Flights ; Flights (various airlines) \u2026 I have a lot of these, like 250 or more. It is really up to you to decide how many to define and how finely to aggregate or \u201ccategorize\u201d your expenses this way. But of course, you should only define them as you need them; don\u2019t bother defining a huge list ahead of time. It\u2019s always easy to add new ones. It is worth noting that the institution does not have to be a \u201creal\u201d institution. For instance, I owned a condo unit in a building, and I used the Loft4530 \u201cinstitution\u201d for all its related accounts: Assets:CA:Loft4530:Property Assets:CA:Loft4530:Association Income:CA:Loft4530:Rental Expenses:Loft4530:Acquisition:Legal Expenses:Loft4530:Acquisition:SaleAgent Expenses:Loft4530:Loan-Interest Expenses:Loft4530:Electricity Expenses:Loft4530:Insurance Expenses:Loft4530:Taxes:Municipal Expenses:Loft4530:Taxes:School If you have all of your business in a single country and have no plans to move to another, you might want to skip the country component for brevity. Finally, for \u201cEquity\u201d accounts, well, \u2026. normally you don\u2019t end up defining many of them, because these are mostly created to report the net income and currency conversions from previous years or the current exercise period on the balance sheet. Typically you will need at least one, and it doesn\u2019t matter much what you name it: Equity:Opening-Balances ; Balances used to open accounts You can customize the name of the other Equity accounts that get automatically created for the balance sheet. Choosing an Account Type \uf0c1 Part of the art of learning what accounts to book transactions to is to come up with relevant account names and design a scheme for how money will flow between those accounts, by jotting down some example transactions. It can get a bit creative. As you\u2019re working out how to \u201ccount\u201d all the financial events in your life, you will often end up wondering what account type to select for some of the accounts. Should this be an \u201cAssets\u201d account? Or an \u201cIncome\u201d account? After all, other than for creating reports, Beancount doesn\u2019t treat any of these account types differently\u2026 But this does not mean that you can just use any type willy nilly. Whether an account appears in the balance sheet or income statement does matter\u2014there is usually a correct choice. When in doubt, here are some guidelines to choose an account type. First, if the amounts to be posted to the account are only relevant to be reported for a period of time , they should be one of the income statement accounts: Income or Expenses. On the other hand, if the amount always needs to be included in the total balance of an account, then it should be a balance sheet account: Assets or Liabilities. Second, if the amounts are generally 1 positive, or \u201cgood from your perspective,\u201d the account should be either an Assets or an Expenses account. If the amounts are generally negative, or \u201cbad from your perspective,\u201d the account should be either a Liabilities or an Income account. Based on these two indicators, you should be able to figure out any case. Let\u2019s work through some examples: A restaurant meal represents something that you obtained in exchange for some assets (cash) or a liability (paid by credit card). Nobody ever cares what the \u201csum total of all food since you were born\u201d amounts to. Only the transitional value matters: \u201cHow much did I spend in restaurants this month ?\u201d Or, since the beginning of the year ? Or, during this trip? This clearly points to an Expenses account. But you might wonder\u2026 this is a positive number, but it is money I spent? Yes, the account that you spent from was subtracted from (debited) in exchange for the expense you received . Think of the numbers in the expenses account as things you received that vanish into the ether right after you receive them. These meals are consumed.. and then they go somewhere. Okay, we\u2019ll stop the analogy here. You own some shares of a bond, and receive an interest payment. This interest is cash deposited in an Assets account, for example, a trading account. What is the other leg to be booked to? Choosing Opening Dates \uf0c1 Some of the accounts you need to define don\u2019t correspond to real world accounts. The Expenses:Groceries account represents the sum total of grocery expenses since you started counting. Personally, I like to use my birth date on those. There\u2019s a rationale to it: it sums all the groceries you\u2019ve ever spent money on, and this started only when you came to this world. You can use this rationale on other accounts. For example, all the income accounts associated with an employer should probably be opened at the date you began the job, and end on the date you left. Makes sense. How to Deal with Cash \uf0c1 Let\u2019s start with cash. I typically define two accounts at my birth date: 1973-04-27 open Assets:Cash 1973-04-27 open Assets:ForeignCash The first account is for active use, this represents my wallet, and usually contains only units of my operating currencies, that is, the commodities I usually think of as \u201ccash.\u201d For me, they are USD and CAD commodities. The second account is meant to hold all the paper bills that I keep stashed in a pocket from trips around the world, so they\u2019re out of the way in some other account and I don\u2019t see them in my cash balance. I transfer those to the main account when I do travel to such places, e.g., if I return to Japan, I\u2019ll move my JPY from Assets:ForeignCash to Assets:Cash right before the trip and use them during that time. Cash Withdrawals \uf0c1 An ATM withdrawal from a checking account to cash will typically look like this: 2014-06-28 * \"DDA WITHDRAW 0609C\" Assets:CA:BofA:Checking -700.00 USD Assets:Cash You would see this transaction be imported in your checking account transactions download. Tracking Cash Expenses \uf0c1 One mistake people make when you tell them you\u2019re tracking all of your financial accounts is to assume that you have to book every single little irrelevant cash transaction to a notebook. Not so! It is your choice to decide how many of these cash transactions to take down (or not). Personally, I try to minimize the amount of manual effort I put into updating my Ledger. My rule for dealing with cash is this: If it is for food or alcohol, I don\u2019t track it. If it is for something else, I keep the receipt and enter it later. This works for me, because the great majority of my cash expenses tend to be food (or maybe I just make it that way by paying for everything else with credit cards). Only a few receipts pile up somewhere on my desk for a couple of months before I bother to type them in. However, you will need to make occasional adjustments to your cash account to account for these expenses. I don\u2019t actually bother doing this very often\u2026 maybe once every three months, when I feel like it. The method I use is to take a snapshot of my wallet (manually, by counting the bills) and enter a corresponding balance assertion: 2014-05-12 balance Assets:Cash 234.13 USD Every time I do this I\u2019ll also add a cash distribution adjusted to balance the account: 2014-06-19 * \"Cash distribution\" Expenses:Food:Restaurant 402.30 USD Expenses:Food:Alcohol 100.00 USD Assets:Cash ; -502.30 USD 2014-06-20 balance Assets:Cash 194.34 USD If you wonder why the amounts in the cash account don\u2019t add up (234.13 -502.30 \u2260 194.34), it is because between the two assertions I added to the cash account by doing some ATM withdrawals against the checking account, and those appear somewhere else (in the checking account section). The withdrawal increased the balance of the cash account. It would appear if I rendered a journal for Assets:Cash . I could have made my life simpler and used a Pad directive if I had booked everything to food\u2014pad entries don\u2019t work solely at the beginning of an account\u2019s history, but also between any two balance assertions on the same account\u2014but I want to book 80% of it to food and 20% alcohol, to more accurately represent my real usage of cash 2 . Finally, if you end up with a long time period between the times that you do this, you may want to \u201cspread out\u201d your expenses by adding more than one cash distribution 3 manually, so that if you generate a monthly report, a large cash expense does not appear as a single lump in or outside that month. Salary Income \uf0c1 Accounting for your salary is rewarding: you will be able to obtain a summary of income earned during the year as well as the detail of where the various deductions are going, and you will enjoy the satisfaction of seeing matching numbers from your Beancount reports when you receive your W-2 form from your employer (or on your T4 if you\u2019re located in Canada). I put all entries related to an employer in their own dedicated section. I start it by setting an event to the date I began working there, for example, using the hypothetical company \u201cHooli\u201d (from the Silicon Valley show): 2012-12-13 event \"employer\" \"Hooli Inc.\" This allows me to automatically calculate the number of days I\u2019ve been working there. When I leave a job, I\u2019ll change it to the new one, or an empty string, if I don\u2019t leave for another job: 2019-03-02 event \"employer\" \"\" This section will make several assumptions. The goal is to expose you to the various ideas you can use to account for your income correctly. You will almost certainly end up having to adapt these ideas to your specific situation. Employment Income Accounts \uf0c1 Then you define accounts for your pay stubs. You need to make sure that you have an account corresponding to each line of your pay stub. For example, here are some of the income accounts I define for this employment income at Hooli Inc.: 2012-12-13 open Income:US:Hooli:Salary USD ; \"Regular Pay\" 2012-12-13 open Income:US:Hooli:ReferralBonus USD ; \"Referral bonuses\" 2012-12-13 open Income:US:Hooli:AnnualBonus USD ; \"Annual bonus\" 2012-12-13 open Income:US:Hooli:Match401k USD ; \"Employer 401k match\" 2012-12-13 open Income:US:Hooli:GroupTermLife USD ; \"Group Term Life\" These correspond to regular salary pay, bonuses received for employee referrals, annual bonus, receipts for 401k (Hooli in this example will match some percentage of your contributions to your retirement account), receipts for life insurance (it appears both as an income and an expense), and benefits paid for your gym subscription. There are more, but this is a good example. (In this example I wrote down the names used in the stub as a comment, but you could insert them as metadata instead if you prefer.) You will need to book taxes withheld at the source to accounts for that year (see the tax section for details on this): 2014-01-01 open Expenses:Taxes:TY2014:US:Medicare USD 2014-01-01 open Expenses:Taxes:TY2014:US:Federal USD 2014-01-01 open Expenses:Taxes:TY2014:US:StateNY USD 2014-01-01 open Expenses:Taxes:TY2014:US:CityNYC USD 2014-01-01 open Expenses:Taxes:TY2014:US:SDI USD 2014-01-01 open Expenses:Taxes:TY2014:US:SocSec USD These accounts are for Medicare taxes, Federal, New York State and NYC taxes (yes, New York City residents have an additional tax on top of state tax), state disability insurance (SDI) payments, and finally, taxes to pay for social security. You will also need to have some accounts defined elsewhere for the various expenses that are paid automatically from your pay: 2012-12-13 open Expenses:Health:Life:GroupTermLife USD ; \"Life Ins.\" 2012-12-13 open Expenses:Health:Dental:Insurance USD ; \"Dental\" 2012-12-13 open Expenses:Health:Medical:Insurance USD ; \"Medical\" 2012-12-13 open Expenses:Health:Vision:Insurance USD ; \"Vision\" 2012-12-13 open Expenses:Internet:Reimbursement USD ; \"Internet Reim\" 2012-12-13 open Expenses:Transportation:PreTax USD ; \"Transit PreTax\" These correspond to typical company group plan life insurance payments, premiums for dental, medical and vision insurances, reimbursements for home internet usage, and pre-tax payments for public transit (the city of New York allows you to pay for your MetroCard with pre-tax money through your employer). Booking Salary Deposits \uf0c1 Then, when I import details for a payment via direct deposit to my checking account, it will look like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD If I haven\u2019t received my pay stub yet, I might book it temporarily to the salary account until I do: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD ! Income:US:Hooli:Salary When I receive or fetch my pay stub, I remove this and complete the rest of the postings. A realistic entry for a gross salary of $140,000 would look something like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD Income:US:Hooli:GroupTermLife -25.38 USD Income:US:Hooli:Salary -5384.62 USD Expenses:Health:Dental:Insurance 2.88 USD Expenses:Health:Life:GroupTermLife 25.38 USD Expenses:Internet:Reimbursement -34.65 USD Expenses:Health:Medical:Insurance 36.33 USD Expenses:Transportation:PreTax 56.00 USD Expenses:Health:Vision:Insurance 0.69 USD Expenses:Taxes:TY2014:US:Medicare 78.08 USD Expenses:Taxes:TY2014:US:Federal 1135.91 USD Expenses:Taxes:TY2014:US:CityNYC 75.03 USD Expenses:Taxes:TY2014:US:SDI 1.20 USD Expenses:Taxes:TY2014:US:StateNY 340.06 USD Expenses:Taxes:TY2014:US:SocSec 328.42 USD It\u2019s quite unusual for a salary payment to have no variation at all from its previous one: rounding up or down from the payroll processor will often result in a difference of a penny, social security payments will cap to their maximum, and there are various deductions that will occur from time to time, e.g., deductions on taxable benefits received. Moreover, contributions to a 401k will affect that amounts of taxes withheld at the source. Therefore, you end up having to look at each pay stub individually to enter its information correctly. But this is not as time-consuming as it sounds! Here\u2019s a trick: it\u2019s a lot easier to update your transactions if you list your postings in the same order as they appear on your pay stub. You just copy-paste the previous entry, read the pay stub from top to bottom and adjust the numbers accordingly. It takes a minute for each. It\u2019s worth noting some unusual things about the previous entry. The \u201cgroup term life\u201d entry has both a $25.38 income leg and an expense one. This is because Hooli pays for the premium (it reads exactly like that on the stubs.) Hooli also reimburses some of home internet, because I use it to deal with production issues. This appears as a negative posting to reduce the amount of my expense Expenses:Internet account. Vacation Hours \uf0c1 Our pay stubs also include accrued vacation and the total vacation balance, in vacation hours. You can also track these amounts on the same transactions. You need to declare corresponding accounts: 2012-12-13 open Income:US:Hooli:Vacation VACHR 2012-12-13 open Assets:US:Hooli:Vacation VACHR 2012-12-13 open Expenses:Vacation:Hooli VACHR Vacation that accrues is something you receive and thus is treated as Income in units of \u201cVACHR\u201d, and accumulates in an Assets account, which holds how many of these hours you currently have available to \u201cspend\u201d as time off. Updating the previous salary income transaction entry: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Hooli:Vacation 4.62 VACHR Income:US:Hooli:Vacation -4.62 VACHR 4.62 VACHR on a bi-weekly paycheck 26 times per year is 26 x 4.62 ~= 120 hours. At 8 hours per day, that is 15 work days, or 3 weeks, which is a standard vacation package for new US Hooligans in this example. When you do take time off, you book an expense against your accumulated vacation time: 2014-06-17 * \"Going to the beach today\" Assets:US:Hooli:Vacation -8 VACHR Expenses:Vacation:Hooli The Expenses account tracks how much vacation you\u2019ve used. From time to time you can check that the balance reported on your pay stub\u2014the amount of vacation left that your employer thinks you have\u2014is the same as that which you have accounted for: 2014-02-29 balance Assets:US:Hooli:Vacation 112.3400 VACHR You can \u201cprice\u201d your vacation hour units to your hourly rate, so that your vacations Assets account shows how much the company would pay you if you decided to quit. Assuming that $140,000/year salary, 40 hour weeks and 50 weeks of work, which is 2000 hours per year, we obtain a rate of $70/hour, which you enter like this: 2012-12-13 price VACHR 70.00 USD Similarly, if your vacation hours expires or caps, you can calculate how much dollar-equivalent you\u2019re forfeiting by working too much and giving up your vacation time. You would write off some of the VACHR from your Assets account into an income account (representing losses). 401k Contributions \uf0c1 The 401k plan allows you to make contributions to a tax-deferred retirement account using pre-tax dollars. This is carried out via withholdings from your pay. To account for those, you simply include a posting with the corresponding contribution towards your retirement account: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD \u2026 If you\u2019re accounting for your available contributions (see the tax section of this document), you will want to reduce your \u201c401k contribution\u201d Assets account at the same time. You would add two more postings to the transaction: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD Assets:US:Federal:PreTax401k -1000.00 US401K Expenses:Taxes:TY2014:US:Federal:PreTax401k 1000.00 US401K \u2026 If your employer matches your contributions, this may not show on your pay stubs. Because these contributions are not taxable\u2014they are deposited directly to a tax-deferred account\u2014your employer does not have to include them in the withholding statement. You will see them appear directly in your investment account as deposits. You can book them like this to the retirement account\u2019s tax balance: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:Cash 1173.08 USD And then insert a second transaction when you invest this case, or directly purchasing assets from the contribution if you have specified an asset allocation and this is automated by the broker: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:VMBPX 106.741 VMBPX {10.99 USD} Note that the fund that manages your 401k accounts may be tracking your contributions and your employer\u2019s contributions in separate buckets. You would declare sub-accounts for this and make the corresponding changes: 2012-12-13 open Assets:US:Vanguard:PreTax401k:VMBPX VMBPX 2012-12-13 open Assets:US:Vanguard:Match401k:VMBPX VMBPX It is common for them to do this in order to track each source of contribution separately, because there are several constraints on rollovers to other accounts that depend on it. Vesting Stock Grants \uf0c1 See the dedicated document on this topic for more details. Other Benefits \uf0c1 You can go crazy with tracking benefits if you want. Here are a few wild ideas. Points \uf0c1 If your employer offers a sponsored massage therapy program on-site, you could presumably book a massage out of your paycheck or even from some internal website (if the company is modern), and you could pay for them using some sort of internal points system, say, \u201cHooli points\u201d. You could track those using a made-up currency, e.g., \u201cMASSA\u2019s\u201d and which could be priced at 0.50 USD, the price at which you could purchase them: 2012-12-13 open Assets:US:Hooli:Massage MASSA 2012-12-13 price MASSA 0.50 USD When I purchase new massage points, I 2013-03-15 * \"Buying points for future massages\" Liabilities:US:BofA:CreditCard -45.00 USD Assets:US:Hooli:Massage 90 MASSA {0.50 USD} If you\u2019re occasionally awarded some of these points, and you can track that in an Income account. Food Benefits \uf0c1 Like many of the larger technology companies, Hooli presumably provides free food for its employees. This saves time and encourages people to eat healthy. This is a bit of a trend in the tech world right now. This benefit does not show up anywhere, but if you want to price it as part of your compensation package, you can track it using an Income account: 2012-12-13 open Income:US:Hooli:Food Depending on how often you end up eating at work, you could guesstimate some monthly allowance per month: 2013-06-30 * \"Distribution for food eaten at Hooli\" Income:US:Hooli:Food -350 USD Expenses:Food:Restaurant Currency Transfers & Conversions \uf0c1 If you convert between currencies, such as when performing an international transfer between banks, you need to provide the exchange rate to Beancount. It looks like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF The balance amount of the second posting is calculated as 10,000.00 USD x 0.90 CHF/USD = 9,000 CHF, and the transaction balances. Depending on your preference, you could have placed the rate on the other posting, like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF @ 1.11111 USD Assets:US:BofA:Checking 10000.00 USD The balance amount of the first posting is calculated as -9000.00 CHF x 1.11111 USD/CHF = 10000.00 USD 4 . Typically I will choose the rate that was reported to me and put it on the corresponding side. You may also want to use the direction that F/X markets use for trading the rate, for example, the Swiss franc trades as USD/CHF, so I would prefer the first transaction. The price database converts the rates in both directions, so it is not that important 5 . If you use wire transfers, which is typical for this type of money transfer, you might incur a fee: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9025.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF Expenses:Fees:Wires 25.00 CHF If you convert cash at one of these shady-looking currency exchange parlors found in tourist locations, it might look like this: 2014-03-03 * \"Changed some cash at the airport in Lausanne\" Assets:Cash -400.00 USD @ 0.90 CHF Assets:Cash 355.00 CHF Expenses:Fees:Services 5.00 CHF In any case, you should never convert currency units using the cost basis syntax, because the original conversion rate needs to be forgotten after depositing the units, and not kept around attached to simple currency. For example, this would be incorrect usage: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD {0.90 CHF} ; <-bad! If you did that by mistake, you would incur errors when you attempted to use the newly USD deposited: Beancount would require that you specify the cost of these \u201cUSD\u201d in CHF, e.g., \u201cdebit from my USD that I changed at 0.90 USD/CHF\u201d. Nobody does this in the real world, and neither should you when you represent your transactions: once the money has converted, it\u2019s just money in a different currency, with no associated cost. Finally, a rather subtle problem is that using these price conversions back and forth at different rates over time breaks the accounting equation to some extent: changes in exchange rate may create small amounts of money out of thin air and all the balances don\u2019t end up summing up to zero. However, this is not a problem, because Beancount implements an elegant solution to automatically correct for this problem, so you can use these conversions freely without having to worry about this: it inserts a special conversions entry on the balance sheet to invert the cumulative effect of conversions for the report and obtain a clean balance of zero. (A discussion of the conversions problem is beyond the scope of this cookbook; please refer to Solving the Conversions Problem if you\u2019d like to know more.) Investing and Trading \uf0c1 Tracking trades and associated gains is a fairly involved topic. You will find a more complete introduction to profit and loss and a detailed discussion of various scenarios in the Trading with Beancount document, which is dedicated to this topic. Here we will discuss how to setup your account and provide simple example transactions to get you started. Accounts Setup \uf0c1 You should create an account prefix to root various sub-accounts associated with your investment account. Say you have an account at ETrade, this could be \u201c Assets:US:ETrade \u201d. Choose an appropriate institution name. Your investment account will have a cash component. You should create a dedicated sub-account will represent uninvested cash deposits: 2013-02-01 open Assets:US:ETrade:Cash USD I recommend that you further define a sub-account for each of the commodity types that you will invest in. Although this is not strictly necessary\u2014Beancount accounts may contain any number of commodities\u2014it is a nice way to aggregate all the positions in that commodity together for reporting. Say you will buy shares of LQD and BND, two popular bond ETFs: 2013-02-01 open Assets:US:ETrade:LQD LQD 2013-02-01 open Assets:US:ETrade:BND BND This also helps produce nicer reports: balances are often shown at cost and it\u2019s nice to see the total cost aggregated by commodity for various reasons (i.e., each commodity provides exposure to different market characteristics). Using a dedicated sub-account for each commodity held within an institution is a good way to do that. Unless you have specific reasons not to do so, I highly suggest sticking with this by default (you can always change it later by renaming accounts). Specifying commodity constraints on your accounts will help you detect data entry mistakes. Stock trades tend to be a bit more involved than regular transactions, and this is certainly going to be helpful. Then, you will hopefully receive income in this account, in two forms: capital gains, or \u201cP&L\u201d, and dividends. I like to account for these by institution, because this is how they have to be declared for taxes. You may also receive interest income. Define these: 2013-02-01 open Income:US:ETrade:PnL USD 2013-02-01 open Income:US:ETrade:Dividends USD 2013-02-01 open Income:US:ETrade:Interest USD Finally, to account for transaction fees and commissions, you will need some general accounts to receive these: 1973-04-27 open Expenses:Financial:Fees 1973-04-27 open Expenses:Financial:Commissions Funds Transfers \uf0c1 You will normally add some initial money in this account by making a transfer from an external account, say, a checking account: 2014-02-04 * \"Transferring money for investing\" Assets:US:BofA:Checking -2000.00 USD Assets:US:ETrade:Cash 2000.00 USD Making a Trade \uf0c1 Buying stock should have a posting that deposits the new commodity in the commodity\u2019s sub-account, and debits the cash account to the corresponding amounts plus commissions: 2014-02-16 * \"Buying some LQD\" Assets:US:ETrade:LQD 10 LQD {119.24 USD} Assets:US:ETrade:Cash -1199.35 USD Expenses:Financial:Commissions 6.95 USD Note that when you\u2019re buying units of a commodity, you are establishing a new trade lot in the account\u2019s inventory and it is necessary that you provide the cost of each unit (in this example, 119.24 USD per share of LQD). This allows us to account for capital gains correctly. Selling some of the same stock work similarly, except that an extra posting is added to absorb the capital gain or loss: 2014-02-16 * \"Selling some LQD\" Assets:US:ETrade:LQD -5 LQD {119.24 USD} @ 123.40 USD Assets:US:ETrade:Cash 610.05 USD Expenses:Financial:Commissions 6.95 USD Income:US:Etrade:PnL Note that the postings of shares removed from the Assets:US:ETrade:LQD account is a lot reduction and you must provide information to identify which lot you\u2019re reducing, in this case, by providing the per-share cost basis of 119.24 USD. I normally let Beancount calculate the capital gain or loss for me, which is why I don\u2019t specify it in the last posting. Beancount will automatically balance the transaction by setting the amount of this posting to -20.80 USD, which is a gain of 20.80 USD (remember that the signs are inverted for income accounts). Specifying the sale price of 123.40 USD is optional, and it is ignored for the purpose of balancing the transaction, the cash deposit and commissions legs determine the profit. Receiving Dividends \uf0c1 Receiving dividends takes on two forms. First, you can receive dividends in cash, which will go into the cash account: 2014-02-16 * \"Dividends from LQD\" Income:US:ETrade:Dividends -87.45 USD Assets:US:ETrade:Cash 87.45 USD Note that the source of the dividends isn\u2019t specified here. You could use a sub-account of the income account to count it separately. Or you can receive dividends in shares reinvested: 2014-06-27 * \"Dividends reinvested\" Assets:US:Vanguard:VBMPX 1.77400 VBMPX {10.83 USD} Income:US:Vanguard:Dividends -19.21 USD This is booked similarly to a stock purchase, and you also have to provide the cost basis of the received units. This would typically happen in a non-taxable retirement account. Refer to the Trading with Beancount document for a more thorough discussion and numerous and more complex examples. Choosing a Date \uf0c1 Buying or selling a single lot of stock typically involves multiple events over time: the trade is placed, the trade is filled (usually on the same day), the trade is settled. Settlement usually occurs 2 or 3 business days after the trade is filled. For simplicity, I recommend using the trade date as the date of your transaction. In the US, this is the date that is recognized for tax purposes, and settlement has no impact on your account (brokers typically won\u2019t allow you to trade without the corresponding cash or margin anyhow). So normally I don\u2019t bother creating separate entries for settlement, it\u2019s not very useful. More complex schemes can be envisioned, e.g. you could store the settlement date as a metadata field and then use it in scripts later on, but that\u2019s beyond the scope of this document. Conclusion \uf0c1 This document is incomplete. I have many more example use cases that I\u2019m planning to add here as I complete them. I will be announcing those on the mailing-list as they materialize. In particular, the following topics will be discussed: Health Care Expenses, e.g., insurance premiums and rebates Taxes IRAs, 401k and other tax-deferred accounts Real Estate Options This is not strictly always true: in accounting for companies, some account types are held at their opposite value for reasons, usually to offset the value of another account of the same type. These are called \u201ccontra\u201d accounts. But as an individual, you\u2019re quite unlikely to have one of those. If you\u2019re setting up a chart of accounts for a company, Beancount doesn\u2019t actually care whether the balance is of one sign or other. You declare contra-accounts just like regular accounts. \u21a9 I am considering supporting an extended version of the Pad directive that can take a percentage value and make it possible to pad only a percentage of the full amount, to automate this. \u21a9 Yet another extension to Beancount involves support multiple Pad directives between two balance assertions and automatically support this spreading out of padding directives. \u21a9 If you\u2019re concerned about the issue of precision or rounding in balancing, see this document . \u21a9 Note that if the price database needs to invert the date its calculation may result in a price with a large number of digits. Beancount uses IEEE decimal objects and the default context of the Python implementation is 28 digits, so inverting 0.9 will result in 1.111111\u2026.111 with 28 digits. \u21a9","title":"Command Line Accounting Cookbook"},{"location":"command_line_accounting_cookbook.html#command-line-accounting-cookbook","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/cookbook Introduction A Note of Caution Account Naming Conventions Choosing an Account Type Choosing Opening Dates How to Deal with Cash Cash Withdrawals Tracking Cash Expenses Salary Income Employment Income Accounts Booking Salary Deposits Vacation Hours 401k Contributions Vesting Stock Grants Other Benefits Points Food Benefits Currency Transfers & Conversions Investing and Trading Accounts Setup Funds Transfers Making a Trade Receiving Dividends Conclusion","title":"Command-line Accounting Cookbook"},{"location":"command_line_accounting_cookbook.html#introduction","text":"The best way to learn the double-entry method is to look at real-world examples. The method is elegant, but it can seem unintuitive to the newcomer how transactions have to be posted in order to perform the various operations that one needs to do in counting for different types financial events. This is why I wrote this cookbook. It is not meant to be a comprehensive description of all the features supported, but rather a set of practical guidelines to help you solve problems. I think this will likely be the most useful document in the Beancount documentation set! All the examples here apply to any double-entry accounting system: Ledger, GnuCash, or even commercial systems. Some of the details may differ only slightly. This cookbook is written using the syntax and calculation method of the Beancount software. This document also assumes that you are already familiar with the general balancing concepts of the double-entry method and with at least some of the syntax of Beancount which is available from its user\u2019s manual or its cheat sheet . If you haven\u2019t begun writing down your first file, you will want to read Getting Started with Beancount and do that first. Command-line accounting systems are agnostic about the types of things they can count and allow you to get creative with the kinds of units that you can invent to track various kinds of things. For instance, you can count \u201cIRA contribution dollars,\u201d which are not real dollars, but which correspond to \u201cpossible contributions in real dollars,\u201d and you obtain accounts of assets, income and expenses types for them - it works. Please do realize that some of those clever tricks may not be possible in more traditional accounting systems. In addition, some of the operations that would normally require a manual process in these systems can be automated away for us, e.g., \u201cclosing a year\u201d is entirely done by the software at any point in time, and balance assertions provide a safeguard that allow us to change the details of past transactions with little risk, so there is no need to \u201creconcile\u201d by baking the past into a frozen state. More flexibility is at hand. Finally, if you have a transaction entry problem that is not covered in this document, please do leave a comment in the margin, or write up your problem to the Ledger mailing-list . I would like for this document to cover as many realistic scenarios as possible.","title":"Introduction"},{"location":"command_line_accounting_cookbook.html#a-note-of-caution","text":"While reading this, please take note that the author is a dilettante: I am a computer scientist, not an accountant. In fact, apart from a general course I took in college and having completed the first year of a CFA program, I have no real training in accounting. Despite this, I do have some practical experience in maintaining three set of books using this software: my personal ledger (8 years worth of full financial data for all accounts), a joint ledger with my spouse, and the books of a contracting and consulting company I used to own. I also used my double-entry system to communicate with my accountant for many years and he made suggestions. Nevertheless\u2026 I may be making fundamental mistakes here and there, and I would appreciate you leaving a comment in the margin if you find anything dubious.","title":"A Note of Caution"},{"location":"command_line_accounting_cookbook.html#account-naming-conventions","text":"You can define any account name you like, as long as it begins with one of the five categories: Assets, Liabilities, Income, Expenses, or Equity (note that you can customize those names with options - see the Language Syntax document for details). The accounts names are generally defined to have multiple name components, separated by a colon (:), which imply an accounts hierarchy, or \u201c chart of accounts \u201d: Assets:Component1:Component2:Component3:... Over time, I\u2019ve iterated over many ways of defining my account names and I have converged to the following convention for Assets, Liabilities, and Income accounts: Type : Country : Institution : Account : SubAccount What I like about this is that when you render a balance sheet, the tree that gets rendered nicely displays accounts by country first, then by institution. Some example account names: Assets:US:BofA:Savings ; Bank of America \u201cSavings\u201d account Assets:CA:RBC:Checking ; Royal Bank of Canada \u201cChecking\u201d account Liabilities:US:Amex:Platinum ; American Express Platinum credit card Liabilities:CA:RBC:Mortgage ; Mortgage loan account at RBC Income:US:ETrade:Interest ; Interest payments in E*Trade account Income:US:Acme:Salary ; Salary income from ACME corp. Sometimes I use a further sub-account or two, when it makes sense. For example, Vanguard internally keeps separate accounts depending on whether the contributions were from the employee or the employer\u2019s matching amount: Assets:US:Vanguard:Contrib401k:RGAGX ; My contributions to this fund Assets:US:Vanguard:Match401k:RGAGX ; Employer contributions For investment accounts, I tend organize all their contents by storing each particular type of stock in its own sub-account: Assets:US:ETrade:Cash ; The cash contents of the account Assets:US:ETrade:FB ; Shares of Facebook Assets:US:ETrade:AAPL ; Shares of Apple Assets:US:ETrade:MSFT ; Shares of Microsoft \u2026 This automatically organizes the balance sheet by types of shares, which I find really nice. Another convention that I like is to use the same institution component name when I have different related types of accounts. For instance, the E*Trade assets account above has associated income streams that would be booked under similarly named accounts: Income:US:ETrade:Interest ; Interest income from cash deposits Income:US:ETrade:Dividends ; Dividends received in this account Income:US:ETrade:PnL ; Capital gains or losses from trades \u2026 For \u201cExpenses\u201d accounts, I find that there are generally no relevant institutions. For those it makes more sense to treat them as categories and just have a simple hierarchy that corresponds to the kinds of expenses they count, some examples: Expenses:Sports:Scuba ; All matters of diving expenses Expenses:Transport:Train ; Train (mostly Amtrak, but not always) Expenses:Transport:Bus ; Various \u201cchinese bus\u201d companies Expenses:Transport:Flights ; Flights (various airlines) \u2026 I have a lot of these, like 250 or more. It is really up to you to decide how many to define and how finely to aggregate or \u201ccategorize\u201d your expenses this way. But of course, you should only define them as you need them; don\u2019t bother defining a huge list ahead of time. It\u2019s always easy to add new ones. It is worth noting that the institution does not have to be a \u201creal\u201d institution. For instance, I owned a condo unit in a building, and I used the Loft4530 \u201cinstitution\u201d for all its related accounts: Assets:CA:Loft4530:Property Assets:CA:Loft4530:Association Income:CA:Loft4530:Rental Expenses:Loft4530:Acquisition:Legal Expenses:Loft4530:Acquisition:SaleAgent Expenses:Loft4530:Loan-Interest Expenses:Loft4530:Electricity Expenses:Loft4530:Insurance Expenses:Loft4530:Taxes:Municipal Expenses:Loft4530:Taxes:School If you have all of your business in a single country and have no plans to move to another, you might want to skip the country component for brevity. Finally, for \u201cEquity\u201d accounts, well, \u2026. normally you don\u2019t end up defining many of them, because these are mostly created to report the net income and currency conversions from previous years or the current exercise period on the balance sheet. Typically you will need at least one, and it doesn\u2019t matter much what you name it: Equity:Opening-Balances ; Balances used to open accounts You can customize the name of the other Equity accounts that get automatically created for the balance sheet.","title":"Account Naming Conventions"},{"location":"command_line_accounting_cookbook.html#choosing-an-account-type","text":"Part of the art of learning what accounts to book transactions to is to come up with relevant account names and design a scheme for how money will flow between those accounts, by jotting down some example transactions. It can get a bit creative. As you\u2019re working out how to \u201ccount\u201d all the financial events in your life, you will often end up wondering what account type to select for some of the accounts. Should this be an \u201cAssets\u201d account? Or an \u201cIncome\u201d account? After all, other than for creating reports, Beancount doesn\u2019t treat any of these account types differently\u2026 But this does not mean that you can just use any type willy nilly. Whether an account appears in the balance sheet or income statement does matter\u2014there is usually a correct choice. When in doubt, here are some guidelines to choose an account type. First, if the amounts to be posted to the account are only relevant to be reported for a period of time , they should be one of the income statement accounts: Income or Expenses. On the other hand, if the amount always needs to be included in the total balance of an account, then it should be a balance sheet account: Assets or Liabilities. Second, if the amounts are generally 1 positive, or \u201cgood from your perspective,\u201d the account should be either an Assets or an Expenses account. If the amounts are generally negative, or \u201cbad from your perspective,\u201d the account should be either a Liabilities or an Income account. Based on these two indicators, you should be able to figure out any case. Let\u2019s work through some examples: A restaurant meal represents something that you obtained in exchange for some assets (cash) or a liability (paid by credit card). Nobody ever cares what the \u201csum total of all food since you were born\u201d amounts to. Only the transitional value matters: \u201cHow much did I spend in restaurants this month ?\u201d Or, since the beginning of the year ? Or, during this trip? This clearly points to an Expenses account. But you might wonder\u2026 this is a positive number, but it is money I spent? Yes, the account that you spent from was subtracted from (debited) in exchange for the expense you received . Think of the numbers in the expenses account as things you received that vanish into the ether right after you receive them. These meals are consumed.. and then they go somewhere. Okay, we\u2019ll stop the analogy here. You own some shares of a bond, and receive an interest payment. This interest is cash deposited in an Assets account, for example, a trading account. What is the other leg to be booked to?","title":"Choosing an Account Type"},{"location":"command_line_accounting_cookbook.html#choosing-opening-dates","text":"Some of the accounts you need to define don\u2019t correspond to real world accounts. The Expenses:Groceries account represents the sum total of grocery expenses since you started counting. Personally, I like to use my birth date on those. There\u2019s a rationale to it: it sums all the groceries you\u2019ve ever spent money on, and this started only when you came to this world. You can use this rationale on other accounts. For example, all the income accounts associated with an employer should probably be opened at the date you began the job, and end on the date you left. Makes sense.","title":"Choosing Opening Dates"},{"location":"command_line_accounting_cookbook.html#how-to-deal-with-cash","text":"Let\u2019s start with cash. I typically define two accounts at my birth date: 1973-04-27 open Assets:Cash 1973-04-27 open Assets:ForeignCash The first account is for active use, this represents my wallet, and usually contains only units of my operating currencies, that is, the commodities I usually think of as \u201ccash.\u201d For me, they are USD and CAD commodities. The second account is meant to hold all the paper bills that I keep stashed in a pocket from trips around the world, so they\u2019re out of the way in some other account and I don\u2019t see them in my cash balance. I transfer those to the main account when I do travel to such places, e.g., if I return to Japan, I\u2019ll move my JPY from Assets:ForeignCash to Assets:Cash right before the trip and use them during that time.","title":"How to Deal with Cash"},{"location":"command_line_accounting_cookbook.html#cash-withdrawals","text":"An ATM withdrawal from a checking account to cash will typically look like this: 2014-06-28 * \"DDA WITHDRAW 0609C\" Assets:CA:BofA:Checking -700.00 USD Assets:Cash You would see this transaction be imported in your checking account transactions download.","title":"Cash Withdrawals"},{"location":"command_line_accounting_cookbook.html#tracking-cash-expenses","text":"One mistake people make when you tell them you\u2019re tracking all of your financial accounts is to assume that you have to book every single little irrelevant cash transaction to a notebook. Not so! It is your choice to decide how many of these cash transactions to take down (or not). Personally, I try to minimize the amount of manual effort I put into updating my Ledger. My rule for dealing with cash is this: If it is for food or alcohol, I don\u2019t track it. If it is for something else, I keep the receipt and enter it later. This works for me, because the great majority of my cash expenses tend to be food (or maybe I just make it that way by paying for everything else with credit cards). Only a few receipts pile up somewhere on my desk for a couple of months before I bother to type them in. However, you will need to make occasional adjustments to your cash account to account for these expenses. I don\u2019t actually bother doing this very often\u2026 maybe once every three months, when I feel like it. The method I use is to take a snapshot of my wallet (manually, by counting the bills) and enter a corresponding balance assertion: 2014-05-12 balance Assets:Cash 234.13 USD Every time I do this I\u2019ll also add a cash distribution adjusted to balance the account: 2014-06-19 * \"Cash distribution\" Expenses:Food:Restaurant 402.30 USD Expenses:Food:Alcohol 100.00 USD Assets:Cash ; -502.30 USD 2014-06-20 balance Assets:Cash 194.34 USD If you wonder why the amounts in the cash account don\u2019t add up (234.13 -502.30 \u2260 194.34), it is because between the two assertions I added to the cash account by doing some ATM withdrawals against the checking account, and those appear somewhere else (in the checking account section). The withdrawal increased the balance of the cash account. It would appear if I rendered a journal for Assets:Cash . I could have made my life simpler and used a Pad directive if I had booked everything to food\u2014pad entries don\u2019t work solely at the beginning of an account\u2019s history, but also between any two balance assertions on the same account\u2014but I want to book 80% of it to food and 20% alcohol, to more accurately represent my real usage of cash 2 . Finally, if you end up with a long time period between the times that you do this, you may want to \u201cspread out\u201d your expenses by adding more than one cash distribution 3 manually, so that if you generate a monthly report, a large cash expense does not appear as a single lump in or outside that month.","title":"Tracking Cash Expenses"},{"location":"command_line_accounting_cookbook.html#salary-income","text":"Accounting for your salary is rewarding: you will be able to obtain a summary of income earned during the year as well as the detail of where the various deductions are going, and you will enjoy the satisfaction of seeing matching numbers from your Beancount reports when you receive your W-2 form from your employer (or on your T4 if you\u2019re located in Canada). I put all entries related to an employer in their own dedicated section. I start it by setting an event to the date I began working there, for example, using the hypothetical company \u201cHooli\u201d (from the Silicon Valley show): 2012-12-13 event \"employer\" \"Hooli Inc.\" This allows me to automatically calculate the number of days I\u2019ve been working there. When I leave a job, I\u2019ll change it to the new one, or an empty string, if I don\u2019t leave for another job: 2019-03-02 event \"employer\" \"\" This section will make several assumptions. The goal is to expose you to the various ideas you can use to account for your income correctly. You will almost certainly end up having to adapt these ideas to your specific situation.","title":"Salary Income"},{"location":"command_line_accounting_cookbook.html#employment-income-accounts","text":"Then you define accounts for your pay stubs. You need to make sure that you have an account corresponding to each line of your pay stub. For example, here are some of the income accounts I define for this employment income at Hooli Inc.: 2012-12-13 open Income:US:Hooli:Salary USD ; \"Regular Pay\" 2012-12-13 open Income:US:Hooli:ReferralBonus USD ; \"Referral bonuses\" 2012-12-13 open Income:US:Hooli:AnnualBonus USD ; \"Annual bonus\" 2012-12-13 open Income:US:Hooli:Match401k USD ; \"Employer 401k match\" 2012-12-13 open Income:US:Hooli:GroupTermLife USD ; \"Group Term Life\" These correspond to regular salary pay, bonuses received for employee referrals, annual bonus, receipts for 401k (Hooli in this example will match some percentage of your contributions to your retirement account), receipts for life insurance (it appears both as an income and an expense), and benefits paid for your gym subscription. There are more, but this is a good example. (In this example I wrote down the names used in the stub as a comment, but you could insert them as metadata instead if you prefer.) You will need to book taxes withheld at the source to accounts for that year (see the tax section for details on this): 2014-01-01 open Expenses:Taxes:TY2014:US:Medicare USD 2014-01-01 open Expenses:Taxes:TY2014:US:Federal USD 2014-01-01 open Expenses:Taxes:TY2014:US:StateNY USD 2014-01-01 open Expenses:Taxes:TY2014:US:CityNYC USD 2014-01-01 open Expenses:Taxes:TY2014:US:SDI USD 2014-01-01 open Expenses:Taxes:TY2014:US:SocSec USD These accounts are for Medicare taxes, Federal, New York State and NYC taxes (yes, New York City residents have an additional tax on top of state tax), state disability insurance (SDI) payments, and finally, taxes to pay for social security. You will also need to have some accounts defined elsewhere for the various expenses that are paid automatically from your pay: 2012-12-13 open Expenses:Health:Life:GroupTermLife USD ; \"Life Ins.\" 2012-12-13 open Expenses:Health:Dental:Insurance USD ; \"Dental\" 2012-12-13 open Expenses:Health:Medical:Insurance USD ; \"Medical\" 2012-12-13 open Expenses:Health:Vision:Insurance USD ; \"Vision\" 2012-12-13 open Expenses:Internet:Reimbursement USD ; \"Internet Reim\" 2012-12-13 open Expenses:Transportation:PreTax USD ; \"Transit PreTax\" These correspond to typical company group plan life insurance payments, premiums for dental, medical and vision insurances, reimbursements for home internet usage, and pre-tax payments for public transit (the city of New York allows you to pay for your MetroCard with pre-tax money through your employer).","title":"Employment Income Accounts"},{"location":"command_line_accounting_cookbook.html#booking-salary-deposits","text":"Then, when I import details for a payment via direct deposit to my checking account, it will look like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD If I haven\u2019t received my pay stub yet, I might book it temporarily to the salary account until I do: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD ! Income:US:Hooli:Salary When I receive or fetch my pay stub, I remove this and complete the rest of the postings. A realistic entry for a gross salary of $140,000 would look something like this: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD Income:US:Hooli:GroupTermLife -25.38 USD Income:US:Hooli:Salary -5384.62 USD Expenses:Health:Dental:Insurance 2.88 USD Expenses:Health:Life:GroupTermLife 25.38 USD Expenses:Internet:Reimbursement -34.65 USD Expenses:Health:Medical:Insurance 36.33 USD Expenses:Transportation:PreTax 56.00 USD Expenses:Health:Vision:Insurance 0.69 USD Expenses:Taxes:TY2014:US:Medicare 78.08 USD Expenses:Taxes:TY2014:US:Federal 1135.91 USD Expenses:Taxes:TY2014:US:CityNYC 75.03 USD Expenses:Taxes:TY2014:US:SDI 1.20 USD Expenses:Taxes:TY2014:US:StateNY 340.06 USD Expenses:Taxes:TY2014:US:SocSec 328.42 USD It\u2019s quite unusual for a salary payment to have no variation at all from its previous one: rounding up or down from the payroll processor will often result in a difference of a penny, social security payments will cap to their maximum, and there are various deductions that will occur from time to time, e.g., deductions on taxable benefits received. Moreover, contributions to a 401k will affect that amounts of taxes withheld at the source. Therefore, you end up having to look at each pay stub individually to enter its information correctly. But this is not as time-consuming as it sounds! Here\u2019s a trick: it\u2019s a lot easier to update your transactions if you list your postings in the same order as they appear on your pay stub. You just copy-paste the previous entry, read the pay stub from top to bottom and adjust the numbers accordingly. It takes a minute for each. It\u2019s worth noting some unusual things about the previous entry. The \u201cgroup term life\u201d entry has both a $25.38 income leg and an expense one. This is because Hooli pays for the premium (it reads exactly like that on the stubs.) Hooli also reimburses some of home internet, because I use it to deal with production issues. This appears as a negative posting to reduce the amount of my expense Expenses:Internet account.","title":"Booking Salary Deposits"},{"location":"command_line_accounting_cookbook.html#vacation-hours","text":"Our pay stubs also include accrued vacation and the total vacation balance, in vacation hours. You can also track these amounts on the same transactions. You need to declare corresponding accounts: 2012-12-13 open Income:US:Hooli:Vacation VACHR 2012-12-13 open Assets:US:Hooli:Vacation VACHR 2012-12-13 open Expenses:Vacation:Hooli VACHR Vacation that accrues is something you receive and thus is treated as Income in units of \u201cVACHR\u201d, and accumulates in an Assets account, which holds how many of these hours you currently have available to \u201cspend\u201d as time off. Updating the previous salary income transaction entry: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Hooli:Vacation 4.62 VACHR Income:US:Hooli:Vacation -4.62 VACHR 4.62 VACHR on a bi-weekly paycheck 26 times per year is 26 x 4.62 ~= 120 hours. At 8 hours per day, that is 15 work days, or 3 weeks, which is a standard vacation package for new US Hooligans in this example. When you do take time off, you book an expense against your accumulated vacation time: 2014-06-17 * \"Going to the beach today\" Assets:US:Hooli:Vacation -8 VACHR Expenses:Vacation:Hooli The Expenses account tracks how much vacation you\u2019ve used. From time to time you can check that the balance reported on your pay stub\u2014the amount of vacation left that your employer thinks you have\u2014is the same as that which you have accounted for: 2014-02-29 balance Assets:US:Hooli:Vacation 112.3400 VACHR You can \u201cprice\u201d your vacation hour units to your hourly rate, so that your vacations Assets account shows how much the company would pay you if you decided to quit. Assuming that $140,000/year salary, 40 hour weeks and 50 weeks of work, which is 2000 hours per year, we obtain a rate of $70/hour, which you enter like this: 2012-12-13 price VACHR 70.00 USD Similarly, if your vacation hours expires or caps, you can calculate how much dollar-equivalent you\u2019re forfeiting by working too much and giving up your vacation time. You would write off some of the VACHR from your Assets account into an income account (representing losses).","title":"Vacation Hours"},{"location":"command_line_accounting_cookbook.html#401k-contributions","text":"The 401k plan allows you to make contributions to a tax-deferred retirement account using pre-tax dollars. This is carried out via withholdings from your pay. To account for those, you simply include a posting with the corresponding contribution towards your retirement account: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD \u2026 If you\u2019re accounting for your available contributions (see the tax section of this document), you will want to reduce your \u201c401k contribution\u201d Assets account at the same time. You would add two more postings to the transaction: 2014-02-28 * \"HOOLI INC PAYROLL\" Assets:US:BofA:Checking 3364.67 USD \u2026 Assets:US:Vanguard:Cash 1000.00 USD Assets:US:Federal:PreTax401k -1000.00 US401K Expenses:Taxes:TY2014:US:Federal:PreTax401k 1000.00 US401K \u2026 If your employer matches your contributions, this may not show on your pay stubs. Because these contributions are not taxable\u2014they are deposited directly to a tax-deferred account\u2014your employer does not have to include them in the withholding statement. You will see them appear directly in your investment account as deposits. You can book them like this to the retirement account\u2019s tax balance: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:Cash 1173.08 USD And then insert a second transaction when you invest this case, or directly purchasing assets from the contribution if you have specified an asset allocation and this is automated by the broker: 2013-03-16 * \"BUYMF - MATCH\" Income:US:Hooli:Match401k -1173.08 USD Assets:US:Vanguard:VMBPX 106.741 VMBPX {10.99 USD} Note that the fund that manages your 401k accounts may be tracking your contributions and your employer\u2019s contributions in separate buckets. You would declare sub-accounts for this and make the corresponding changes: 2012-12-13 open Assets:US:Vanguard:PreTax401k:VMBPX VMBPX 2012-12-13 open Assets:US:Vanguard:Match401k:VMBPX VMBPX It is common for them to do this in order to track each source of contribution separately, because there are several constraints on rollovers to other accounts that depend on it.","title":"401k Contributions"},{"location":"command_line_accounting_cookbook.html#vesting-stock-grants","text":"See the dedicated document on this topic for more details.","title":"Vesting Stock Grants"},{"location":"command_line_accounting_cookbook.html#other-benefits","text":"You can go crazy with tracking benefits if you want. Here are a few wild ideas.","title":"Other Benefits"},{"location":"command_line_accounting_cookbook.html#points","text":"If your employer offers a sponsored massage therapy program on-site, you could presumably book a massage out of your paycheck or even from some internal website (if the company is modern), and you could pay for them using some sort of internal points system, say, \u201cHooli points\u201d. You could track those using a made-up currency, e.g., \u201cMASSA\u2019s\u201d and which could be priced at 0.50 USD, the price at which you could purchase them: 2012-12-13 open Assets:US:Hooli:Massage MASSA 2012-12-13 price MASSA 0.50 USD When I purchase new massage points, I 2013-03-15 * \"Buying points for future massages\" Liabilities:US:BofA:CreditCard -45.00 USD Assets:US:Hooli:Massage 90 MASSA {0.50 USD} If you\u2019re occasionally awarded some of these points, and you can track that in an Income account.","title":"Points"},{"location":"command_line_accounting_cookbook.html#food-benefits","text":"Like many of the larger technology companies, Hooli presumably provides free food for its employees. This saves time and encourages people to eat healthy. This is a bit of a trend in the tech world right now. This benefit does not show up anywhere, but if you want to price it as part of your compensation package, you can track it using an Income account: 2012-12-13 open Income:US:Hooli:Food Depending on how often you end up eating at work, you could guesstimate some monthly allowance per month: 2013-06-30 * \"Distribution for food eaten at Hooli\" Income:US:Hooli:Food -350 USD Expenses:Food:Restaurant","title":"Food Benefits"},{"location":"command_line_accounting_cookbook.html#currency-transfers-conversions","text":"If you convert between currencies, such as when performing an international transfer between banks, you need to provide the exchange rate to Beancount. It looks like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF The balance amount of the second posting is calculated as 10,000.00 USD x 0.90 CHF/USD = 9,000 CHF, and the transaction balances. Depending on your preference, you could have placed the rate on the other posting, like this: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF @ 1.11111 USD Assets:US:BofA:Checking 10000.00 USD The balance amount of the first posting is calculated as -9000.00 CHF x 1.11111 USD/CHF = 10000.00 USD 4 . Typically I will choose the rate that was reported to me and put it on the corresponding side. You may also want to use the direction that F/X markets use for trading the rate, for example, the Swiss franc trades as USD/CHF, so I would prefer the first transaction. The price database converts the rates in both directions, so it is not that important 5 . If you use wire transfers, which is typical for this type of money transfer, you might incur a fee: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9025.00 CHF Assets:US:BofA:Checking 10000.00 USD @ 0.90 CHF Expenses:Fees:Wires 25.00 CHF If you convert cash at one of these shady-looking currency exchange parlors found in tourist locations, it might look like this: 2014-03-03 * \"Changed some cash at the airport in Lausanne\" Assets:Cash -400.00 USD @ 0.90 CHF Assets:Cash 355.00 CHF Expenses:Fees:Services 5.00 CHF In any case, you should never convert currency units using the cost basis syntax, because the original conversion rate needs to be forgotten after depositing the units, and not kept around attached to simple currency. For example, this would be incorrect usage: 2014-03-03 * \"Transfer from Swiss account\" Assets:CH:UBS:Checking -9000.00 CHF Assets:US:BofA:Checking 10000.00 USD {0.90 CHF} ; <-bad! If you did that by mistake, you would incur errors when you attempted to use the newly USD deposited: Beancount would require that you specify the cost of these \u201cUSD\u201d in CHF, e.g., \u201cdebit from my USD that I changed at 0.90 USD/CHF\u201d. Nobody does this in the real world, and neither should you when you represent your transactions: once the money has converted, it\u2019s just money in a different currency, with no associated cost. Finally, a rather subtle problem is that using these price conversions back and forth at different rates over time breaks the accounting equation to some extent: changes in exchange rate may create small amounts of money out of thin air and all the balances don\u2019t end up summing up to zero. However, this is not a problem, because Beancount implements an elegant solution to automatically correct for this problem, so you can use these conversions freely without having to worry about this: it inserts a special conversions entry on the balance sheet to invert the cumulative effect of conversions for the report and obtain a clean balance of zero. (A discussion of the conversions problem is beyond the scope of this cookbook; please refer to Solving the Conversions Problem if you\u2019d like to know more.)","title":"Currency Transfers & Conversions"},{"location":"command_line_accounting_cookbook.html#investing-and-trading","text":"Tracking trades and associated gains is a fairly involved topic. You will find a more complete introduction to profit and loss and a detailed discussion of various scenarios in the Trading with Beancount document, which is dedicated to this topic. Here we will discuss how to setup your account and provide simple example transactions to get you started.","title":"Investing and Trading"},{"location":"command_line_accounting_cookbook.html#accounts-setup","text":"You should create an account prefix to root various sub-accounts associated with your investment account. Say you have an account at ETrade, this could be \u201c Assets:US:ETrade \u201d. Choose an appropriate institution name. Your investment account will have a cash component. You should create a dedicated sub-account will represent uninvested cash deposits: 2013-02-01 open Assets:US:ETrade:Cash USD I recommend that you further define a sub-account for each of the commodity types that you will invest in. Although this is not strictly necessary\u2014Beancount accounts may contain any number of commodities\u2014it is a nice way to aggregate all the positions in that commodity together for reporting. Say you will buy shares of LQD and BND, two popular bond ETFs: 2013-02-01 open Assets:US:ETrade:LQD LQD 2013-02-01 open Assets:US:ETrade:BND BND This also helps produce nicer reports: balances are often shown at cost and it\u2019s nice to see the total cost aggregated by commodity for various reasons (i.e., each commodity provides exposure to different market characteristics). Using a dedicated sub-account for each commodity held within an institution is a good way to do that. Unless you have specific reasons not to do so, I highly suggest sticking with this by default (you can always change it later by renaming accounts). Specifying commodity constraints on your accounts will help you detect data entry mistakes. Stock trades tend to be a bit more involved than regular transactions, and this is certainly going to be helpful. Then, you will hopefully receive income in this account, in two forms: capital gains, or \u201cP&L\u201d, and dividends. I like to account for these by institution, because this is how they have to be declared for taxes. You may also receive interest income. Define these: 2013-02-01 open Income:US:ETrade:PnL USD 2013-02-01 open Income:US:ETrade:Dividends USD 2013-02-01 open Income:US:ETrade:Interest USD Finally, to account for transaction fees and commissions, you will need some general accounts to receive these: 1973-04-27 open Expenses:Financial:Fees 1973-04-27 open Expenses:Financial:Commissions","title":"Accounts Setup"},{"location":"command_line_accounting_cookbook.html#funds-transfers","text":"You will normally add some initial money in this account by making a transfer from an external account, say, a checking account: 2014-02-04 * \"Transferring money for investing\" Assets:US:BofA:Checking -2000.00 USD Assets:US:ETrade:Cash 2000.00 USD","title":"Funds Transfers"},{"location":"command_line_accounting_cookbook.html#making-a-trade","text":"Buying stock should have a posting that deposits the new commodity in the commodity\u2019s sub-account, and debits the cash account to the corresponding amounts plus commissions: 2014-02-16 * \"Buying some LQD\" Assets:US:ETrade:LQD 10 LQD {119.24 USD} Assets:US:ETrade:Cash -1199.35 USD Expenses:Financial:Commissions 6.95 USD Note that when you\u2019re buying units of a commodity, you are establishing a new trade lot in the account\u2019s inventory and it is necessary that you provide the cost of each unit (in this example, 119.24 USD per share of LQD). This allows us to account for capital gains correctly. Selling some of the same stock work similarly, except that an extra posting is added to absorb the capital gain or loss: 2014-02-16 * \"Selling some LQD\" Assets:US:ETrade:LQD -5 LQD {119.24 USD} @ 123.40 USD Assets:US:ETrade:Cash 610.05 USD Expenses:Financial:Commissions 6.95 USD Income:US:Etrade:PnL Note that the postings of shares removed from the Assets:US:ETrade:LQD account is a lot reduction and you must provide information to identify which lot you\u2019re reducing, in this case, by providing the per-share cost basis of 119.24 USD. I normally let Beancount calculate the capital gain or loss for me, which is why I don\u2019t specify it in the last posting. Beancount will automatically balance the transaction by setting the amount of this posting to -20.80 USD, which is a gain of 20.80 USD (remember that the signs are inverted for income accounts). Specifying the sale price of 123.40 USD is optional, and it is ignored for the purpose of balancing the transaction, the cash deposit and commissions legs determine the profit.","title":"Making a Trade"},{"location":"command_line_accounting_cookbook.html#receiving-dividends","text":"Receiving dividends takes on two forms. First, you can receive dividends in cash, which will go into the cash account: 2014-02-16 * \"Dividends from LQD\" Income:US:ETrade:Dividends -87.45 USD Assets:US:ETrade:Cash 87.45 USD Note that the source of the dividends isn\u2019t specified here. You could use a sub-account of the income account to count it separately. Or you can receive dividends in shares reinvested: 2014-06-27 * \"Dividends reinvested\" Assets:US:Vanguard:VBMPX 1.77400 VBMPX {10.83 USD} Income:US:Vanguard:Dividends -19.21 USD This is booked similarly to a stock purchase, and you also have to provide the cost basis of the received units. This would typically happen in a non-taxable retirement account. Refer to the Trading with Beancount document for a more thorough discussion and numerous and more complex examples.","title":"Receiving Dividends"},{"location":"command_line_accounting_cookbook.html#choosing-a-date","text":"Buying or selling a single lot of stock typically involves multiple events over time: the trade is placed, the trade is filled (usually on the same day), the trade is settled. Settlement usually occurs 2 or 3 business days after the trade is filled. For simplicity, I recommend using the trade date as the date of your transaction. In the US, this is the date that is recognized for tax purposes, and settlement has no impact on your account (brokers typically won\u2019t allow you to trade without the corresponding cash or margin anyhow). So normally I don\u2019t bother creating separate entries for settlement, it\u2019s not very useful. More complex schemes can be envisioned, e.g. you could store the settlement date as a metadata field and then use it in scripts later on, but that\u2019s beyond the scope of this document.","title":"Choosing a Date"},{"location":"command_line_accounting_cookbook.html#conclusion","text":"This document is incomplete. I have many more example use cases that I\u2019m planning to add here as I complete them. I will be announcing those on the mailing-list as they materialize. In particular, the following topics will be discussed: Health Care Expenses, e.g., insurance premiums and rebates Taxes IRAs, 401k and other tax-deferred accounts Real Estate Options This is not strictly always true: in accounting for companies, some account types are held at their opposite value for reasons, usually to offset the value of another account of the same type. These are called \u201ccontra\u201d accounts. But as an individual, you\u2019re quite unlikely to have one of those. If you\u2019re setting up a chart of accounts for a company, Beancount doesn\u2019t actually care whether the balance is of one sign or other. You declare contra-accounts just like regular accounts. \u21a9 I am considering supporting an extended version of the Pad directive that can take a percentage value and make it possible to pad only a percentage of the full amount, to automate this. \u21a9 Yet another extension to Beancount involves support multiple Pad directives between two balance assertions and automatically support this spreading out of padding directives. \u21a9 If you\u2019re concerned about the issue of precision or rounding in balancing, see this document . \u21a9 Note that if the price database needs to invert the date its calculation may result in a price with a large number of digits. Beancount uses IEEE decimal objects and the default context of the Python implementation is 28 digits, so inverting 0.9 will result in 1.111111\u2026.111 with 28 digits. \u21a9","title":"Conclusion"},{"location":"command_line_accounting_in_context.html","text":"Command-line Accounting in Context \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/motivation This text provides context and motivation for the usage of double-entry command-line accounting systems to manage personal finances. Motivation What exactly is \u201cAccounting\u201d? What can it do for me? Keeping Books Generating Reports Custom Scripting Filing Documents How the Pieces Fit Together Why not just use a spreadsheet? Why not just use a commercial app? How about Mint.com? How about Quicken? How about Quickbooks? How about GnuCash? Why build a computer language? Advantages of Command-Line Bookkeeping Why not just use an SQL database? But... I just want to do X? Why am I so Excited? Motivation \uf0c1 When I tell people about my command-line accounting hacking activities, I get a variety of reactions. Sometimes I hear echoes of desperation about their own finances: many people wish they had a better handle and understanding of their finances, they sigh and wish they were better organized. Other times, after I describe my process I get incredulous reactions: \u201cWhy do you even bother?\u201d Those are the people who feel that their financial life is so simple that it can be summarized in 5 minutes. These are most often blissfully unaware how many accounts they have and have only an imprecise idea of where they stand. They have at least 20 accounts to their name; it\u2019s only when we go through the details that they realize that their financial ecosystem is more complex than they thought. Finally, there are those who get really excited about the idea of using a powerful accounting system but who don\u2019t actually understand what it is that I\u2019m talking about. They might think it is a system to support investment portfolios, or something that I use to curate a budget like a stickler. Usually they end up thinking it is too complicated to actually get started with. This document attempts to explain what command-line bookkeeping is about in concrete terms, what you will get out of doing it, how the various software pieces fit together, and what kinds of results you can expect from this method. The fact is, the double-entry method is a basic technique that everyone should have been taught in high school. And using it for yourself is a simple and powerful process you can use to drive your entire financial life. What exactly is \u201cAccounting\u201d? \uf0c1 When one talks about \u201caccounting,\u201d they often implicitly refer to one or more of various distinct financial processes: Bookkeeping. Recording past transactions in a single place, a \u201cledger,\u201d also called \u201cthe books,\u201d as in, \u201cthe books of this company.\u201d Essentially, this means copying the amounts and category of financial transactions that occur in external account statements to a single system that includes all of the \u201caccounts\u201d relating to an entity and links them together. This is colloquially called \u201ckeeping books,\u201d or the activity of \u201cbookkeeping.\u201d Invoices. Preparing invoices and tracking payments. Contractors will often bring this up because it is a major concern of their activity: issuing requests to clients for payments for services rendered, and checking whether the corresponding payments have actually been received later on (and taking collection actions in case they haven\u2019t). If you\u2019re managing a company\u2019s finances, processing payroll is another aspect of this which is heavy in bookkeeping. Taxes. Finding or calculating taxable income, filling out tax forms and filing taxes to the various governmental authorities the entity is subject to. This process can be arduous, and for people who are beginning to see an increase in the complexity of their personal assets (many accounts of different types, new reporting requirements), it can be quite stressful. This is often the moment that they start thinking about organizing themselves. Expenses. Analyzing expenses, basically answering the question: \u201cWhere is my money going?\u201d A person with a small income and many credit cards may want to precisely track and calculate how much they\u2019re spending every month. This just develops awareness. Even in the presence of abundant resources, it is interesting to look at how much one is spending regularly, and where most of one\u2019s regular income is going, it often brings up surprises. Budgeting. Forecasting future expenses, allocating limited amounts to spend in various categories, and tracking how close one\u2019s actual spending is to those allocations. For individuals, this is usually in the context of trying to pay down debt or finding ways to save more. For companies, this occurs when planning for various projects. Investing. Summarizing and computing investment returns and capital gains. Many of us now manage our own savings via discount brokers, investing via ETFs and individually selected mutual funds. It is useful to be able to view asset distribution and risk exposure, as well as compute capital gains. Reporting. Public companies have regulatory requirements to provide transparency to their investors. As such, they usually report an annual income statement and a beginning and ending balance sheet . For an individual, those same reports are useful when applying for a personal loan or a mortgage at a bank, as they provide a window to someone\u2019s financial health situation. In this document, I will describe how command-line accounting can provide help and support for these activities. What can it do for me? \uf0c1 You might legitimately ask: sure, but why bother? We can look at the uses of accounting in terms of the questions it can answer for you: Where\u2019s my money, Lebowski? If you don\u2019t keep track of stuff, use cash a lot, have too many credit cards or if you are simply a disorganized and brilliant artist with his head in the clouds, you might wonder why there aren\u2019t as many beans left at the end of the month as you would like. An income statement will answer this question. I\u2019d like to be like Warren Buffet. How much do I have to save every month? I personally don\u2019t do a budget, but I know many people who do. If you set specific financial goals for a project or to save for later, the first thing you need to do is allocate a budget and set limits on your spending. Tracking your money is the first step towards doing that. You could even compute your returns in a way that can be compared against the market. I have some cash to invest. Given my portfolio, where should I invest it? Being able to report on the totality of your holdings, you can determine your asset class and currency exposures. This can help you decide where to place new savings in order to match a target portfolio allocation. How much am I worth? You have a 401k, an IRA and taxable accounts. A remaining student loan, or perhaps you\u2019re sitting on real-estate with two mortgages. Whatever. What\u2019s the total? It\u2019s really nice to obtain a single number that tells you how far you are from your retirement goals. Beancount can easily compute your net worth (to the cent). I still hurt from 2008. Are my investments safe? For example, I\u2019m managing my own money with various discount brokers, and many people are. I\u2019m basically running a miniature hedge fund using ETFs, where my goal is to be as diversified as possible between asset classes, sector exposure, currency exposure, etc. Plus, because of tax-deferred accounts I cannot do that in a single place. With Beancount you can generate a list of holdings and aggregate them by category. Taxes suck. Well, enough said. Why do they suck? Mostly because of uncertainty and doubt. It\u2019s not fun to not know what\u2019s going on. If you had all the data at your fingertips instantly it wouldn\u2019t be nearly as bad. What do you have to report? For some people there\u2019s a single stream of income, but for many others, it gets complicated (you might even be in that situation and you don\u2019t know it). Qualified vs. ordinary dividends, long-term vs. short-term capital gains, income from secondary sources, wash sales, etc. When it\u2019s time to do my taxes, I bring up the year\u2019s income statement, and I have a clear list of items to put in and deductions I can make. I want to buy a home. How much can I gather for a down payment? If you needed a lot of cash all of a sudden, how much could you afford? Well, if you can produce a list of your holdings, you can aggregate them \u201cby liquidity.\u201d This gives you an idea of available cash in case of an urgent need. Mr. Banker, please lend me some money. Impress the banker geek: just bring your balance sheet. Ideally the one generated fresh from that morning. You should have your complete balance sheet at any point in time. Instructions for your eventual passing. Making a will involves listing your assets. Make your children\u2019s lives easier should you pass by having a complete list of all the beans to be passed on or collected. The amounts are just part of the story: being able to list all the accounts and institutions will make it easier for someone to clear your assets. I can\u2019t remember if they paid me. If you send out invoices and have receivables, it\u2019s nice to have a method to track who has paid and who just says they\u2019ll pay. I\u2019m sure there is a lot more you can think of. Let me know. Keeping Books \uf0c1 Alright, so here\u2019s the part where I give you the insight of the method. The central and most basic activity that provides support for all the other ones is bookkeeping . A most important and fundamental realization is that this relatively simple act\u2014that of copying all of the financial transactions into a single integrated system\u2014allows you to produce reports that solve all of the other problems. You are building a corpus of data, a list of dated transaction objects that each represents movements of money between some of the accounts that you own. This data provides a full timeline of your financial activity. All you have to do is enter each transaction while respecting a simple constraint, that of the double-entry method , which is this: Each time an amount is posted to an account, it must have a corresponding inverse amount posted to some other account(s), and the sum of these amounts must be zero. That\u2019s it. This is the essence of the method, and an ensemble of transactions that respects this constraint acquires nice properties which are discussed in detail in some of my other documents. In order to generate sensible reports, all accounts are further labeled with one of four categories: Assets , Liabilities , Income and Expenses . A fifth category, Equity , exists only to summarize the history of all previous income and expense transactions. Command-line accounting systems are just simplistic computer languages for conveniently entering, reading and processing this transaction data, and ensure that the constraint of the double-entry method is respected. They\u2019re simple counting systems, that can add any kind of \u201cthing\u201d in dedicated counters (\u201caccounts\u201d). They\u2019re basically calculators with multiple buckets. Beancount and Ledger may differ slightly in their syntax and on some of their semantics, but the general principle is the same, and the simplifying assumptions are very similar. Here is a concrete example of what a transaction looks like, just so you can get a feeling for it. In a text file\u2014which is the input to a command-line accounting system\u2014something like the following is written and expresses a credit card transaction: 2014-05-23 * \"CAFE MOGADOR NEW YO\" \"Dinner with Caroline\" Liabilities:US:BofA:CreditCard -98.32 USD Expenses:Restaurant This is an example transaction with two \u201cpostings,\u201d or \u201clegs.\u201d The \u201c Expenses:Restaurant \u201d line is an account, not a category, though accounts often act like categories (there is no distinction between these two concepts). The amount on the expenses leg is left unspecified, and this is a convenience allowed by the input language. The software determines its amount automatically with the remainder of the balance which in this case will be 98.32 USD, so that -98.32 USD + 98.32 USD = 0 (remember that the sum of all postings must balance to zero\u2014this is enforced by the language). Most financial institutions provide some way for you to download a summary of account activity in some format or other. Much of the details of your transactions are automatically pulled in from such a downloaded file, using some custom script you write that converts it into the above syntax: A transaction date is always available from the downloadable files. The \u201c CAFE MOGADOR NEW YO \u201d bit is the \u201cmemo,\u201d also provided by the downloaded file, and those names are often good enough for you to figure out what the business you spent at was. Those memos are the same ugly names you would see appear on your credit card statements. This is attached to a transaction as a \u201cpayee\u201d attribute. I manually added \u201c Dinner with Caroline \u201d as a comment. I don\u2019t have to do this (it\u2019s optional), but I like to do it when I reconcile new transactions, it takes me only a minute and it helps me remember past events if I look for them. The importer brought in the Liabilities:US:BofA:CreditCard posting with its amount automatically, but I\u2019ve had to insert the Expenses:Restaurant account myself: I typed it in. I have shortcuts in my text editor that allow me to do that using account name completion, it takes a second and it\u2019s incredibly easy. Furthermore, pre-selecting this account could be automated as well, by running a simple learning algorithm on the previous history contained in the same input file (we tend to go to the same places all the time). The syntax gets a little bit more complicated, for example it allows you to represent stock purchases and sales, tracking the cost basis of your assets, and there are many other types of conveniences, like being able to define and count any kind of \u201cthing\u201d in an account (e.g., \u201cvacation hours accumulated\u201d), but this example captures the essence of what I do. I replicate all the transactions from all of the accounts that I own in this way, for the most part in an automated fashion. I spend about 1-2 hours every couple of weeks in order to update this input file, and only for the most used accounts (credit card and checking accounts). Other accounts I\u2019ll update every couple of months, or when I need to generate some reports. Because I have a solid understanding of my finances, this is not a burden anymore\u2026 it has become fun . What you obtain is a full history, a complete timeline of all the financial transactions from all the accounts over time, often connected together, your financial life, in a single text file . Generating Reports \uf0c1 So what\u2019s the point of manicuring this file? I have code that reads it, parses it, and that can serve various views and aggregations of it on a local web server running on my machine, or produce custom reports from this stream of data. The most useful views are undeniably the balance sheet and income statement , but there are others: Balance Sheet. A balance sheet lists the final balance of all of your assets and liabilities accounts on a single page. This is a snapshot of all your accounts at a single point in time, a well-understood overview of your financial situation. This shows your net worth (very precisely, if you update all your accounts) and is also what a banker would be interested in if you were to apply for a loan. Beancount can produce my balance sheet at any point in time, but most often I\u2019m interested in the \u201ccurrent\u201d or \u201clatest\u201d balance sheet. Income Statement. An income statement is a summary of all income and expenses that occur between two points in time. It renders the final balance for these accounts in format familiar to any accountant: income accounts on the left, expense accounts on the right. This provides insights on the changes that occurred during a time period, a delta of changes. This tells you how much you\u2019re earning and where your money is going, in detail. The difference between the two is how much you saved. Beancount can render such a statement for any arbitrary period of time, e.g., this year, or month by month. Journals. For each account (or category, if you like to think of them that way), I can render a list of all the transactions that posted changes to that account. I call this a journal (Ledger calls this a \u201cregister\u201d). If you\u2019re looking at your credit card account\u2019s journal, for instance, it should match that of your bank statement. On the other hand, if you look at your \u201crestaurants expense\u201d account, it should show all of your restaurant outings, across all methods of payment (including cash, if you choose to enter that). You can easily fetch any detail of your financial history, like \u201cWhere was that place I had dinner with Arthur in March three years ago?\u201d or \u201cI want to sell that couch\u2026 how much did I pay for it again?\u201d Payables and Receivables. If you have amounts known to be received, for example, you filed your taxes or sent an invoice and are expecting a payment, or you mailed a check and you are expecting it to be cashed at some point in the future, you can track this with dedicated accounts. Transactions have syntax that allows you to link many of them together and figure out what has been paid or received. Calculating taxes. If you enter your salary deposits with the detail from your pay stub, you can calculate exactly how much taxes you\u2019ve paid, and the amounts should match exactly those that will be reported on your employer\u2019s annual income reporting form (e.g., W2 form in the USA, T4 if in Canada, Form P60 in the UK, or otherwise if you live elsewhere). This is particularly convenient if you have to make tax installments, for instance. If you\u2019re a contractor, you will have to track many such things, including company expensable expenses. It is also useful to count dividends that you have to report as taxable income. Investing. I\u2019m not quite rich, but I manage my own assets using discount brokers and mainly ETFs. In addition, I take advantage of a variety of tax sheltered accounts, with limited choices in assets. This is pretty common now. You could say that I\u2019m managing a miniature hedge fund and you wouldn\u2019t be too far from reality. Using Beancount, I can produce a detailed list of all holdings, with reports on daily market value changes, capital gains, dividends, asset type distribution (e.g. stocks vs. fixed income), currency exposure, and I can figure out how much of my assets are liquid, e.g., how much I have available towards a downpayment on a house. Precisely, and at any point in time. This is nice. I can also produce various aggregations of the holdings, i.e., value by account, by type of instrument, by currency. Forecasting. The company I work for has an employee stock plan, with a vesting schedule. I can forecast my approximate expected income including the vesting shares under reasonable assumptions. I can answer the question: \u201cAt this rate, at which date do I reach a net worth of X?\u201d Say, if X is the amount which you require for retirement. Sharing Expenses. If you share expenses with others, like when you go on a trip with friends, or have a roommate, the double-entry method provides a natural and easy solution to reconciling expenses together. You can also produce reports of the various expenses incurred for clarity. No uncertainty. Budgeting. I make enough money that I don\u2019t particularly set specific goals myself, but I plan to support features to do this. You basically end up managing your personal finances like a company would\u2026 but it\u2019s very easy because you\u2019re using a simple and cleverly designed computer language that makes a lot of simplifications (doing away with the concepts of \u201ccredit and debits\u201d for example), reducing the process to its essential minimum. Custom Scripting \uf0c1 The applications are endless. I have all sorts of wild ideas for generating reports for custom projects. These are useful and fun experiments, \u201cchallenges\u201d as I call them. Some examples: I once owned a condo unit and I\u2019ve been doing double-entry bookkeeping throughout the period I owned it, through selling it. All of the corresponding accounts share the Loft4530 name in them. This means that I could potentially compute the precise internal rate of return on all of the cash flows related to it, including such petty things as replacement light bulbs expenses. To consider it as a pure investment. Just for fun. I can render a tree-map of my annual expenses and assets. This is a good visualization of these categories, that preserve their relative importance. I could look at average expenses with a correction for the time-value of money. This would be fun, tell me how my cost of living has changed over time. The beauty of it is that once you have the corpus of data, which is relatively easy to create if you maintain it incrementally, you can do all sorts of fun things by writing a little bit of Python code. I built Beancount to be able to do that in two ways: By providing a \u201cplugins\u201d system that allows you to filter a parsed set of transactions. This makes it possible for you to hack the syntax of Beancount and prototype new conveniences in data entry. Plugins provided by default provide extended features just by filtering and transforming the list of parsed transactions. And it\u2019s simple: all you have to do is implement a callback function with a particular signature and add a line to your input file. By writing scripts. You can parse and obtain the contents of a ledger with very little code. In Python, this looks no more complicated than this: import beancount.loader \u2026 entries, errors, options = beancount.loader.load_file('myfile.ledger') for entry in entries: \u2026 Voila. You\u2019re on your way to spitting out whatever output you want. You have access to all the libraries in the Python world, and my code is mostly functional, heavily documented and thoroughly unit-tested. You should be able to find your way easily. Moreover, if you\u2019re uncertain about using this system, you could just use it to begin entering your data and later write a script that converts it into something else. Filing Documents \uf0c1 If you have defined accounts for each of your real-world accounts, you have also created a natural method for organizing your statements and other paper documents. As we are communicating more and more by email with our accountants and institutions, it is becoming increasingly common to scan letters to PDFs and have those available as computer files. All the banks have now gone paperless, and you can download these statements if you care (I tend to do this once at the end of the year, for preservation, just in case). It\u2019s nice to be able to organize these nicely and retrieve those documents easily. What I do is keep a directory hierarchy mirroring the account names that I\u2019ve defined, something that looks like this: .../documents/ Assets/ US/ TDBank/ Checking/ 2014-04-08.statement.march.pdf 2014-05-07.statement.april.pdf \u2026 Liabilities/ US/ Amex/ Platinum/ 2014-04-17.March.pdf 2014-04-19.download.ofx \u2026 Expenses/ Health/ Medical/ 2014-04-02.anthem.eob-physiotherapy.pdf \u2026 These example files would correspond to accounts with names Assets:US:TDBank:Checking , Liabilities:US:Amex:Platinum , and Expenses:Health:Medical . I keep this directory under version control. As long as a file name begins with a date, such as \u201c 2014-04-02 \u201d, Beancount is able to find the files automatically and insert directives that are rendered in its web interface as part of an account\u2019s journal, which you can click on to view the document itself. This allows me to find all my statements in one place, and if I\u2019m searching for a document, it has a well-defined place where I know to find it. Moreover, the importing software I wrote is able to identify downloaded files and automatically move them into the corresponding account\u2019s directory. How the Pieces Fit Together \uf0c1 So I\u2019ve described what I do to organize my finances. Here I\u2019ll tell you about the various software pieces and how they fit together: Beancount is the core of the system. It reads the text file I update, parses the computer language I\u2019ve defined and produces reports from the resulting data structures. This software only reads the input file and does not communicate with other programs on purpose. It runs in isolation. Beancount\u2019s ingest package and tools help automate the updating of account data by extracting transactions from file downloads from banks and other institutions. These tools orchestrate the running of importers which you implement (this is where all the messy importing code lives, the code you need to make it easier to keep your text file up-to-date, which can parse OFX and CSV files, for instance). See bean-extract, bean-identify tools. The ingest package also helps with filing documents (see bean-file tool). Because it is able to identify which document belongs to which account, it can move the downloaded file to my documents archive automatically. This saves me time. Finally, in order to provide market values, a Beancount input file should have suitable price directives. Beancount also contains code to fetch latest or historical prices for the various commodities present in one\u2019s ledger file (see the bean-price tool). Like the extraction of transactions from OFX downloads, it also spits out Beancount input syntax used to define prices. See the diagram below for a pretty picture that illustrates how these work together. Why not just use a spreadsheet? \uf0c1 This is indeed a good question, and spreadsheets are incredibly useful for sure. I certainly would not be writing my own software if I could track my finances with a spreadsheet. The problem is that the intrinsic structure of the double-entry transactional data does not lend itself to a tabular representation. Each transaction has multiple attributes (date, narration, tags), and two or more legs, each of which has an associated amount and possibly a cost. If you put the dates on one axis and the accounts on the other, you would end up with a very sparse and very large table; that would not be very useful, and it would be incredibly difficult to edit. If on the other hand, you had a column dedicated to account names for each row, all of your computations would have to take the account cell into the calculation logic. It would be very difficult to deal with, if not impossible. All matters of data aggregations are performed relative to account names. Moreover, dealing with the accumulation of units with a cost basis would require difficult gymnastics. I don\u2019t even know how I would proceed forward to do this in a spreadsheet. A core part of command-line accounting systems is the inventory logic that allows you to track a cost basis for every unit held in an account and book reductions of positions against existing lots only. This allows the system to compute capital gains automatically. And this is related to the codes that enforce the constraint that all postings on a transaction balance out to zero. I believe that the \u201ctransaction <-> postings\u201d data representation combined with a sensible method for updating inventories is the essence of this system. The need for these is the justification to create a dedicated method to build this data, such as a computer language. Finally, having our own syntax offers the opportunity to provide other types of useful directives such as balance assertions, open/close dates, and sophisticated filtering of subsets of transactions. You just cannot do what we\u2019re doing in a spreadsheet. Why not just use a commercial app? \uf0c1 How about Mint.com? \uf0c1 Oftentimes people tell me they\u2019re using Mint.com to track their finances, usually with some amount of specific complaints following close behind. Online services such as Mint provide a subset of the functionality of double-entry accounting. The main focus of these services is the reporting of expenses by category, and the maintenance of a budget. As such, they do a great job at automating the download of your credit card and bank transactions. I think that if you want a low-maintenance option to tracking your finances and you don\u2019t have a problem with the obvious privacy risks, this is a fantastic option: it comes with a web interface, mobile apps, and updating your accounts probably requires clicking some buttons, providing some passwords, and a small amount of manual corrections every couple of weeks. I have several reservations about using it for myself, however: Passwords. I\u2019m just not comfortable enough with any commercial company to share the passwords to all my bank, credit card, and investment accounts. This sounds like an insane idea to me, a very scary one, and I\u2019m just not willing to put that much trust in any one company\u2019s hands. I don\u2019t care what they say: I worked for several software companies and I\u2019m aware of the disconnect between promises made by salespeople, website PR, and engineering reality. I also know the power of determined computer hackers. Insufficient reporting. Reporting is probably beautiful and colorful\u2014it\u2019s gorgeous on the website\u2014but certainly insufficient for all the reporting I want to do on my data. Reporting that doesn\u2019t do what they want it to is a common complaint I hear about these systems. With my system, I can always write a script and produce any kind of report I might possibly want in the future. For instance, spitting out a treemap visualization of my expenses instead of a useless pie chart. I can slice and dice my transactions in all kinds of unique ways. I can tag subsets of transactions for projects or trips and report on them. It\u2019s more powerful than generic reporting. Perennity. What if the company is not in business in 5 years? Where will my data be? If I spend any time at all curating my financial data, I want to ensure that it will be available to me forever, in an open format. In their favor, some of these sites probably have a downloadable option, but who uses it? Does it even work well? Would it include all of the transactions that they downloaded? I don\u2019t know. Not international. It probably does not work well with an international, multi-currency situation. These services target a \u201cmajority\u201d of users, most of which have all their affairs in a single country. I live in the USA, have a past history in Canada, which involves remaining tax-sheltered investment accounts, occasional expenses during visits which justify maintaining a credit card there, and a future history which might involve some years spent in another country such as Hong Kong or Australia. Will this work in Mint? Probably not. They might support Canada, but will they support accounts in both places? How about some other country I might want to move to? I want a single integrated view of all my accounts across all countries in all currencies forever, nothing less. My system supports that very well. (I\u2019m not aware of any double-entry system that deals with the international problem in a currency agnostic way as well as Beancount does.) Inability to deal with particular situations. What if I own real estate? Will I be able to price the value of my home at a reasonable amount, so it creates an accurate balance sheet? Regularly obtaining \u201ccomparables\u201d for my property from an eager real estate agent will tell me much more precisely how much my home is worth than services like Zillow ever could. I need to be able to input that for my balance sheet. What about those stock options from that privately held Australian company I used to work for? How do I price that? How about other intangible things, such as receivables from a personal loan I made to a friend? I doubt online services are able to provide you with the ability to enter those. If you want the whole picture with precision, you need to be able to make these adjustments. Custom tracking. Using \u201cimaginary currencies\u201d, I\u2019m able to track all kinds of other things than currencies and stocks. For example, by using an \u201c IRAUSD \u201d commodity in Beancount I\u2019m able to track how many 401k contributions I\u2019ve made at any point during the year. I can count the after-tax basis of a traditional IRA account similarly. I can even count my vacation hours using an imaginary \u201c VACHR \u201d currency and verify it against my pay stubs. Capital gains reporting. I haven\u2019t tried it myself, but I\u2019ve heard some discontent about Mint from others about its limited capabilities for capital gains reporting. Will it maintain trade lots? Will it handle average cost booking? How about PnL on FOREX gains? What about revenue received for that book I\u2019m selling via PayPal? I want to be able to use my accounting system as an input for my tax reporting. Cash transactions. How difficult is it to enter cash transactions? Do I have to log in, or start a slow, heavy program that will only work under Windows? With Beancount I bring up a file in a text editor, this is instant and easy. Is it even possible to enter custom cash entries in online services? In other words, I\u2019m a very sophisticated user and yes, a bit of a control freak . I\u2019m not lying about it. I don\u2019t have any ideological objections about using a commercial service, it is probably not for me. If it\u2019s good enough for you, suit yourself. Despite their beautiful and promising websites, I haven\u2019t heard of anyone being completely satisfied with these services. I hear a fair amount of complaining, some mild satisfaction, but I have yet to meet anyone who raves about it. How about Quicken? \uf0c1 Quicken is a single-entry system, that is, it replicates the transactions of a remote account locally, and allows you to add a label to each transaction to place it in a category. I believe it also has support for synchronizing investment accounts. This is not enough for me, I want to track all kinds of things, and I want to use the double-entry method, which provides an intrinsic check that I\u2019ve entered my data correctly. Single-entry accounting is just not good enough if you\u2019ve already crossed the bridge of understanding the double-entry method. How about Quickbooks? \uf0c1 So let\u2019s talk about sophisticated software that is good enough for a company. Why wouldn\u2019t I use that instead? If it\u2019s good enough for small businesses, it should be good enough for me, no? There\u2019s Quickbooks and other ones. Why don\u2019t I use them: It costs money. Commercial software comes at a price. Ok, I probably could afford to pay a few hundred dollars per year (Quickbooks 2014 looks like around 300$/year for the full set of features), but I don\u2019t really want to. Platform. These softwares usually run on Microsoft Windows and sometimes on Mac OS X . I\u2019m a software developer, I mostly use Linux , and a Macbook Air for a laptop, on which I get annoyed running anything other than tmux and a web browser . I\u2019m not going to reboot just to enter a quick cash transaction. Slow startup. I cannot speak specifically to Quickbooks\u2019 implementation, but virtually every software suite of commercial scope I\u2019ve had to use had a splash screen and a slow, slow startup that involved initializing tons of plugins. They assume you\u2019re going to spend hours in it, which is reasonable for commercial users, but not for me, if I want to do a quick update of my ledger. UIs are inadequate. I haven\u2019t seen their UI but given the nature of transactions and my desire to input precisely and search quickly and organize things, I want to be able to edit in as text. I imagine it would be inconvenient for me. With Emacs and org-mode , I can easily i-search my way to any transaction within seconds after opening my ledger file. Inflexible. How would I go about re-organizing all my account names? I think I\u2019m still learning about the double-entry bookkeeping method and I have made mistakes in the past, mistakes where I desire to revisit the way I organize my accounts in a hierarchy. With my text file, I was able to safely rename a large number of accounts several times, and evolve my chart-of-accounts to reflect my improving understanding of how to organize my financial tracking system. Text is powerful! How about GnuCash? \uf0c1 I don\u2019t like UIs; they\u2019re inconvenient. There\u2019s nothing quite like editing a text file if you are a programmer. Moreover, GnuCash does not deal with multiple currencies well. I find bugs in it within the first hour every time I kick the tires on it, which I do every couple of years, out of curiosity. Other programs, such as Skrooge, also take the heavy-handed big UI approach. Why build a computer language? \uf0c1 A bookkeeping system provides conditions for a solution that involves a simple computer language for many reasons. Single-entry bookkeeping is largely insufficient if you're trying to track everything holistically. Existing systems either limit themselves to expense categories with little checking beyond \"reconciling\" which sometimes involves freezing the past. If you're not doing the bookkeeping for a company, sometimes just changing the past and fixing the mistakes where they occurred makes more sense. More importantly, the single-entry method leaves us wanting for the natural error-checking mechanism involved in the double-entry system. The problem is also not solvable elegantly by using spreadsheets; the simple data structure that forms the basis of the double-entry system infers either a very sparse spreadsheet with accounts on one dimension and transactions on the other. For real-world usage, this is impractical. Another iteration on this theme would involve inserting the postings with two columns, one with the account and one with the amount, but the varying number of columns and the lookup code makes this inelegant as well. Plus, it's not obvious how you would deal with a large number of currencies. Programs that provide fancy graphical or web-based user interfaces are inevitably awkward, due to the nature of the problem: each transaction is organized by viewing it through the lens of one account's journal, but any of the accounts present in its postings provide equally valid views. Ideally, what you want, is just to look at the transaction. Organizing them for most convenient input has little to do with the order in which they are to be presented. Using text has a lot of advantages: You can easily used search-and-replace and/or sed to make global changes, for example, rename or reorganize your accounts; You can organize the transactions in the order that is most convenient for data entry; There are a number of existing tools to search the text; You can easily write various little tools to spit out the data syntax, i.e., for importing from other file types, or converting from other systems. Text is inherently open , that is the file format is one that you can read your data from and store anywhere else, not a blob of incomprehensible data that becomes unusable when the company that makes the software stops supporting it. Finally, systems that attempt to automate the process of importing all your data from automated sources (e.g., mint.com) have one major shortfall: most often it's not very easy or even possible to add information for accounts that aren't automated. It is my experience that in practice you will have some entries and accounts to track that will not have a nice downloadable file format, or that simply don't have a real-world counterpart. In order to produce a complete view of one's balance sheet, it is important to be able to enter all of an individual's account within a single system. In other words, custom accounts and manually entered transactions do matter a lot. For all the reasons mentioned above, I feel that a computer language is more appropriate to express this data structure than a heavy piece of software with a customized interface. Being able to easily bring up a text file and quickly type in a few lines of text to add a transaction is great\u2013it's fast and easy. The ledger file provides a naturally open way to express one's data, and can be source-controlled or shared between people as well. Multiple files can be merged together, and scripted manipulations on a source file can be used to reorganize one's data history. Furthermore, a read-only web interface that presents the various reports one expects and allows the user to explore different views on the dataset is sufficient for the purpose of viewing the data. Advantages of Command-Line Bookkeeping \uf0c1 In summary, there are many advantages to using a command-line accounting system over a commercial package that provides a user-interface: Fast. You don\u2019t have to fire up a slow program with a splash screen that will take a while to initialize in order to add something to your books. Bringing up a text file from a bookmark in your favorite editing program (I use Emacs) is easy and quick. And if you\u2019re normally using a text editor all day long, as many programmers do, you won\u2019t even blink before the file is in front of your eyes. It\u2019s quick and easy. Portable. It will work on all platforms. Beancount is written in Python 3 with some C extensions, and as such will work on Mac, Linux and Windows platforms. I am very careful and determined to keep external dependencies on third-party packages to an absolute minimum in order to avoid installation problems. It should be easy to install and update, and work everywhere the same. Openness. Your data is open, and will remain open forever . I plan on having my corpus of data until the day I die. With an open format you will never end up in a situation where your transactional data is sitting in a binary blob with an unknown format and the software goes unsupported. Furthermore, your data can be converted to other languages easily. You can easily invoke the parser I provide and write a script that spits it out in another program\u2019s input syntax. You can place it under version control. You can entirely reorganize the structure of your accounts by renaming strings with sed. Text is empowering. Customized. You can produce very customized reports, that address exactly the kinds of problems you are having. One complaint I often hear from people about other financial software is that it doesn\u2019t quite do what they want. With command-line accounting systems you can at least write it yourself, as a small extension that uses your corpus of data. Why not just use an SQL database? \uf0c1 I don\u2019t like to reinvent the wheel. If this problem could be solved by filling up an SQL database and then making queries on it, that\u2019s exactly what I would do. Creating a language is a large overhead, it needs to be maintained and evolved, it\u2019s not an easy task, but as it turns out, necessary and justified to solve this problem. The problem is due to a few reasons: Filtering occurs on a two-level data structure of transactions vs. their children postings, and it is inconvenient to represent the data in a single table upon which we could then make manipulations. You cannot simply work only with postings: in order to ensure that the reports balance, you need to be selecting complete transactions. When you select transactions, the semantics is \u201cselect all the postings for which the transaction has this or that property.\u201d One such property is \u201call transactions that have some posting with account X .\u201d These semantics are not obvious to implement with a database. The nature of the data structure makes it inconvenient. The wide variety of directives makes it difficult to design a single elegant table that can accommodate all of their data. Ideally we would want to define two tables for each type of directive: a table that holds all common data (date, source filename & lineno, directive type) and a table to hold the type-specific data. While this is possible, it steps away from the neat structure of a single table of data. Operations on inventories\u2014the data structure that holds the incrementally changing contents of accounts\u2014require special treatment that would be difficult to implement in a database. Lot reductions are constrained against a running inventory of lots, each of which has a specific cost basis. Some lot reductions trigger the merging of lots (for average cost booking). This requires some custom operations on these inventory objects. Aggregations (breakdowns) are hierarchical in nature. The tree-like structure of accounts allows us to perform operations on subtrees of postings. This would also not be easy to implement with tables. Finally, input would be difficult. By defining a language, we side-step the problem of having to build a custom user interface that would allow us to create the data. Nevertheless, I really like the idea of working with databases. A script (bean-sql) is provided to convert the contents of a Beancount file to an SQL database; it\u2019s not super elegant to carry out computations on those tables, but it\u2019s provided nonetheless. You should be able to play around with it, even if operations are difficult. I might even pre-compute some of the operation\u2019s outputs to make it easier to run SQL queries. But... I just want to do X ? \uf0c1 Some may infer that what we\u2019re doing must be terribly complicated given that they envision they might want to use such a system only for a single purpose. But the fact is, how many accounts you decide to track is a personal choice. You can choose to track as little as you want in as little detail as you deem sufficient. For example, if all that you\u2019re interested in is investing, then you can keep books on only your investment accounts. If you\u2019re interested in replacing your usage of Mint.com or Quicken , you can simply just replicate the statements for your credit cards and see your corresponding expenses. The simplicity of having to just replicate the transactions in all your accounts over doing a painful annual \u201cstarting from scratch\u201d evaluation of all your assets and expenses for the purpose of looking at your finances will convince you. Looking at your finances with a spreadsheet will require you to at least copy values from your portfolio. Every time you want to generate the report you\u2019ll have to update the values\u2026 with my method, you just update each account\u2019s full activity, and you can obtain the complete list holdings as a by-product. It\u2019s easy to bring everything up-to-date if you have a systematic method. To those I say: try it out, accounting just for the bits that you\u2019re interested in. Once you\u2019ll have a taste of how the double-entry method works and have learned a little bit about the language syntax, you will want to get a fuller picture. You will get sucked in. Why am I so Excited? \uf0c1 Okay, so I\u2019ve become an accounting nerd. I did not ask for it. Just kind-of happened while I wasn\u2019t looking (I still don\u2019t wear brown socks though). Why is it I can\u2019t stop talking about this stuff? I used to have a company. It was an umbrella for doing contract work (yes, I originally had bigger aspirations for it, but it ended up being just that. Bleh.) As a company owner in Canada, I had to periodically make five different kinds of tax installments to the Federal and Provincial governments, some every month, some every three months, and then it varied. An accountant was feeding me the \u201cmagic numbers\u201d to put in the forms, numbers which I was copying like a monkey, without fully understanding how they were going to get used later on. I had to count separately the various expenses I would incur in relation to my work (for deductions), and those which were only personal \u2014I did not have the enlightened view of getting separate credit cards so I was trying to track personal vs. company expenses from the same accounts. I also had to track transfers between company and personal accounts that would later on get reported as dividends or salary income. I often had multiple pending invoices that would take more than two months to get paid, from different clients. It was a demi-hell. You get the idea. I used to try to track all these things manually, using systems composed of little text files and spreadsheets and doing things very carefully and in a particular way. The thing is, although I am by nature quite methodical, I would sometimes, just sometimes forget to update one of the files. Things would fall out of sync. When this happened it was incredibly frustrating, I had to spend time digging around various statements and figure out where I had gone wrong. This was time-consuming and unpleasant. And of course, when something you have to do is unpleasant, you tend not to do it so well. I did not have a solid idea of the current state of affairs of my company during the year, I just worked and earned money for it. My accountant would draw a balance sheet once a year after doing taxes in July, and it was always a tiny bit of a surprise. I felt permanently somewhat confused, and frankly annoyed every time I had to deal with \u201cmoney things.\u201d It wasn\u2019t quite a nightmare, but it was definitely making me tired. I felt my life was so much simpler when I just had a job. But one day\u2026 one day\u2026 I saw the light: I discovered the double-entry method. I don\u2019t recall exactly how. I think it was on a website, yes\u2026 this site: dwmbeancounter.com (it\u2019s a wonderful site). I realized that using a single system , I could account for all of these problems at the same time, and in a way that would impose an inherent error-checking mechanism. I was blown away! I think I printed the whole thing on paper at the time and worked my way through every tutorial. This simple counting trick is exactly what I was in dire need for. But all the software I tried was either disappointing, broken, or too complicated. So I read up on Ledger. And I got in touch with its author . And then shortly after I started on Beancount 1 . I\u2019ll admit that going through the effort of designing my own system just to solve my accounting problems is a bit overkill, but I\u2019ve been known to be a little more than extreme about certain things , and I\u2019ve really enjoyed solving this problem. My life is fulfilled when I maintain a good balance of \u201clearning\u201d and \u201cdoing,\u201d and this falls squarely in the \u201cdoing\u201d domain. Ever since, I feel so excited about anything related to personal finance. Probably because it makes me so happy to have such a level of awareness about what\u2019s going on with mine. I even sometimes find myself loving spending money in a new way, just from knowing that I\u2019ll have to figure out how to account for it later. I feel so elated by the ability to solve these financial puzzles that I have had bouts of engaging complete weekends in building this software. They are small challenges with a truly practical application and a tangible consequence. Instant gratification. I get a feeling of empowerment . And I wish the same for you. For a full list of differences, refer to this document . \u21a9","title":"Command Line Accounting in Context"},{"location":"command_line_accounting_in_context.html#command-line-accounting-in-context","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/motivation This text provides context and motivation for the usage of double-entry command-line accounting systems to manage personal finances. Motivation What exactly is \u201cAccounting\u201d? What can it do for me? Keeping Books Generating Reports Custom Scripting Filing Documents How the Pieces Fit Together Why not just use a spreadsheet? Why not just use a commercial app? How about Mint.com? How about Quicken? How about Quickbooks? How about GnuCash? Why build a computer language? Advantages of Command-Line Bookkeeping Why not just use an SQL database? But... I just want to do X? Why am I so Excited?","title":"Command-line Accounting in Context"},{"location":"command_line_accounting_in_context.html#motivation","text":"When I tell people about my command-line accounting hacking activities, I get a variety of reactions. Sometimes I hear echoes of desperation about their own finances: many people wish they had a better handle and understanding of their finances, they sigh and wish they were better organized. Other times, after I describe my process I get incredulous reactions: \u201cWhy do you even bother?\u201d Those are the people who feel that their financial life is so simple that it can be summarized in 5 minutes. These are most often blissfully unaware how many accounts they have and have only an imprecise idea of where they stand. They have at least 20 accounts to their name; it\u2019s only when we go through the details that they realize that their financial ecosystem is more complex than they thought. Finally, there are those who get really excited about the idea of using a powerful accounting system but who don\u2019t actually understand what it is that I\u2019m talking about. They might think it is a system to support investment portfolios, or something that I use to curate a budget like a stickler. Usually they end up thinking it is too complicated to actually get started with. This document attempts to explain what command-line bookkeeping is about in concrete terms, what you will get out of doing it, how the various software pieces fit together, and what kinds of results you can expect from this method. The fact is, the double-entry method is a basic technique that everyone should have been taught in high school. And using it for yourself is a simple and powerful process you can use to drive your entire financial life.","title":"Motivation"},{"location":"command_line_accounting_in_context.html#what-exactly-is-accounting","text":"When one talks about \u201caccounting,\u201d they often implicitly refer to one or more of various distinct financial processes: Bookkeeping. Recording past transactions in a single place, a \u201cledger,\u201d also called \u201cthe books,\u201d as in, \u201cthe books of this company.\u201d Essentially, this means copying the amounts and category of financial transactions that occur in external account statements to a single system that includes all of the \u201caccounts\u201d relating to an entity and links them together. This is colloquially called \u201ckeeping books,\u201d or the activity of \u201cbookkeeping.\u201d Invoices. Preparing invoices and tracking payments. Contractors will often bring this up because it is a major concern of their activity: issuing requests to clients for payments for services rendered, and checking whether the corresponding payments have actually been received later on (and taking collection actions in case they haven\u2019t). If you\u2019re managing a company\u2019s finances, processing payroll is another aspect of this which is heavy in bookkeeping. Taxes. Finding or calculating taxable income, filling out tax forms and filing taxes to the various governmental authorities the entity is subject to. This process can be arduous, and for people who are beginning to see an increase in the complexity of their personal assets (many accounts of different types, new reporting requirements), it can be quite stressful. This is often the moment that they start thinking about organizing themselves. Expenses. Analyzing expenses, basically answering the question: \u201cWhere is my money going?\u201d A person with a small income and many credit cards may want to precisely track and calculate how much they\u2019re spending every month. This just develops awareness. Even in the presence of abundant resources, it is interesting to look at how much one is spending regularly, and where most of one\u2019s regular income is going, it often brings up surprises. Budgeting. Forecasting future expenses, allocating limited amounts to spend in various categories, and tracking how close one\u2019s actual spending is to those allocations. For individuals, this is usually in the context of trying to pay down debt or finding ways to save more. For companies, this occurs when planning for various projects. Investing. Summarizing and computing investment returns and capital gains. Many of us now manage our own savings via discount brokers, investing via ETFs and individually selected mutual funds. It is useful to be able to view asset distribution and risk exposure, as well as compute capital gains. Reporting. Public companies have regulatory requirements to provide transparency to their investors. As such, they usually report an annual income statement and a beginning and ending balance sheet . For an individual, those same reports are useful when applying for a personal loan or a mortgage at a bank, as they provide a window to someone\u2019s financial health situation. In this document, I will describe how command-line accounting can provide help and support for these activities.","title":"What exactly is \u201cAccounting\u201d?"},{"location":"command_line_accounting_in_context.html#what-can-it-do-for-me","text":"You might legitimately ask: sure, but why bother? We can look at the uses of accounting in terms of the questions it can answer for you: Where\u2019s my money, Lebowski? If you don\u2019t keep track of stuff, use cash a lot, have too many credit cards or if you are simply a disorganized and brilliant artist with his head in the clouds, you might wonder why there aren\u2019t as many beans left at the end of the month as you would like. An income statement will answer this question. I\u2019d like to be like Warren Buffet. How much do I have to save every month? I personally don\u2019t do a budget, but I know many people who do. If you set specific financial goals for a project or to save for later, the first thing you need to do is allocate a budget and set limits on your spending. Tracking your money is the first step towards doing that. You could even compute your returns in a way that can be compared against the market. I have some cash to invest. Given my portfolio, where should I invest it? Being able to report on the totality of your holdings, you can determine your asset class and currency exposures. This can help you decide where to place new savings in order to match a target portfolio allocation. How much am I worth? You have a 401k, an IRA and taxable accounts. A remaining student loan, or perhaps you\u2019re sitting on real-estate with two mortgages. Whatever. What\u2019s the total? It\u2019s really nice to obtain a single number that tells you how far you are from your retirement goals. Beancount can easily compute your net worth (to the cent). I still hurt from 2008. Are my investments safe? For example, I\u2019m managing my own money with various discount brokers, and many people are. I\u2019m basically running a miniature hedge fund using ETFs, where my goal is to be as diversified as possible between asset classes, sector exposure, currency exposure, etc. Plus, because of tax-deferred accounts I cannot do that in a single place. With Beancount you can generate a list of holdings and aggregate them by category. Taxes suck. Well, enough said. Why do they suck? Mostly because of uncertainty and doubt. It\u2019s not fun to not know what\u2019s going on. If you had all the data at your fingertips instantly it wouldn\u2019t be nearly as bad. What do you have to report? For some people there\u2019s a single stream of income, but for many others, it gets complicated (you might even be in that situation and you don\u2019t know it). Qualified vs. ordinary dividends, long-term vs. short-term capital gains, income from secondary sources, wash sales, etc. When it\u2019s time to do my taxes, I bring up the year\u2019s income statement, and I have a clear list of items to put in and deductions I can make. I want to buy a home. How much can I gather for a down payment? If you needed a lot of cash all of a sudden, how much could you afford? Well, if you can produce a list of your holdings, you can aggregate them \u201cby liquidity.\u201d This gives you an idea of available cash in case of an urgent need. Mr. Banker, please lend me some money. Impress the banker geek: just bring your balance sheet. Ideally the one generated fresh from that morning. You should have your complete balance sheet at any point in time. Instructions for your eventual passing. Making a will involves listing your assets. Make your children\u2019s lives easier should you pass by having a complete list of all the beans to be passed on or collected. The amounts are just part of the story: being able to list all the accounts and institutions will make it easier for someone to clear your assets. I can\u2019t remember if they paid me. If you send out invoices and have receivables, it\u2019s nice to have a method to track who has paid and who just says they\u2019ll pay. I\u2019m sure there is a lot more you can think of. Let me know.","title":"What can it do for me?"},{"location":"command_line_accounting_in_context.html#keeping-books","text":"Alright, so here\u2019s the part where I give you the insight of the method. The central and most basic activity that provides support for all the other ones is bookkeeping . A most important and fundamental realization is that this relatively simple act\u2014that of copying all of the financial transactions into a single integrated system\u2014allows you to produce reports that solve all of the other problems. You are building a corpus of data, a list of dated transaction objects that each represents movements of money between some of the accounts that you own. This data provides a full timeline of your financial activity. All you have to do is enter each transaction while respecting a simple constraint, that of the double-entry method , which is this: Each time an amount is posted to an account, it must have a corresponding inverse amount posted to some other account(s), and the sum of these amounts must be zero. That\u2019s it. This is the essence of the method, and an ensemble of transactions that respects this constraint acquires nice properties which are discussed in detail in some of my other documents. In order to generate sensible reports, all accounts are further labeled with one of four categories: Assets , Liabilities , Income and Expenses . A fifth category, Equity , exists only to summarize the history of all previous income and expense transactions. Command-line accounting systems are just simplistic computer languages for conveniently entering, reading and processing this transaction data, and ensure that the constraint of the double-entry method is respected. They\u2019re simple counting systems, that can add any kind of \u201cthing\u201d in dedicated counters (\u201caccounts\u201d). They\u2019re basically calculators with multiple buckets. Beancount and Ledger may differ slightly in their syntax and on some of their semantics, but the general principle is the same, and the simplifying assumptions are very similar. Here is a concrete example of what a transaction looks like, just so you can get a feeling for it. In a text file\u2014which is the input to a command-line accounting system\u2014something like the following is written and expresses a credit card transaction: 2014-05-23 * \"CAFE MOGADOR NEW YO\" \"Dinner with Caroline\" Liabilities:US:BofA:CreditCard -98.32 USD Expenses:Restaurant This is an example transaction with two \u201cpostings,\u201d or \u201clegs.\u201d The \u201c Expenses:Restaurant \u201d line is an account, not a category, though accounts often act like categories (there is no distinction between these two concepts). The amount on the expenses leg is left unspecified, and this is a convenience allowed by the input language. The software determines its amount automatically with the remainder of the balance which in this case will be 98.32 USD, so that -98.32 USD + 98.32 USD = 0 (remember that the sum of all postings must balance to zero\u2014this is enforced by the language). Most financial institutions provide some way for you to download a summary of account activity in some format or other. Much of the details of your transactions are automatically pulled in from such a downloaded file, using some custom script you write that converts it into the above syntax: A transaction date is always available from the downloadable files. The \u201c CAFE MOGADOR NEW YO \u201d bit is the \u201cmemo,\u201d also provided by the downloaded file, and those names are often good enough for you to figure out what the business you spent at was. Those memos are the same ugly names you would see appear on your credit card statements. This is attached to a transaction as a \u201cpayee\u201d attribute. I manually added \u201c Dinner with Caroline \u201d as a comment. I don\u2019t have to do this (it\u2019s optional), but I like to do it when I reconcile new transactions, it takes me only a minute and it helps me remember past events if I look for them. The importer brought in the Liabilities:US:BofA:CreditCard posting with its amount automatically, but I\u2019ve had to insert the Expenses:Restaurant account myself: I typed it in. I have shortcuts in my text editor that allow me to do that using account name completion, it takes a second and it\u2019s incredibly easy. Furthermore, pre-selecting this account could be automated as well, by running a simple learning algorithm on the previous history contained in the same input file (we tend to go to the same places all the time). The syntax gets a little bit more complicated, for example it allows you to represent stock purchases and sales, tracking the cost basis of your assets, and there are many other types of conveniences, like being able to define and count any kind of \u201cthing\u201d in an account (e.g., \u201cvacation hours accumulated\u201d), but this example captures the essence of what I do. I replicate all the transactions from all of the accounts that I own in this way, for the most part in an automated fashion. I spend about 1-2 hours every couple of weeks in order to update this input file, and only for the most used accounts (credit card and checking accounts). Other accounts I\u2019ll update every couple of months, or when I need to generate some reports. Because I have a solid understanding of my finances, this is not a burden anymore\u2026 it has become fun . What you obtain is a full history, a complete timeline of all the financial transactions from all the accounts over time, often connected together, your financial life, in a single text file .","title":"Keeping Books"},{"location":"command_line_accounting_in_context.html#generating-reports","text":"So what\u2019s the point of manicuring this file? I have code that reads it, parses it, and that can serve various views and aggregations of it on a local web server running on my machine, or produce custom reports from this stream of data. The most useful views are undeniably the balance sheet and income statement , but there are others: Balance Sheet. A balance sheet lists the final balance of all of your assets and liabilities accounts on a single page. This is a snapshot of all your accounts at a single point in time, a well-understood overview of your financial situation. This shows your net worth (very precisely, if you update all your accounts) and is also what a banker would be interested in if you were to apply for a loan. Beancount can produce my balance sheet at any point in time, but most often I\u2019m interested in the \u201ccurrent\u201d or \u201clatest\u201d balance sheet. Income Statement. An income statement is a summary of all income and expenses that occur between two points in time. It renders the final balance for these accounts in format familiar to any accountant: income accounts on the left, expense accounts on the right. This provides insights on the changes that occurred during a time period, a delta of changes. This tells you how much you\u2019re earning and where your money is going, in detail. The difference between the two is how much you saved. Beancount can render such a statement for any arbitrary period of time, e.g., this year, or month by month. Journals. For each account (or category, if you like to think of them that way), I can render a list of all the transactions that posted changes to that account. I call this a journal (Ledger calls this a \u201cregister\u201d). If you\u2019re looking at your credit card account\u2019s journal, for instance, it should match that of your bank statement. On the other hand, if you look at your \u201crestaurants expense\u201d account, it should show all of your restaurant outings, across all methods of payment (including cash, if you choose to enter that). You can easily fetch any detail of your financial history, like \u201cWhere was that place I had dinner with Arthur in March three years ago?\u201d or \u201cI want to sell that couch\u2026 how much did I pay for it again?\u201d Payables and Receivables. If you have amounts known to be received, for example, you filed your taxes or sent an invoice and are expecting a payment, or you mailed a check and you are expecting it to be cashed at some point in the future, you can track this with dedicated accounts. Transactions have syntax that allows you to link many of them together and figure out what has been paid or received. Calculating taxes. If you enter your salary deposits with the detail from your pay stub, you can calculate exactly how much taxes you\u2019ve paid, and the amounts should match exactly those that will be reported on your employer\u2019s annual income reporting form (e.g., W2 form in the USA, T4 if in Canada, Form P60 in the UK, or otherwise if you live elsewhere). This is particularly convenient if you have to make tax installments, for instance. If you\u2019re a contractor, you will have to track many such things, including company expensable expenses. It is also useful to count dividends that you have to report as taxable income. Investing. I\u2019m not quite rich, but I manage my own assets using discount brokers and mainly ETFs. In addition, I take advantage of a variety of tax sheltered accounts, with limited choices in assets. This is pretty common now. You could say that I\u2019m managing a miniature hedge fund and you wouldn\u2019t be too far from reality. Using Beancount, I can produce a detailed list of all holdings, with reports on daily market value changes, capital gains, dividends, asset type distribution (e.g. stocks vs. fixed income), currency exposure, and I can figure out how much of my assets are liquid, e.g., how much I have available towards a downpayment on a house. Precisely, and at any point in time. This is nice. I can also produce various aggregations of the holdings, i.e., value by account, by type of instrument, by currency. Forecasting. The company I work for has an employee stock plan, with a vesting schedule. I can forecast my approximate expected income including the vesting shares under reasonable assumptions. I can answer the question: \u201cAt this rate, at which date do I reach a net worth of X?\u201d Say, if X is the amount which you require for retirement. Sharing Expenses. If you share expenses with others, like when you go on a trip with friends, or have a roommate, the double-entry method provides a natural and easy solution to reconciling expenses together. You can also produce reports of the various expenses incurred for clarity. No uncertainty. Budgeting. I make enough money that I don\u2019t particularly set specific goals myself, but I plan to support features to do this. You basically end up managing your personal finances like a company would\u2026 but it\u2019s very easy because you\u2019re using a simple and cleverly designed computer language that makes a lot of simplifications (doing away with the concepts of \u201ccredit and debits\u201d for example), reducing the process to its essential minimum.","title":"Generating Reports"},{"location":"command_line_accounting_in_context.html#custom-scripting","text":"The applications are endless. I have all sorts of wild ideas for generating reports for custom projects. These are useful and fun experiments, \u201cchallenges\u201d as I call them. Some examples: I once owned a condo unit and I\u2019ve been doing double-entry bookkeeping throughout the period I owned it, through selling it. All of the corresponding accounts share the Loft4530 name in them. This means that I could potentially compute the precise internal rate of return on all of the cash flows related to it, including such petty things as replacement light bulbs expenses. To consider it as a pure investment. Just for fun. I can render a tree-map of my annual expenses and assets. This is a good visualization of these categories, that preserve their relative importance. I could look at average expenses with a correction for the time-value of money. This would be fun, tell me how my cost of living has changed over time. The beauty of it is that once you have the corpus of data, which is relatively easy to create if you maintain it incrementally, you can do all sorts of fun things by writing a little bit of Python code. I built Beancount to be able to do that in two ways: By providing a \u201cplugins\u201d system that allows you to filter a parsed set of transactions. This makes it possible for you to hack the syntax of Beancount and prototype new conveniences in data entry. Plugins provided by default provide extended features just by filtering and transforming the list of parsed transactions. And it\u2019s simple: all you have to do is implement a callback function with a particular signature and add a line to your input file. By writing scripts. You can parse and obtain the contents of a ledger with very little code. In Python, this looks no more complicated than this: import beancount.loader \u2026 entries, errors, options = beancount.loader.load_file('myfile.ledger') for entry in entries: \u2026 Voila. You\u2019re on your way to spitting out whatever output you want. You have access to all the libraries in the Python world, and my code is mostly functional, heavily documented and thoroughly unit-tested. You should be able to find your way easily. Moreover, if you\u2019re uncertain about using this system, you could just use it to begin entering your data and later write a script that converts it into something else.","title":"Custom Scripting"},{"location":"command_line_accounting_in_context.html#filing-documents","text":"If you have defined accounts for each of your real-world accounts, you have also created a natural method for organizing your statements and other paper documents. As we are communicating more and more by email with our accountants and institutions, it is becoming increasingly common to scan letters to PDFs and have those available as computer files. All the banks have now gone paperless, and you can download these statements if you care (I tend to do this once at the end of the year, for preservation, just in case). It\u2019s nice to be able to organize these nicely and retrieve those documents easily. What I do is keep a directory hierarchy mirroring the account names that I\u2019ve defined, something that looks like this: .../documents/ Assets/ US/ TDBank/ Checking/ 2014-04-08.statement.march.pdf 2014-05-07.statement.april.pdf \u2026 Liabilities/ US/ Amex/ Platinum/ 2014-04-17.March.pdf 2014-04-19.download.ofx \u2026 Expenses/ Health/ Medical/ 2014-04-02.anthem.eob-physiotherapy.pdf \u2026 These example files would correspond to accounts with names Assets:US:TDBank:Checking , Liabilities:US:Amex:Platinum , and Expenses:Health:Medical . I keep this directory under version control. As long as a file name begins with a date, such as \u201c 2014-04-02 \u201d, Beancount is able to find the files automatically and insert directives that are rendered in its web interface as part of an account\u2019s journal, which you can click on to view the document itself. This allows me to find all my statements in one place, and if I\u2019m searching for a document, it has a well-defined place where I know to find it. Moreover, the importing software I wrote is able to identify downloaded files and automatically move them into the corresponding account\u2019s directory.","title":"Filing Documents"},{"location":"command_line_accounting_in_context.html#how-the-pieces-fit-together","text":"So I\u2019ve described what I do to organize my finances. Here I\u2019ll tell you about the various software pieces and how they fit together: Beancount is the core of the system. It reads the text file I update, parses the computer language I\u2019ve defined and produces reports from the resulting data structures. This software only reads the input file and does not communicate with other programs on purpose. It runs in isolation. Beancount\u2019s ingest package and tools help automate the updating of account data by extracting transactions from file downloads from banks and other institutions. These tools orchestrate the running of importers which you implement (this is where all the messy importing code lives, the code you need to make it easier to keep your text file up-to-date, which can parse OFX and CSV files, for instance). See bean-extract, bean-identify tools. The ingest package also helps with filing documents (see bean-file tool). Because it is able to identify which document belongs to which account, it can move the downloaded file to my documents archive automatically. This saves me time. Finally, in order to provide market values, a Beancount input file should have suitable price directives. Beancount also contains code to fetch latest or historical prices for the various commodities present in one\u2019s ledger file (see the bean-price tool). Like the extraction of transactions from OFX downloads, it also spits out Beancount input syntax used to define prices. See the diagram below for a pretty picture that illustrates how these work together.","title":"How the Pieces Fit Together"},{"location":"command_line_accounting_in_context.html#why-not-just-use-a-spreadsheet","text":"This is indeed a good question, and spreadsheets are incredibly useful for sure. I certainly would not be writing my own software if I could track my finances with a spreadsheet. The problem is that the intrinsic structure of the double-entry transactional data does not lend itself to a tabular representation. Each transaction has multiple attributes (date, narration, tags), and two or more legs, each of which has an associated amount and possibly a cost. If you put the dates on one axis and the accounts on the other, you would end up with a very sparse and very large table; that would not be very useful, and it would be incredibly difficult to edit. If on the other hand, you had a column dedicated to account names for each row, all of your computations would have to take the account cell into the calculation logic. It would be very difficult to deal with, if not impossible. All matters of data aggregations are performed relative to account names. Moreover, dealing with the accumulation of units with a cost basis would require difficult gymnastics. I don\u2019t even know how I would proceed forward to do this in a spreadsheet. A core part of command-line accounting systems is the inventory logic that allows you to track a cost basis for every unit held in an account and book reductions of positions against existing lots only. This allows the system to compute capital gains automatically. And this is related to the codes that enforce the constraint that all postings on a transaction balance out to zero. I believe that the \u201ctransaction <-> postings\u201d data representation combined with a sensible method for updating inventories is the essence of this system. The need for these is the justification to create a dedicated method to build this data, such as a computer language. Finally, having our own syntax offers the opportunity to provide other types of useful directives such as balance assertions, open/close dates, and sophisticated filtering of subsets of transactions. You just cannot do what we\u2019re doing in a spreadsheet.","title":"Why not just use a spreadsheet?"},{"location":"command_line_accounting_in_context.html#why-not-just-use-a-commercial-app","text":"","title":"Why not just use a commercial app?"},{"location":"command_line_accounting_in_context.html#how-about-mintcom","text":"Oftentimes people tell me they\u2019re using Mint.com to track their finances, usually with some amount of specific complaints following close behind. Online services such as Mint provide a subset of the functionality of double-entry accounting. The main focus of these services is the reporting of expenses by category, and the maintenance of a budget. As such, they do a great job at automating the download of your credit card and bank transactions. I think that if you want a low-maintenance option to tracking your finances and you don\u2019t have a problem with the obvious privacy risks, this is a fantastic option: it comes with a web interface, mobile apps, and updating your accounts probably requires clicking some buttons, providing some passwords, and a small amount of manual corrections every couple of weeks. I have several reservations about using it for myself, however: Passwords. I\u2019m just not comfortable enough with any commercial company to share the passwords to all my bank, credit card, and investment accounts. This sounds like an insane idea to me, a very scary one, and I\u2019m just not willing to put that much trust in any one company\u2019s hands. I don\u2019t care what they say: I worked for several software companies and I\u2019m aware of the disconnect between promises made by salespeople, website PR, and engineering reality. I also know the power of determined computer hackers. Insufficient reporting. Reporting is probably beautiful and colorful\u2014it\u2019s gorgeous on the website\u2014but certainly insufficient for all the reporting I want to do on my data. Reporting that doesn\u2019t do what they want it to is a common complaint I hear about these systems. With my system, I can always write a script and produce any kind of report I might possibly want in the future. For instance, spitting out a treemap visualization of my expenses instead of a useless pie chart. I can slice and dice my transactions in all kinds of unique ways. I can tag subsets of transactions for projects or trips and report on them. It\u2019s more powerful than generic reporting. Perennity. What if the company is not in business in 5 years? Where will my data be? If I spend any time at all curating my financial data, I want to ensure that it will be available to me forever, in an open format. In their favor, some of these sites probably have a downloadable option, but who uses it? Does it even work well? Would it include all of the transactions that they downloaded? I don\u2019t know. Not international. It probably does not work well with an international, multi-currency situation. These services target a \u201cmajority\u201d of users, most of which have all their affairs in a single country. I live in the USA, have a past history in Canada, which involves remaining tax-sheltered investment accounts, occasional expenses during visits which justify maintaining a credit card there, and a future history which might involve some years spent in another country such as Hong Kong or Australia. Will this work in Mint? Probably not. They might support Canada, but will they support accounts in both places? How about some other country I might want to move to? I want a single integrated view of all my accounts across all countries in all currencies forever, nothing less. My system supports that very well. (I\u2019m not aware of any double-entry system that deals with the international problem in a currency agnostic way as well as Beancount does.) Inability to deal with particular situations. What if I own real estate? Will I be able to price the value of my home at a reasonable amount, so it creates an accurate balance sheet? Regularly obtaining \u201ccomparables\u201d for my property from an eager real estate agent will tell me much more precisely how much my home is worth than services like Zillow ever could. I need to be able to input that for my balance sheet. What about those stock options from that privately held Australian company I used to work for? How do I price that? How about other intangible things, such as receivables from a personal loan I made to a friend? I doubt online services are able to provide you with the ability to enter those. If you want the whole picture with precision, you need to be able to make these adjustments. Custom tracking. Using \u201cimaginary currencies\u201d, I\u2019m able to track all kinds of other things than currencies and stocks. For example, by using an \u201c IRAUSD \u201d commodity in Beancount I\u2019m able to track how many 401k contributions I\u2019ve made at any point during the year. I can count the after-tax basis of a traditional IRA account similarly. I can even count my vacation hours using an imaginary \u201c VACHR \u201d currency and verify it against my pay stubs. Capital gains reporting. I haven\u2019t tried it myself, but I\u2019ve heard some discontent about Mint from others about its limited capabilities for capital gains reporting. Will it maintain trade lots? Will it handle average cost booking? How about PnL on FOREX gains? What about revenue received for that book I\u2019m selling via PayPal? I want to be able to use my accounting system as an input for my tax reporting. Cash transactions. How difficult is it to enter cash transactions? Do I have to log in, or start a slow, heavy program that will only work under Windows? With Beancount I bring up a file in a text editor, this is instant and easy. Is it even possible to enter custom cash entries in online services? In other words, I\u2019m a very sophisticated user and yes, a bit of a control freak . I\u2019m not lying about it. I don\u2019t have any ideological objections about using a commercial service, it is probably not for me. If it\u2019s good enough for you, suit yourself. Despite their beautiful and promising websites, I haven\u2019t heard of anyone being completely satisfied with these services. I hear a fair amount of complaining, some mild satisfaction, but I have yet to meet anyone who raves about it.","title":"How about Mint.com?"},{"location":"command_line_accounting_in_context.html#how-about-quicken","text":"Quicken is a single-entry system, that is, it replicates the transactions of a remote account locally, and allows you to add a label to each transaction to place it in a category. I believe it also has support for synchronizing investment accounts. This is not enough for me, I want to track all kinds of things, and I want to use the double-entry method, which provides an intrinsic check that I\u2019ve entered my data correctly. Single-entry accounting is just not good enough if you\u2019ve already crossed the bridge of understanding the double-entry method.","title":"How about Quicken?"},{"location":"command_line_accounting_in_context.html#how-about-quickbooks","text":"So let\u2019s talk about sophisticated software that is good enough for a company. Why wouldn\u2019t I use that instead? If it\u2019s good enough for small businesses, it should be good enough for me, no? There\u2019s Quickbooks and other ones. Why don\u2019t I use them: It costs money. Commercial software comes at a price. Ok, I probably could afford to pay a few hundred dollars per year (Quickbooks 2014 looks like around 300$/year for the full set of features), but I don\u2019t really want to. Platform. These softwares usually run on Microsoft Windows and sometimes on Mac OS X . I\u2019m a software developer, I mostly use Linux , and a Macbook Air for a laptop, on which I get annoyed running anything other than tmux and a web browser . I\u2019m not going to reboot just to enter a quick cash transaction. Slow startup. I cannot speak specifically to Quickbooks\u2019 implementation, but virtually every software suite of commercial scope I\u2019ve had to use had a splash screen and a slow, slow startup that involved initializing tons of plugins. They assume you\u2019re going to spend hours in it, which is reasonable for commercial users, but not for me, if I want to do a quick update of my ledger. UIs are inadequate. I haven\u2019t seen their UI but given the nature of transactions and my desire to input precisely and search quickly and organize things, I want to be able to edit in as text. I imagine it would be inconvenient for me. With Emacs and org-mode , I can easily i-search my way to any transaction within seconds after opening my ledger file. Inflexible. How would I go about re-organizing all my account names? I think I\u2019m still learning about the double-entry bookkeeping method and I have made mistakes in the past, mistakes where I desire to revisit the way I organize my accounts in a hierarchy. With my text file, I was able to safely rename a large number of accounts several times, and evolve my chart-of-accounts to reflect my improving understanding of how to organize my financial tracking system. Text is powerful!","title":"How about Quickbooks?"},{"location":"command_line_accounting_in_context.html#how-about-gnucash","text":"I don\u2019t like UIs; they\u2019re inconvenient. There\u2019s nothing quite like editing a text file if you are a programmer. Moreover, GnuCash does not deal with multiple currencies well. I find bugs in it within the first hour every time I kick the tires on it, which I do every couple of years, out of curiosity. Other programs, such as Skrooge, also take the heavy-handed big UI approach.","title":"How about GnuCash?"},{"location":"command_line_accounting_in_context.html#why-build-a-computer-language","text":"A bookkeeping system provides conditions for a solution that involves a simple computer language for many reasons. Single-entry bookkeeping is largely insufficient if you're trying to track everything holistically. Existing systems either limit themselves to expense categories with little checking beyond \"reconciling\" which sometimes involves freezing the past. If you're not doing the bookkeeping for a company, sometimes just changing the past and fixing the mistakes where they occurred makes more sense. More importantly, the single-entry method leaves us wanting for the natural error-checking mechanism involved in the double-entry system. The problem is also not solvable elegantly by using spreadsheets; the simple data structure that forms the basis of the double-entry system infers either a very sparse spreadsheet with accounts on one dimension and transactions on the other. For real-world usage, this is impractical. Another iteration on this theme would involve inserting the postings with two columns, one with the account and one with the amount, but the varying number of columns and the lookup code makes this inelegant as well. Plus, it's not obvious how you would deal with a large number of currencies. Programs that provide fancy graphical or web-based user interfaces are inevitably awkward, due to the nature of the problem: each transaction is organized by viewing it through the lens of one account's journal, but any of the accounts present in its postings provide equally valid views. Ideally, what you want, is just to look at the transaction. Organizing them for most convenient input has little to do with the order in which they are to be presented. Using text has a lot of advantages: You can easily used search-and-replace and/or sed to make global changes, for example, rename or reorganize your accounts; You can organize the transactions in the order that is most convenient for data entry; There are a number of existing tools to search the text; You can easily write various little tools to spit out the data syntax, i.e., for importing from other file types, or converting from other systems. Text is inherently open , that is the file format is one that you can read your data from and store anywhere else, not a blob of incomprehensible data that becomes unusable when the company that makes the software stops supporting it. Finally, systems that attempt to automate the process of importing all your data from automated sources (e.g., mint.com) have one major shortfall: most often it's not very easy or even possible to add information for accounts that aren't automated. It is my experience that in practice you will have some entries and accounts to track that will not have a nice downloadable file format, or that simply don't have a real-world counterpart. In order to produce a complete view of one's balance sheet, it is important to be able to enter all of an individual's account within a single system. In other words, custom accounts and manually entered transactions do matter a lot. For all the reasons mentioned above, I feel that a computer language is more appropriate to express this data structure than a heavy piece of software with a customized interface. Being able to easily bring up a text file and quickly type in a few lines of text to add a transaction is great\u2013it's fast and easy. The ledger file provides a naturally open way to express one's data, and can be source-controlled or shared between people as well. Multiple files can be merged together, and scripted manipulations on a source file can be used to reorganize one's data history. Furthermore, a read-only web interface that presents the various reports one expects and allows the user to explore different views on the dataset is sufficient for the purpose of viewing the data.","title":"Why build a computer language?"},{"location":"command_line_accounting_in_context.html#advantages-of-command-line-bookkeeping","text":"In summary, there are many advantages to using a command-line accounting system over a commercial package that provides a user-interface: Fast. You don\u2019t have to fire up a slow program with a splash screen that will take a while to initialize in order to add something to your books. Bringing up a text file from a bookmark in your favorite editing program (I use Emacs) is easy and quick. And if you\u2019re normally using a text editor all day long, as many programmers do, you won\u2019t even blink before the file is in front of your eyes. It\u2019s quick and easy. Portable. It will work on all platforms. Beancount is written in Python 3 with some C extensions, and as such will work on Mac, Linux and Windows platforms. I am very careful and determined to keep external dependencies on third-party packages to an absolute minimum in order to avoid installation problems. It should be easy to install and update, and work everywhere the same. Openness. Your data is open, and will remain open forever . I plan on having my corpus of data until the day I die. With an open format you will never end up in a situation where your transactional data is sitting in a binary blob with an unknown format and the software goes unsupported. Furthermore, your data can be converted to other languages easily. You can easily invoke the parser I provide and write a script that spits it out in another program\u2019s input syntax. You can place it under version control. You can entirely reorganize the structure of your accounts by renaming strings with sed. Text is empowering. Customized. You can produce very customized reports, that address exactly the kinds of problems you are having. One complaint I often hear from people about other financial software is that it doesn\u2019t quite do what they want. With command-line accounting systems you can at least write it yourself, as a small extension that uses your corpus of data.","title":"Advantages of Command-Line Bookkeeping"},{"location":"command_line_accounting_in_context.html#why-not-just-use-an-sql-database","text":"I don\u2019t like to reinvent the wheel. If this problem could be solved by filling up an SQL database and then making queries on it, that\u2019s exactly what I would do. Creating a language is a large overhead, it needs to be maintained and evolved, it\u2019s not an easy task, but as it turns out, necessary and justified to solve this problem. The problem is due to a few reasons: Filtering occurs on a two-level data structure of transactions vs. their children postings, and it is inconvenient to represent the data in a single table upon which we could then make manipulations. You cannot simply work only with postings: in order to ensure that the reports balance, you need to be selecting complete transactions. When you select transactions, the semantics is \u201cselect all the postings for which the transaction has this or that property.\u201d One such property is \u201call transactions that have some posting with account X .\u201d These semantics are not obvious to implement with a database. The nature of the data structure makes it inconvenient. The wide variety of directives makes it difficult to design a single elegant table that can accommodate all of their data. Ideally we would want to define two tables for each type of directive: a table that holds all common data (date, source filename & lineno, directive type) and a table to hold the type-specific data. While this is possible, it steps away from the neat structure of a single table of data. Operations on inventories\u2014the data structure that holds the incrementally changing contents of accounts\u2014require special treatment that would be difficult to implement in a database. Lot reductions are constrained against a running inventory of lots, each of which has a specific cost basis. Some lot reductions trigger the merging of lots (for average cost booking). This requires some custom operations on these inventory objects. Aggregations (breakdowns) are hierarchical in nature. The tree-like structure of accounts allows us to perform operations on subtrees of postings. This would also not be easy to implement with tables. Finally, input would be difficult. By defining a language, we side-step the problem of having to build a custom user interface that would allow us to create the data. Nevertheless, I really like the idea of working with databases. A script (bean-sql) is provided to convert the contents of a Beancount file to an SQL database; it\u2019s not super elegant to carry out computations on those tables, but it\u2019s provided nonetheless. You should be able to play around with it, even if operations are difficult. I might even pre-compute some of the operation\u2019s outputs to make it easier to run SQL queries.","title":"Why not just use an SQL database?"},{"location":"command_line_accounting_in_context.html#but-i-just-want-to-do-x","text":"Some may infer that what we\u2019re doing must be terribly complicated given that they envision they might want to use such a system only for a single purpose. But the fact is, how many accounts you decide to track is a personal choice. You can choose to track as little as you want in as little detail as you deem sufficient. For example, if all that you\u2019re interested in is investing, then you can keep books on only your investment accounts. If you\u2019re interested in replacing your usage of Mint.com or Quicken , you can simply just replicate the statements for your credit cards and see your corresponding expenses. The simplicity of having to just replicate the transactions in all your accounts over doing a painful annual \u201cstarting from scratch\u201d evaluation of all your assets and expenses for the purpose of looking at your finances will convince you. Looking at your finances with a spreadsheet will require you to at least copy values from your portfolio. Every time you want to generate the report you\u2019ll have to update the values\u2026 with my method, you just update each account\u2019s full activity, and you can obtain the complete list holdings as a by-product. It\u2019s easy to bring everything up-to-date if you have a systematic method. To those I say: try it out, accounting just for the bits that you\u2019re interested in. Once you\u2019ll have a taste of how the double-entry method works and have learned a little bit about the language syntax, you will want to get a fuller picture. You will get sucked in.","title":"But... I just want to do X?"},{"location":"command_line_accounting_in_context.html#why-am-i-so-excited","text":"Okay, so I\u2019ve become an accounting nerd. I did not ask for it. Just kind-of happened while I wasn\u2019t looking (I still don\u2019t wear brown socks though). Why is it I can\u2019t stop talking about this stuff? I used to have a company. It was an umbrella for doing contract work (yes, I originally had bigger aspirations for it, but it ended up being just that. Bleh.) As a company owner in Canada, I had to periodically make five different kinds of tax installments to the Federal and Provincial governments, some every month, some every three months, and then it varied. An accountant was feeding me the \u201cmagic numbers\u201d to put in the forms, numbers which I was copying like a monkey, without fully understanding how they were going to get used later on. I had to count separately the various expenses I would incur in relation to my work (for deductions), and those which were only personal \u2014I did not have the enlightened view of getting separate credit cards so I was trying to track personal vs. company expenses from the same accounts. I also had to track transfers between company and personal accounts that would later on get reported as dividends or salary income. I often had multiple pending invoices that would take more than two months to get paid, from different clients. It was a demi-hell. You get the idea. I used to try to track all these things manually, using systems composed of little text files and spreadsheets and doing things very carefully and in a particular way. The thing is, although I am by nature quite methodical, I would sometimes, just sometimes forget to update one of the files. Things would fall out of sync. When this happened it was incredibly frustrating, I had to spend time digging around various statements and figure out where I had gone wrong. This was time-consuming and unpleasant. And of course, when something you have to do is unpleasant, you tend not to do it so well. I did not have a solid idea of the current state of affairs of my company during the year, I just worked and earned money for it. My accountant would draw a balance sheet once a year after doing taxes in July, and it was always a tiny bit of a surprise. I felt permanently somewhat confused, and frankly annoyed every time I had to deal with \u201cmoney things.\u201d It wasn\u2019t quite a nightmare, but it was definitely making me tired. I felt my life was so much simpler when I just had a job. But one day\u2026 one day\u2026 I saw the light: I discovered the double-entry method. I don\u2019t recall exactly how. I think it was on a website, yes\u2026 this site: dwmbeancounter.com (it\u2019s a wonderful site). I realized that using a single system , I could account for all of these problems at the same time, and in a way that would impose an inherent error-checking mechanism. I was blown away! I think I printed the whole thing on paper at the time and worked my way through every tutorial. This simple counting trick is exactly what I was in dire need for. But all the software I tried was either disappointing, broken, or too complicated. So I read up on Ledger. And I got in touch with its author . And then shortly after I started on Beancount 1 . I\u2019ll admit that going through the effort of designing my own system just to solve my accounting problems is a bit overkill, but I\u2019ve been known to be a little more than extreme about certain things , and I\u2019ve really enjoyed solving this problem. My life is fulfilled when I maintain a good balance of \u201clearning\u201d and \u201cdoing,\u201d and this falls squarely in the \u201cdoing\u201d domain. Ever since, I feel so excited about anything related to personal finance. Probably because it makes me so happy to have such a level of awareness about what\u2019s going on with mine. I even sometimes find myself loving spending money in a new way, just from knowing that I\u2019ll have to figure out how to account for it later. I feel so elated by the ability to solve these financial puzzles that I have had bouts of engaging complete weekends in building this software. They are small challenges with a truly practical application and a tangible consequence. Instant gratification. I get a feeling of empowerment . And I wish the same for you. For a full list of differences, refer to this document . \u21a9","title":"Why am I so Excited?"},{"location":"exporting_your_portfolio.html","text":"Exporting Your Portfolio \uf0c1 Martin Blais , December 2015 (v2) http://furius.ca/beancount/doc/export Overview \uf0c1 This document explains how to export your portfolio of holdings from Beancount to a Google Finance portfolio (and eventually to other portfolio tracking websites). Note: This is the second version of this document, rewritten in Dec 2015, after greatly simplifying the process of exporting portfolios and completely separating the specification of stock tickers for price downloads. This new, simplified version only uses a single metadata field name: \u201cexport\u201d. The previous document can be found here . Portfolio Tracking Tools \uf0c1 There are multiple websites on the internet that allow someone to create a portfolio of investments (or upload a list of transactions to create such a portfolio) and that reports on the changes in the portfolio due to price movements, shows you unrealized capital gains, etc. One such website is the Google Finance portal. Another example is the Yahoo Finance one. These are convenient because they allow you to monitor the impact of price changes on your entire portfolio of assets, across all accounts, during the day or otherwise. However, each of these sites expects their users to use their interfaces and workflows to painfully enter each of the positions one-by-one. A great advantage of using Beancount is that you should never have to enter this type of information manually; instead, you should be able to extract it and upload it to one of these sites. You can be independent of the particular portfolio tracking service you use and should be able to switch between them without losing any data; Beancount can serve as your pristine source for your list of holdings as your needs evolve. Google Finance supports an \u201cimport\u201d feature to create portfolio data which supports the Microsoft OFX financial interchange data format. In this document, we show how we built a Beancount report that exports the portfolio of holdings to OFX for creating a Google Finance portfolio. Exporting to Google Finance \uf0c1 Exporting your Holdings to OFX \uf0c1 First, create an OFX file corresponding to your Beancount holdings. You can use this command to do this: bean-report file.beancount export_portfolio > portfolio.ofx See the report\u2019s own help for options: bean-report file.beancount export_portfolio --help Importing the OFX File in Google Finance \uf0c1 Then we have to import that OFX file in a web-based portfolio. Visit http://finance.google.com and click on \u201cPortfolios\u201d on the left (or simply visit https://www.google.com/finance/portfolio , this works as of Jan 2015) If you have an existing, previously imported portfolio, click on \u201cDelete Portfolio\u201d to get rid of it. Click on \u201cImport Transactions\u201d, then \u201cChoose File\u201d and select the portfolio.ofx file you exported to, then click on \u201cPreview Import\u201d. You should see a list of imported lots, with familiar stock symbols and names, and Type \u201cBuy\u201d with realistic Shares and Price columns. If not, see the note below. Otherwise, scroll to the bottom of the page and click \u201cImport\u201d. Your portfolio should now appear. You are done. You should never bother updating this portfolio directly using the website\u2026 instead, update your Beancount ledger file, re-export to a new OFX file, delete the previous portfolio and re-import a brand new one over it. Your pristine source is always your Beancount file, ideally you should never have to be worried about corrupting or deleting the portfolio data in any external website. Controlling Exported Commodities \uf0c1 Declaring Your Commodities \uf0c1 Generally, we recommend that you explicitly declare each of the commodities used in your input file. It is a neat place to attach information about those commodities, metadata that you should be able to use later on from bean-query or in scripts that you make. For example, you could declare a human-readable description of the commodity, and some other attributes, like this: 2001-09-06 commodity XIN name: \"iShares MSCI EAFE Index ETF (CAD-Hedged)\" class: \"Stock\" type: \"ETF\" ... Beancount will work with or without these declarations (it automatically generates Commodity directives if you haven\u2019t provided them). If you like to be strict and have a bit of discipline, you can require that each commodity be declared by using a plugin that will issue an error when an undeclared commodity appears: plugin \"beancount.plugins.check_commodity\" You can use any date for that Commodity directive. I recommend using the date of the commodity\u2019s inception, or perhaps when it was first introduced by the issuing country, if it is a currency. You can find a suitable date on Wikipedia or on the issuer\u2019s websites. Google Finance may have the date itself. What Happens by Default \uf0c1 By default, all holdings are exported as positions with a ticker symbol named the same as the Beancount commodity that you used to define them. If you have a holding of \u201cAAPL\u201d units, it will create an export entry for \u201cAAPL\u201d. The export code attempts to export all holdings by default. However, in any but the simplest unambiguous cases, this is probably not good enough to produce a working Google Finance portfolio. The name for each commodity that you use in your Beancount input file may or may not correspond to a financial instrument in the Google Finance database; due to the very large number of symbols supported in its database, just specifying the ticker symbol is often ambiguous. Google Finance attempts to resolve an ambiguous symbol string to the most likely instrument in its database. It is possible that it resolves it to a different financial instrument from the one you intended. So even if you use the same basic symbol that is used by the exchange, you often still need to disambiguate the symbol by specifying which exchange or symbology it lives in. Google provides a list of these symbol spaces . Here is a real-life example. The symbol for the \u201c CAD-Hedged MSCI EAFE Index \u201d ETF product issued by iShares/Blackrock is \u201c XIN \u201d on the Toronto Stock Exchange ( TSE ). If you just looked up \u201cXIN\u201d on Google Finance , it would choose to resolve it by default to the more likely \u201c NYSE:XIN \u201d symbol ( Xinyuan Real Estate Co. on the New York Stock Exchange ). So you need to disambiguate it by specifying that the desired ETF ticker for this instrument is \u201c TSE:XIN \u201d. Explicitly Specifying Exported Symbols \uf0c1 You can specify which exchange-specific symbol is used to export a commodity by attaching an \u201c export \u201d metadata field to each of your Commodity directives, like this: 2001-09-06 commodity XIN ... export: \"TSE:XIN\" The \u201c export \u201d field is used to map your commodity name to the corresponding instrument in the Google Finance system. If a holding in that commodity needs to be exported, this code is used instead of the Beancount currency name. The symbology used by Google Finance appears to follow the following syntax: Exchange:Symbol \uf0c1 where Exchange is a code either for the exchange where the stock trades, or for another source of financial data, e.g. \u201c MUTF \u201d for \u201cmutual funds in the US\u201d, and more . Symbol is a name that is unique within that exchange. I recommend searching for each of your financial instruments in Google Finance, confirming that the instrument corresponds to your instrument (by inspecting the full name, description and price), and inserting the corresponding code like this. Exporting to a Cash Equivalent \uf0c1 To account for positions that aren\u2019t supported in Google Finance, the export report can convert a holding to its cash-equivalent value. This is also useful for cash positions (e.g., cash sitting idle in a savings or checking account). For example, I hold units of an insurance policy investment vehicle (this is common in Canada, for example, with London Life). This is a financial instrument, but each particular policy issuance has its own associated value\u2014there is no public source of data for each of those products, it\u2019s rather opaque, I can obtain its value with my annual statement, but definitely not in Google Finance. But I\u2019d still like for the asset\u2019s value to be reflected in my portfolio. The way you tell the export code to make this conversion is to specify a special value of \u201cCASH\u201d for the \u201cexport\u201d field, like this: 1878-01-01 commodity LDNLIFE export: \"CASH\" This would convert holdings in LDNLIFE commodities to their corresponding quoted value before exporting, using the price nearest to the date of exporting. Note that attempting to convert to cash a commodity that does not have a corresponding cost or price available for us to determine its value will generate an error. A price must be present to make the conversion. Simple currencies should also be marked as cash in order to be exported: 1999-01-01 commodity EUR name: \"European Union Euro currency\" export: \"CASH\" Finally, all converted holdings are agglomerated into a single cash position. There is no point in exporting these cash entries to separate OFX entries because the Google Finance code will agglomerate them to a single one anyhow. Declaring Money Instruments \uf0c1 There is a small hiccup in this cash conversion story: the Google Finance importer does not appear to correctly grok an OFX position in \u201ccash\u201d amounts in the importer; I think this is probably just a bug in Google Finance\u2019s import code (or perhaps I haven\u2019t found the correct OFX field values to make this work). Instead, in order to insert a cash position the exporter uses a cash-equivalent commodity which always prices at 1 unit of the currency, e.g. $1.00 for US dollars. For example, for US dollars I I use VMMXX which is a Vanguard Prime Money Market Fund, and for Canadian dollars I use IGI806 . A good type of commodity for this is some sort of Money Market fund. It doesn\u2019t matter so much which one you use, as long as it prices very close to 1. Find one. If you want to include cash commodities, you need to find such a commodity for each of the cash currencies you have on your books and tell Beancount about them. Typically that will be only one or two currencies. You declare them by append the special value \u201c MONEY \u201d for the \u201c export \u201d field, specifying which currency this commodity represents, like this: 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" 1900-01-01 commodity IGI806 export: \"MUTF_CA:IGI806 (MONEY:CAD)\" Ignoring Commodities \uf0c1 Finally, some commodities held in a ledger should be ignored. This is the case for the imaginary commodities used in mirror accounting, for example, to track unvested shares of an employment stock plan, or commodities used to track amounts contributed to a retirement account, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" You tell the export code to ignore a commodity specifying the special value \u201c IGNORE \u201d for the \u201c export \u201d field, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" export: \"IGNORE\" All holdings in units of RSPCAD will thus not be exported. The question of whether some commodities should be exported or not sometimes presents interesting choices. Here is an example: I track my accumulated vacation hours in an asset account. The units are \u201c VACHR \u201d. I associate with this commodity a price that is roughly equivalent to my net hourly salary. This gives me a rough idea how much vacation time money is accumulated on the books, e.g. if I quit my job, how much I\u2019d get paid. Do I want to them included in my total net worth? Should the value from those hours be reflected in the value of my exported portfolio? I think that largely depends on whether I plan to use up those vacations before I leave this job or not, whether I want to have this accumulated value show up on my balance sheet. Comparing with Net Worth \uf0c1 The end result is that the sum total of all your exported positions plus the cash position should approximate the value of all your assets, and the total value calculated by the Google Finance website should be very close to the one reported by this report: bean-report file.beancount networth As a point of comparison, the value of my own portfolio is usually close to within a few hundred US dollars. Details of the OFX Export \uf0c1 Import Failures \uf0c1 Exporting a portfolio with symbols that Google Finance does not recognize fatally trips up Google\u2019s import feature. Google Finance then proceeds to fail to recognize your entire file. I recommend that you use explicit exchange:symbol names on all commodities that get exported in order to avoid this problem, as is described further in this document. Google Finance can also be a little bit finicky about the format of the particular OFX file you give it to import. The export_portfolio command attempts to avoid OFX features that would break it but it\u2019s fragile, and it\u2019s possible that the particulars of your portfolio\u2019s contents triggers output that fails to import. If this is the case, at step (4) above, instead of a list of stock symbols you would see a long list of positions that look like XML tags (this is how failure manifests itself). If that is the case, send email to the mailing-list (best if you can isolate the positions that trigger breakage and have the capability to diff files and do some troubleshooting). Mutual Funds vs. Stocks \uf0c1 The OFX format distinguishes between stocks and mutual funds. In practice, the Google Finance importer does not appear to distinguish between these two (at least it appears to behave the same way), so this is likely an irrelevant implementation detail. Nevertheless, the export code is able to honor the OFX convention of distinguishing between \u201cBUYMF\u201d vs. \u201cBUYSTOCK\u201d XML elements. To this effect, the export code attempts to classify which commodities represent mutual funds by inspecting whether the ticker associated with the commodity begins with the letters \u201cMUTF\u201d and is followed by a colon. For example, \u201c MUTF:RGAGX \u201d and \u201c MUTF_CA:RBF1005 \" will both be detected as mutual funds, for example. Debugging the Export \uf0c1 In order to debug how each of your holdings gets exported, use the --debug flag, which will print a detailed account of how each holding is handled by the export script to stderr : bean-report file.beancount export_portfolio --debug 2>&1 >/dev/null | more The script should print the list of exported positions and their corresponding holdings, then the list of converted positions and their corresponding holdings (usually many cash positions are aggregated together) and finally, the list of ignored holdings. This should be enough to explain the entire contents of the exported portfolio. Purchase Dates \uf0c1 Beancount does not currently have all the lot purchase dates, so the purchase dates are exported as if purchased the day before the export. Eventually, when the purchase date is available in Beancount (pending the inventory booking changes ) the actual lot purchase date will probably be used in the export format. However, it\u2019s not yet clear that using the correct date is the right thing to do, because Google Finance might insist on inserting cash for dividends since the reported purchase date\u2026 but Beancount already takes care of inserting a special lot for cash that should already include this. We shall see when we get there. Disable Dividends \uf0c1 Under the \u201cEdit Portfolio\u201d option there is a checkbox that appears to disable the calculation of dividends offered. It would be nice to find a way to automatically disable this checkbox upon import. Automate Upload \uf0c1 It would be nice to automate the replacement of the portfolio with a Python script. Unfortunately, the Google Finance API has been deprecated. Maybe someone can write a screen-scraping routine to do this. Summary \uf0c1 Each holding\u2019s export can be controlled by how its commodity is treated, in one of the following ways: Exported to a portfolio position. This is the default, but you should specify the ticker symbol using the \u201c ticker \u201d or \u201c export \u201d metadata fields, in \u201c ExchangeCode:Symbol \u201d format. Converted to cash and exported to a money market cash-equivalent position, by setting the value of the \u201c export \u201d metadata field to the special value \u201c CASH \u201d. Ignored by specifying the \u201c export \u201d metadata field to the special value \u201c IGNORE \u201d. Provided as Money Instrument , to be used for cash-equivalent value of each holding intended to be converted to cash and included in the portfolio. These are identified by a special value \u201c( MONEY:) \u201d in the \u201c export \u201d metadata field.","title":"Exporting Your Portfolio"},{"location":"exporting_your_portfolio.html#exporting-your-portfolio","text":"Martin Blais , December 2015 (v2) http://furius.ca/beancount/doc/export","title":"Exporting Your Portfolio"},{"location":"exporting_your_portfolio.html#overview","text":"This document explains how to export your portfolio of holdings from Beancount to a Google Finance portfolio (and eventually to other portfolio tracking websites). Note: This is the second version of this document, rewritten in Dec 2015, after greatly simplifying the process of exporting portfolios and completely separating the specification of stock tickers for price downloads. This new, simplified version only uses a single metadata field name: \u201cexport\u201d. The previous document can be found here .","title":"Overview"},{"location":"exporting_your_portfolio.html#portfolio-tracking-tools","text":"There are multiple websites on the internet that allow someone to create a portfolio of investments (or upload a list of transactions to create such a portfolio) and that reports on the changes in the portfolio due to price movements, shows you unrealized capital gains, etc. One such website is the Google Finance portal. Another example is the Yahoo Finance one. These are convenient because they allow you to monitor the impact of price changes on your entire portfolio of assets, across all accounts, during the day or otherwise. However, each of these sites expects their users to use their interfaces and workflows to painfully enter each of the positions one-by-one. A great advantage of using Beancount is that you should never have to enter this type of information manually; instead, you should be able to extract it and upload it to one of these sites. You can be independent of the particular portfolio tracking service you use and should be able to switch between them without losing any data; Beancount can serve as your pristine source for your list of holdings as your needs evolve. Google Finance supports an \u201cimport\u201d feature to create portfolio data which supports the Microsoft OFX financial interchange data format. In this document, we show how we built a Beancount report that exports the portfolio of holdings to OFX for creating a Google Finance portfolio.","title":"Portfolio Tracking Tools"},{"location":"exporting_your_portfolio.html#exporting-to-google-finance","text":"","title":"Exporting to Google Finance"},{"location":"exporting_your_portfolio.html#exporting-your-holdings-to-ofx","text":"First, create an OFX file corresponding to your Beancount holdings. You can use this command to do this: bean-report file.beancount export_portfolio > portfolio.ofx See the report\u2019s own help for options: bean-report file.beancount export_portfolio --help","title":"Exporting your Holdings to OFX"},{"location":"exporting_your_portfolio.html#importing-the-ofx-file-in-google-finance","text":"Then we have to import that OFX file in a web-based portfolio. Visit http://finance.google.com and click on \u201cPortfolios\u201d on the left (or simply visit https://www.google.com/finance/portfolio , this works as of Jan 2015) If you have an existing, previously imported portfolio, click on \u201cDelete Portfolio\u201d to get rid of it. Click on \u201cImport Transactions\u201d, then \u201cChoose File\u201d and select the portfolio.ofx file you exported to, then click on \u201cPreview Import\u201d. You should see a list of imported lots, with familiar stock symbols and names, and Type \u201cBuy\u201d with realistic Shares and Price columns. If not, see the note below. Otherwise, scroll to the bottom of the page and click \u201cImport\u201d. Your portfolio should now appear. You are done. You should never bother updating this portfolio directly using the website\u2026 instead, update your Beancount ledger file, re-export to a new OFX file, delete the previous portfolio and re-import a brand new one over it. Your pristine source is always your Beancount file, ideally you should never have to be worried about corrupting or deleting the portfolio data in any external website.","title":"Importing the OFX File in Google Finance"},{"location":"exporting_your_portfolio.html#controlling-exported-commodities","text":"","title":"Controlling Exported Commodities"},{"location":"exporting_your_portfolio.html#declaring-your-commodities","text":"Generally, we recommend that you explicitly declare each of the commodities used in your input file. It is a neat place to attach information about those commodities, metadata that you should be able to use later on from bean-query or in scripts that you make. For example, you could declare a human-readable description of the commodity, and some other attributes, like this: 2001-09-06 commodity XIN name: \"iShares MSCI EAFE Index ETF (CAD-Hedged)\" class: \"Stock\" type: \"ETF\" ... Beancount will work with or without these declarations (it automatically generates Commodity directives if you haven\u2019t provided them). If you like to be strict and have a bit of discipline, you can require that each commodity be declared by using a plugin that will issue an error when an undeclared commodity appears: plugin \"beancount.plugins.check_commodity\" You can use any date for that Commodity directive. I recommend using the date of the commodity\u2019s inception, or perhaps when it was first introduced by the issuing country, if it is a currency. You can find a suitable date on Wikipedia or on the issuer\u2019s websites. Google Finance may have the date itself.","title":"Declaring Your Commodities"},{"location":"exporting_your_portfolio.html#what-happens-by-default","text":"By default, all holdings are exported as positions with a ticker symbol named the same as the Beancount commodity that you used to define them. If you have a holding of \u201cAAPL\u201d units, it will create an export entry for \u201cAAPL\u201d. The export code attempts to export all holdings by default. However, in any but the simplest unambiguous cases, this is probably not good enough to produce a working Google Finance portfolio. The name for each commodity that you use in your Beancount input file may or may not correspond to a financial instrument in the Google Finance database; due to the very large number of symbols supported in its database, just specifying the ticker symbol is often ambiguous. Google Finance attempts to resolve an ambiguous symbol string to the most likely instrument in its database. It is possible that it resolves it to a different financial instrument from the one you intended. So even if you use the same basic symbol that is used by the exchange, you often still need to disambiguate the symbol by specifying which exchange or symbology it lives in. Google provides a list of these symbol spaces . Here is a real-life example. The symbol for the \u201c CAD-Hedged MSCI EAFE Index \u201d ETF product issued by iShares/Blackrock is \u201c XIN \u201d on the Toronto Stock Exchange ( TSE ). If you just looked up \u201cXIN\u201d on Google Finance , it would choose to resolve it by default to the more likely \u201c NYSE:XIN \u201d symbol ( Xinyuan Real Estate Co. on the New York Stock Exchange ). So you need to disambiguate it by specifying that the desired ETF ticker for this instrument is \u201c TSE:XIN \u201d.","title":"What Happens by Default"},{"location":"exporting_your_portfolio.html#explicitly-specifying-exported-symbols","text":"You can specify which exchange-specific symbol is used to export a commodity by attaching an \u201c export \u201d metadata field to each of your Commodity directives, like this: 2001-09-06 commodity XIN ... export: \"TSE:XIN\" The \u201c export \u201d field is used to map your commodity name to the corresponding instrument in the Google Finance system. If a holding in that commodity needs to be exported, this code is used instead of the Beancount currency name. The symbology used by Google Finance appears to follow the following syntax:","title":"Explicitly Specifying Exported Symbols"},{"location":"exporting_your_portfolio.html#exchangesymbol","text":"where Exchange is a code either for the exchange where the stock trades, or for another source of financial data, e.g. \u201c MUTF \u201d for \u201cmutual funds in the US\u201d, and more . Symbol is a name that is unique within that exchange. I recommend searching for each of your financial instruments in Google Finance, confirming that the instrument corresponds to your instrument (by inspecting the full name, description and price), and inserting the corresponding code like this.","title":"Exchange:Symbol"},{"location":"exporting_your_portfolio.html#exporting-to-a-cash-equivalent","text":"To account for positions that aren\u2019t supported in Google Finance, the export report can convert a holding to its cash-equivalent value. This is also useful for cash positions (e.g., cash sitting idle in a savings or checking account). For example, I hold units of an insurance policy investment vehicle (this is common in Canada, for example, with London Life). This is a financial instrument, but each particular policy issuance has its own associated value\u2014there is no public source of data for each of those products, it\u2019s rather opaque, I can obtain its value with my annual statement, but definitely not in Google Finance. But I\u2019d still like for the asset\u2019s value to be reflected in my portfolio. The way you tell the export code to make this conversion is to specify a special value of \u201cCASH\u201d for the \u201cexport\u201d field, like this: 1878-01-01 commodity LDNLIFE export: \"CASH\" This would convert holdings in LDNLIFE commodities to their corresponding quoted value before exporting, using the price nearest to the date of exporting. Note that attempting to convert to cash a commodity that does not have a corresponding cost or price available for us to determine its value will generate an error. A price must be present to make the conversion. Simple currencies should also be marked as cash in order to be exported: 1999-01-01 commodity EUR name: \"European Union Euro currency\" export: \"CASH\" Finally, all converted holdings are agglomerated into a single cash position. There is no point in exporting these cash entries to separate OFX entries because the Google Finance code will agglomerate them to a single one anyhow.","title":"Exporting to a Cash Equivalent"},{"location":"exporting_your_portfolio.html#declaring-money-instruments","text":"There is a small hiccup in this cash conversion story: the Google Finance importer does not appear to correctly grok an OFX position in \u201ccash\u201d amounts in the importer; I think this is probably just a bug in Google Finance\u2019s import code (or perhaps I haven\u2019t found the correct OFX field values to make this work). Instead, in order to insert a cash position the exporter uses a cash-equivalent commodity which always prices at 1 unit of the currency, e.g. $1.00 for US dollars. For example, for US dollars I I use VMMXX which is a Vanguard Prime Money Market Fund, and for Canadian dollars I use IGI806 . A good type of commodity for this is some sort of Money Market fund. It doesn\u2019t matter so much which one you use, as long as it prices very close to 1. Find one. If you want to include cash commodities, you need to find such a commodity for each of the cash currencies you have on your books and tell Beancount about them. Typically that will be only one or two currencies. You declare them by append the special value \u201c MONEY \u201d for the \u201c export \u201d field, specifying which currency this commodity represents, like this: 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" 1900-01-01 commodity IGI806 export: \"MUTF_CA:IGI806 (MONEY:CAD)\"","title":"Declaring Money Instruments"},{"location":"exporting_your_portfolio.html#ignoring-commodities","text":"Finally, some commodities held in a ledger should be ignored. This is the case for the imaginary commodities used in mirror accounting, for example, to track unvested shares of an employment stock plan, or commodities used to track amounts contributed to a retirement account, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" You tell the export code to ignore a commodity specifying the special value \u201c IGNORE \u201d for the \u201c export \u201d field, like this: 1996-01-01 commodity RSPCAD name: \"Canada Registered Savings Plan Contributions\" export: \"IGNORE\" All holdings in units of RSPCAD will thus not be exported. The question of whether some commodities should be exported or not sometimes presents interesting choices. Here is an example: I track my accumulated vacation hours in an asset account. The units are \u201c VACHR \u201d. I associate with this commodity a price that is roughly equivalent to my net hourly salary. This gives me a rough idea how much vacation time money is accumulated on the books, e.g. if I quit my job, how much I\u2019d get paid. Do I want to them included in my total net worth? Should the value from those hours be reflected in the value of my exported portfolio? I think that largely depends on whether I plan to use up those vacations before I leave this job or not, whether I want to have this accumulated value show up on my balance sheet.","title":"Ignoring Commodities"},{"location":"exporting_your_portfolio.html#comparing-with-net-worth","text":"The end result is that the sum total of all your exported positions plus the cash position should approximate the value of all your assets, and the total value calculated by the Google Finance website should be very close to the one reported by this report: bean-report file.beancount networth As a point of comparison, the value of my own portfolio is usually close to within a few hundred US dollars.","title":"Comparing with Net Worth"},{"location":"exporting_your_portfolio.html#details-of-the-ofx-export","text":"","title":"Details of the OFX Export"},{"location":"exporting_your_portfolio.html#import-failures","text":"Exporting a portfolio with symbols that Google Finance does not recognize fatally trips up Google\u2019s import feature. Google Finance then proceeds to fail to recognize your entire file. I recommend that you use explicit exchange:symbol names on all commodities that get exported in order to avoid this problem, as is described further in this document. Google Finance can also be a little bit finicky about the format of the particular OFX file you give it to import. The export_portfolio command attempts to avoid OFX features that would break it but it\u2019s fragile, and it\u2019s possible that the particulars of your portfolio\u2019s contents triggers output that fails to import. If this is the case, at step (4) above, instead of a list of stock symbols you would see a long list of positions that look like XML tags (this is how failure manifests itself). If that is the case, send email to the mailing-list (best if you can isolate the positions that trigger breakage and have the capability to diff files and do some troubleshooting).","title":"Import Failures"},{"location":"exporting_your_portfolio.html#mutual-funds-vs-stocks","text":"The OFX format distinguishes between stocks and mutual funds. In practice, the Google Finance importer does not appear to distinguish between these two (at least it appears to behave the same way), so this is likely an irrelevant implementation detail. Nevertheless, the export code is able to honor the OFX convention of distinguishing between \u201cBUYMF\u201d vs. \u201cBUYSTOCK\u201d XML elements. To this effect, the export code attempts to classify which commodities represent mutual funds by inspecting whether the ticker associated with the commodity begins with the letters \u201cMUTF\u201d and is followed by a colon. For example, \u201c MUTF:RGAGX \u201d and \u201c MUTF_CA:RBF1005 \" will both be detected as mutual funds, for example.","title":"Mutual Funds vs. Stocks"},{"location":"exporting_your_portfolio.html#debugging-the-export","text":"In order to debug how each of your holdings gets exported, use the --debug flag, which will print a detailed account of how each holding is handled by the export script to stderr : bean-report file.beancount export_portfolio --debug 2>&1 >/dev/null | more The script should print the list of exported positions and their corresponding holdings, then the list of converted positions and their corresponding holdings (usually many cash positions are aggregated together) and finally, the list of ignored holdings. This should be enough to explain the entire contents of the exported portfolio.","title":"Debugging the Export"},{"location":"exporting_your_portfolio.html#purchase-dates","text":"Beancount does not currently have all the lot purchase dates, so the purchase dates are exported as if purchased the day before the export. Eventually, when the purchase date is available in Beancount (pending the inventory booking changes ) the actual lot purchase date will probably be used in the export format. However, it\u2019s not yet clear that using the correct date is the right thing to do, because Google Finance might insist on inserting cash for dividends since the reported purchase date\u2026 but Beancount already takes care of inserting a special lot for cash that should already include this. We shall see when we get there.","title":"Purchase Dates"},{"location":"exporting_your_portfolio.html#disable-dividends","text":"Under the \u201cEdit Portfolio\u201d option there is a checkbox that appears to disable the calculation of dividends offered. It would be nice to find a way to automatically disable this checkbox upon import.","title":"Disable Dividends"},{"location":"exporting_your_portfolio.html#automate-upload","text":"It would be nice to automate the replacement of the portfolio with a Python script. Unfortunately, the Google Finance API has been deprecated. Maybe someone can write a screen-scraping routine to do this.","title":"Automate Upload"},{"location":"exporting_your_portfolio.html#summary","text":"Each holding\u2019s export can be controlled by how its commodity is treated, in one of the following ways: Exported to a portfolio position. This is the default, but you should specify the ticker symbol using the \u201c ticker \u201d or \u201c export \u201d metadata fields, in \u201c ExchangeCode:Symbol \u201d format. Converted to cash and exported to a money market cash-equivalent position, by setting the value of the \u201c export \u201d metadata field to the special value \u201c CASH \u201d. Ignored by specifying the \u201c export \u201d metadata field to the special value \u201c IGNORE \u201d. Provided as Money Instrument , to be used for cash-equivalent value of each holding intended to be converted to cash and included in the portfolio. These are identified by a special value \u201c( MONEY:) \u201d in the \u201c export \u201d metadata field.","title":"Summary"},{"location":"external_contributions.html","text":"External Contributions to Beancount \uf0c1 Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/contrib Links to codes written by other people that build on top of or that are related to Beancount and/or Ledgerhub. Indexes \uf0c1 This document contains only packages that were discussed or have had an announcement sent to the mailing-list. You will be able to find other packages on public indices: PyPI: You can find a lot of other Beancount-related projects at PyPI . GitHub: A search for \"beancount\" as of September 2020 brings up 318 projects. Books and Articles \uf0c1 Managing Personal Finances using Python (Siddhant Goel): a 2020 book on plain-text accounting, and Beancount. The Five Minute Ledger Update (RedStreet) A series of articles showing how to automate downloading data from institutions (banks, credit cards, brokerages, etc.) so that ledger updates can be done in under five minutes. Mailing list thread . Tax Loss Harvesting with Beancount (RedStreet): An article about TLH from a US perspective, includes requirements, wash sale subtleties and safe to sell/buy dates, and comparisons to robo-advisors. (Related: fava_investor TLH module . for fava and plain-beancount command line version). Scaled Estimates of Mutual Fund NAVs (RedStreet) : Problem: Mutual fund NAVs (at least in the US) are updated exactly once per day, at the end of the day. When needing to make financial decisions when the trading window is still open (eg: when tax loss harvesting), and the end-of-day NAV is not yet available, it is sometimes useful to make a trivial estimate of that NAV, especially on days when there are huge changes in the market. A Shortcut to Scrape Trade History from Fidelity (David Avraamides) I wrote up a description of how I use a Shortcut to scrape trade history from Fidelity\u2019s website, run it through a Python script to convert to Beancount\u2019s ledger format, and then save it in the clipboard so I can paste it into a ledger file. Lazy Beancount (Vasily M) / Evernight/lazy-beancount : Opinionated guide on how to start (and continue) tracking personal finances using the open-source Beancount accounting system. It comes together with some code. The primary goal of this guide is to provide you a way to start managing your own finances using plain-text accounting gradually and incrementally. Also with various useful tools already included and set up. The Zen of Balance \u2014 https://academy.beanhub.io/ (Fang-Pen Lin) : An explanation of double-entry accounting using visualizations and diagrams. Multiperiod hledger-Style Reports in beancount: Pivoting a Table | Altynbek Isabekov : An article showing how to produce pivot table summaries of account balances, e.g. by year, with associated code (github) . Plugins \uf0c1 split_transactions : Johann Kl\u00e4hn wrote a plugin that can split a single transaction into many against a limbo account, as would be done for depreciation. zerosum : Red S wrote a plugin to match up transactions that when taken together should sum up to zero and move them to a separate account. effective_dates : Red S wrote a plugin to book different legs of a transaction to different dates beancount-plugins : Dave Stephens created a repository to share various of his plugins related to depreciation. beancount-plugins-zack : Stefano Zacchiroli created this repository to share his plugins. Contains sorting of directives and more. b eancount-oneliner : Akuukis created a plugin to write an entry in one line ( PyPi ). beancount-interpolate : Akuukis created plugins for Beancount to interpolate transactions (recur, split, depr, spread) ( PyPi ). metadata-spray : Add metadata across entries by regex expression rather than having explicit entries (by Vivek Gani). Akuukis/beancount_share : A beancount plugin to share expenses among multiple partners within one ledger. This plugin is very powerful and most probably can deal with all of your sharing needs. w1ndy/beancount_balexpr (Di Weng): A plugin that provides \"balance expressions\" to be run against the Beancount entries, as a Custom directive. See this thread . autobean.narration (Archimedes Smith): Allows to annotate each posting in a concise way by populating per-posting narration metadata from inline comments. autobean.sorted : Checks that transactions are in non-descending order in each file. Helps identifying misplaced or misdated directives, by warning on those directives not following a non-descending order by date in the file. hoostus/beancount-asset-transfer-plugin : A plugin to automatically generate in-kind transfers between two beancount accounts, while preserving the cost basis and acquisition date. PhracturedBlue/fava-portfolio-summary (Phractured Blue): Fava Plugin to show portfolio summaries with rate of return. rename_accounts : Plugin from Red S to rename accounts. E.g.: rename \u201cExpenses:Taxes\u201d to \u201cIncome:Taxes\u201d is helpful for expense analysis. More here . Long_short capital gains classifier : Plugin from Red S to classify capital gains into long and short based on duration the assets were held, and into gains and losses based on value. Autoclose_tree : Automatically closes all of an account's descendants when an account is closed. Evernight/beancount-valuation (Vasily M) : A Beancount plugin to track total value of the opaque fund. You can use it instead of the balance operation to assert total value of the account. If the value of the account is currently different, it will instead alter price of the underlying synthetical commodity created by the plugin used for technical purposes. Tools \uf0c1 alfred-beancount (Yue Wu): An add-on to the \u201cAlfred\u201d macOS tool to quickly enter transactions in one\u2019s Beancount file. Supports full account names and payees match. bean-add (Simon Volpert): A Beancount transaction entry assistant. hoostus/fincen_114 (Justus Pendleton): An FBAR / FinCEN 114 report generator. ghislainbourgeois/beancount_portfolio_allocation ( Ghislain Bourgeois ): A quick way to figure out the asset allocations in different portfolios. hoostus/portfolio-returns (Justus Pendleton): portfolio returns calculator costflow/syntax (Leplay Li): A product that allows users to keep plain text accounting from their favorite messaging apps. A syntax for converting one-line message to beancount/*ledger format. process control chart (Justus Pendleton): Spending relative to portfolio size. Thread. Pinto (Sean Leavey): Supercharged command line interface for Beancount. Supports automatic insertions of transactions in ledger file. PhracturedBlue/fava-encrypt : A docker-base solution for keeping Fava online while keeping beancount data encrypted at rest. See this thread for context. kubauk/beancount-import-gmail : beancount-import-gmail uses the gmail API and OAuth to log into your mailbox and download order details which are then used to augment your transactions for easier classification. sulemankm/budget_report : A very simple command-line budget tracking tool for beancount ledger files. dyumnin/dyu_accounting : Accounting setup to automate generation of various financial statements for Compliance with Indian Govt. Gains Minimizer (RedStreet): Automatically determine lots to sell to minimize capital gains taxes. Live example. beanahead (Marcus Read): Adds the ability to include future transactions (automatically generates regular transactions, provides for ad hoc expected transactions, expected transactions are reconciled against imported transactions; all functionality accessible via cli). autobean-format (Archimedes Smith): Yet another formatter for beancount,, powered by earlier project autobean-refactor, a library for parsing and programmatically manipulating beancount files. based on a proper parser, allowing it to format every corner of your ledger, including arithmetic expressions. akirak/flymake-bean-check (Akira Komamura): flymake support for Emacs. bean-download (Red Street): A downloader that ships with beancount-reds-importers that you can configure to run arbitrary commands to download your account statements. It now has a new feature: the needs-update subcommand. gerdemb/beanpost (Ben Gerdemann): Beanpost consists of a PostgreSQL schema and import/export commands that let you transfer data between a beancount file and a PostgreSQL database. Much of Beancount's functionality is implemented using custom PostgreSQL functions, allowing for complex queries and data manipulation. This setup provides a flexible backend that can integrate with other tools like web apps or reporting systems LaunchPlatform/beanhub-cli (Fang-Pen Lin): Command line tools for BeanHub or Beancount users. zacchiro/beangrep : Beangrep is a grep-like filter for the Beancount plain text accounting system. Alternative Parsers \uf0c1 Bison \uf0c1 The Beancount v2 parser uses GNU flex + GNU bison (for maximum portability). The Beancount v3 parser uses RE/flex + GNU bison (for Unicode and C++). Using Antlr \uf0c1 jord1e/jbeancount (Jordie Biemold) / using Antlr: An alternative parser for Beancount input syntax in Java (using the Antlr4 parser generator). This provides access to parsed Beancount data - without the effect of plugins - from JVM languages. See this post for details. Using Tree-sitter \uf0c1 polarmutex/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the Beancount syntax. https://github.com/dnicolodi/tree-sitter-beancount (Daniele Nicolodi): Another tree-sitter based parser for the Beancount syntax. In Rust \uf0c1 jcornaz/beancount-parser (Jonathan Cornaz): A beancount file parser library for Rust. Uses nom. beancount_parser_lima (Simon Guest): A zero-copy parser for Beancount in Rust. It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive). Uses Logos , Chumsky , and Ariadne . Emacs Lisp \uf0c1 trs-80/beancount-txn-elisp/ : beancount-txn-elisp: A library to read/parse and write/insert individual Beancount transactions, implemented in Emacs Lisp. Importers \uf0c1 reds importers : Simple importers and tools for several US based institutions, and various file types. Emphasizes ease of writing your own importers by providing well maintained common libraries for banks, credit cards, and investment houses, and for various file types, which minimizes the institution specific code you need to write. This is a reference implementation of the principles expressed in The Five Minute Ledger Update . Contributions welcome. By RedStreet plaid2text : An importer from Plaid which stores the transactions to a Mongo DB and is able to render it to Beancount syntax. By Micah Duke. jbms/beancount-import : A tool for semi-automatically importing transactions from external data sources, with support for merging and reconciling imported transactions with each other and with existing transactions in the beancount journal. The UI is web based. ( Announcement , link to previous version ). By Jeremy Maitin-Shepard. awesome-beancount : A collection of importers for Chinese banks + tips and tricks. By Zhuoyun Wei . beansoup : Filippo Tampieri is sharing some of his Beancount importers and auto-completer in this project. montaropdf/beancount-importers : An importer to extract overtime and vacation from a timesheet format for invoicing customers. siddhantgoel/beancount-dkb (Siddhant Goel): importer for DKB CSV files. prabusw/beancount-importer-zerodha : Importer for the Indian broker Zerodha. swapi/beancount-utils : Another importer for Zerodha. Dr-Nuke/drnuke-bean (Dr Nuke): An importer for IBKR, based on the flex query (API-like) and one for Swiss PostFinance. Beanborg (Luciano Fiandesio): Beanborg automatically imports financial transactions from external CSV files into the Beancount bookkeeping system. szabootibor/beancount-degiro ( PyPI ): Importer for the trading accounts of the Dutch broker Degiro. siddhantgoel/beancount-ing-diba ( PyPI ): ING account importer (NL). PaulsTek/csv2bean : Asimple application to preprocess csv files using google sheets in Go. ericaltendorf/magicbeans (Eric Altendorf): Beancount importers for crypto data. Detailed lot tracking and capital gains/losses reporting for crypto assets. \" I wrote it because I was not satisfied with the accuracy or transparency of existing commercial services for crypto tax reporting.\" OSadovy/uabean (Oleksii Sadovyi): A set of Beancount importers and scripts for popular Ukrainian banks and more. fdavies93/seneca (Frank Davies): Importer for Wise. Multi-currency transfers. LaunchPlatform/beanhub-import : New beancount importer with a UI. rlan/beancount-multitool (Rick Lan): Beancount Multitool is a command-line-interface (CLI) tool that converts financial data from financial institutions to Beancount files (supported: JA Bank \uff2a\uff21\u30cd\u30c3\u30c8\u30d0\u30f3\u30af, Rakuten Card \u697d\u5929\u30ab\u30fc\u30c9, Rakuten Bank \u697d\u5929\u9280\u884c, SBI Shinsei Bank \u65b0\u751f\u9280\u884c). Associated post: https://www.linkedin.com/feed/update/urn:li:activity:7198125470662500352/ LaunchPlatform/beanhub-import (Fang-Pen Lin): Beanhub-import is a simple, declarative, smart, and easy-to-use library for importing extracted transactions from beanhub-extract. It generates Beancount transactions based on predefined rules. Converters \uf0c1 plaid2text : Python Scripts to export Plaid transactions and transform them into Ledger or Beancount syntax formatted files. gnucash-to-beancount : A script from Henrique Bastos to convert a GNUcash SQLite database into an equivalent Beancount input file. debanjum/gnucash-to-beancount : A fork of the above. andrewStein/gnucash-to-beancount : A further fork from the above two, which fixes a lot of issues (see this thread ). hoostus/beancount-ynab : A converter from YNAB to Beancount. hoostus/beancount-ynab5 : Same convert for YNAB from the same author, but for the more recent version 5. ledger2beancount : A script to convert ledger files to beancount. It was developed by Stefano Zacchiroli and Martin Michlmayr. smart_importer : A smart importer for beancount and fava, with intelligent suggestions for account names. By Johannes Harms. beancount-export-patreon.js : JavaScript that will export your Patreon transactions so you can see details of exactly who you've been giving money to. By kanepyork@gmail. alensiljak/pta-converters (Alen \u0160iljak): GnuCash -> Beancount converter (2019). grostim/Beancount-myTools (Timothee Gros): Personal importer tools of the author for French banks. Downloaders \uf0c1 bean-download (RedStreet): bean-download is a tool to conveniently download your transactions from supporting institutions. You configure it with a list of your institutions and arbitrary commands to download them, typically via ofxget . It downloads all of them in parallel, names them appropriately and puts them in the directory of your choice, from which you can then import. The tool is installed as a part of beancount-reds-importers . See accompanying article . ofx-summarize (RedStreet): When building importers, it helps to be able to peek into a .ofx or a .qfx file that you are trying to import. The ofx-summarize command does just that. It ships with beancount-reds-importers , and should be available by simply invoking the command. Running the command on a file shows you a few transactions in the file. What is very useful is to be able to explore your .ofx file via the python debugger or interpreter. Price Sources \uf0c1 hoostus/beancount-price-sources : A Morningstar price fetcher which aggregates multiple exchanges, including non-US ones. andyjscott/beancount-financequote : Finance::Quote support for bean-price. aamerabbas/beancount-coinmarketcap : Price fetcher for coinmarketcap ( see post ). grostim/Beancount-myTools/.../iexcloud.py : Price fetcher for iexcloud by Timothee Gros. xuhcc/beancount-cryptoassets (Kirill Goncharov): Price sources for cryptocurrencies. xuhcc/beancount-ethereum-importer (Kirill Goncharov): Ethereum transaction importer for Beancount. Includes a script that downloads transactions from Etherscan and an importer for downloaded transactions. xuhcc/beancount-exchangerates (Kirill Goncharov): Price source for http://exchangeratesapi.io . tarioch/beancounttools (Patrick Ruckstuhl): Price sources and importers. https://gitlab.com/chrisberkhout/pricehist (Chris Berkhout): A command-line tool that can fetch daily historical prices from multiple sources and output them in several formats. Supports some sources for CoinDesk, European Central Bank, Alpha Vantage, CoinMarketCap. The user can request a specific price type such as high, low, open, close or adjusted close. It can also be used through bean-price. Development \uf0c1 Py3k type annotations : Yuchen Ying is implementing python3 type annotations for Beancount. bryall/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the beancount syntax. jmgilman/beancount-stubs : Typing .pyi stubs for some of the Beancount source. Documentation \uf0c1 Beancount Documentation ( Kirill Goncharov ): Official conversion of the Beancount documentation from Google Docs source to Markdown and HTML. This includes most of the Google Docs documents and is maintained in a Beancount org repo here by Kirill Goncharov. Beancount Source Code Documentation ( Dominik Aumayr ): A Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . SQL queries for Beancount (Dominik Aumayr): Example SQL queries. Beancount \u2014\u2014 \u547d\u4ee4\u884c\u590d\u5f0f\u7c3f\u8bb0 (Zhuoyun Wei): A tutorial (blog post) in Chinese on how to use Beancount. Managing my personal finances with Beancount (Alex Johnstone) Counting beans\u2014and more\u2014with Beancount (LWN) Interfaces / Web \uf0c1 fava: A web interface for Beancount (Dominik Aumayr, Jakob Schnitzer): Beancount comes with its own simple web front-end (\u201cbean-web\u201d) intended merely as a thin shell to invoke and display HTML versions of its reports. \u201cFava\u201d is an alternative web application front-end with more & different features, intended initially as a playground and proof-of-concept to explore a newer, better design for presenting the contents of a Beancount file. Fava Classy Portfolio (Vivek Gani): Classy Portfolio is an Extension for Fava, a web interface for the Beancount plaintext accounting software. The extension displays a list of different portfolios (e.g. 'taxable' vs. 'retirement'), with breakdowns using 'asset-class' and 'asset-subclass' metadata labels on commodities. Fava Investor project (RedStreet): Fava_investor aims to be a comprehensive set of reports, analyses, and tools for investments, for Beancount and Fava. It is a collection of modules, with each module offering a Fava plugin, a Beancount library, and a Beancount based CLI (command line interface). Current modules include: Visual, tree structured asset allocation by class, asset allocation by account, tax loss harvester, cash drag analysis. Fava Miler (RedStreet): Airline miles and rewards points: expiration and value reporting. Fava Envelope (Brian Ryall): A beancount fava extension to add an envelope budgeting capability to fava and beancount. It is developed as a Fava plugin and CLI. scauligi/refried (Sunjay Cauligi): An envelope budgeting plugin for Fava, inspired by YNAB: all expense accounts become individual budgeting categories, budgeting is carried out using transactions to these accounts, and the plugin automaticallyapplies a tag to all rebudget transactions so they can easily be filtered out. Provides budget and account views like YNAB. BeanHub.io : A web front-end for Beancount content. \" Since I started using Beancount, I have dreamed of making it fully automatic. For a few years now, I've been building tools for that goal. Connecting to the bank and fetching data directly from there is one of the goals I want to achieve. I built this feature and have been testing it for a while for my accounting book. Now my Beancount books are 80% fully automatic. I can open my repository, and the transactions from the bank will automatically appear as a new commit like this without me lifting a finger. The whole import system is based on our open-source beanhub-import and beanhub-extract. The only proprietary part in the import flow is the Plaid integration. So, suppose you don't trust me and still want to import transactions automatically. As long as you connect to Plaid and write CSV files based on the transactions you fetched from Plaid, you should be able to have the same automatic transaction importing system without using the BeanHub service. \" Blog posts: https://beanhub.io/blog/2024/06/24/introduction-of-beanhub-connect/ https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/ https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/ jmgilman/bdantic : A package for extending beancount with pydantic . With this package you can convert your ledger to JSON, and more. autobean/refactor (Archimedes Smith): Tooling to programmatically edit one's ledger, including formatting, sorting, refactoring, rearranging accounts, optimizing via plugins, migration from v2, inserting transactions in a ledger on import, and more. seltzered/beancolage (Vivek Gani): An Eclipse Theia (vendor-agnostic vscode) app that tries to bundle existing beancount-based packages such as vscode-beancount and Fava. aaronstacy.com/personal-finances-dashboard : HTML + D3.js visualization dashboard for Beancount data. Mobile/Phone Data Entry \uf0c1 Beancount Mobile App (Kirill Goncharov): A mobile data entry app for Beancount. (Currently only Android is supported.) Repo: https://github.com/xuhcc/beancount-mobile ( Announcement ). http://costflow.io : Plain Text Accounting in WeChat. \" Send a message to our bot in Telegram, Facebook Messenger, Whatsapp, LINE, WeChat, etc. Costflow will transform your message into Beancount / Ledger / hledger format transaction magically. Append the transaction to the file in your Dropbox / Google Drive. With the help of their apps, the file will be synced to your computer. \"","title":"External Contributions"},{"location":"external_contributions.html#external-contributions-to-beancount","text":"Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/contrib Links to codes written by other people that build on top of or that are related to Beancount and/or Ledgerhub.","title":"External Contributions to Beancount"},{"location":"external_contributions.html#indexes","text":"This document contains only packages that were discussed or have had an announcement sent to the mailing-list. You will be able to find other packages on public indices: PyPI: You can find a lot of other Beancount-related projects at PyPI . GitHub: A search for \"beancount\" as of September 2020 brings up 318 projects.","title":"Indexes"},{"location":"external_contributions.html#books-and-articles","text":"Managing Personal Finances using Python (Siddhant Goel): a 2020 book on plain-text accounting, and Beancount. The Five Minute Ledger Update (RedStreet) A series of articles showing how to automate downloading data from institutions (banks, credit cards, brokerages, etc.) so that ledger updates can be done in under five minutes. Mailing list thread . Tax Loss Harvesting with Beancount (RedStreet): An article about TLH from a US perspective, includes requirements, wash sale subtleties and safe to sell/buy dates, and comparisons to robo-advisors. (Related: fava_investor TLH module . for fava and plain-beancount command line version). Scaled Estimates of Mutual Fund NAVs (RedStreet) : Problem: Mutual fund NAVs (at least in the US) are updated exactly once per day, at the end of the day. When needing to make financial decisions when the trading window is still open (eg: when tax loss harvesting), and the end-of-day NAV is not yet available, it is sometimes useful to make a trivial estimate of that NAV, especially on days when there are huge changes in the market. A Shortcut to Scrape Trade History from Fidelity (David Avraamides) I wrote up a description of how I use a Shortcut to scrape trade history from Fidelity\u2019s website, run it through a Python script to convert to Beancount\u2019s ledger format, and then save it in the clipboard so I can paste it into a ledger file. Lazy Beancount (Vasily M) / Evernight/lazy-beancount : Opinionated guide on how to start (and continue) tracking personal finances using the open-source Beancount accounting system. It comes together with some code. The primary goal of this guide is to provide you a way to start managing your own finances using plain-text accounting gradually and incrementally. Also with various useful tools already included and set up. The Zen of Balance \u2014 https://academy.beanhub.io/ (Fang-Pen Lin) : An explanation of double-entry accounting using visualizations and diagrams. Multiperiod hledger-Style Reports in beancount: Pivoting a Table | Altynbek Isabekov : An article showing how to produce pivot table summaries of account balances, e.g. by year, with associated code (github) .","title":"Books and Articles"},{"location":"external_contributions.html#plugins","text":"split_transactions : Johann Kl\u00e4hn wrote a plugin that can split a single transaction into many against a limbo account, as would be done for depreciation. zerosum : Red S wrote a plugin to match up transactions that when taken together should sum up to zero and move them to a separate account. effective_dates : Red S wrote a plugin to book different legs of a transaction to different dates beancount-plugins : Dave Stephens created a repository to share various of his plugins related to depreciation. beancount-plugins-zack : Stefano Zacchiroli created this repository to share his plugins. Contains sorting of directives and more. b eancount-oneliner : Akuukis created a plugin to write an entry in one line ( PyPi ). beancount-interpolate : Akuukis created plugins for Beancount to interpolate transactions (recur, split, depr, spread) ( PyPi ). metadata-spray : Add metadata across entries by regex expression rather than having explicit entries (by Vivek Gani). Akuukis/beancount_share : A beancount plugin to share expenses among multiple partners within one ledger. This plugin is very powerful and most probably can deal with all of your sharing needs. w1ndy/beancount_balexpr (Di Weng): A plugin that provides \"balance expressions\" to be run against the Beancount entries, as a Custom directive. See this thread . autobean.narration (Archimedes Smith): Allows to annotate each posting in a concise way by populating per-posting narration metadata from inline comments. autobean.sorted : Checks that transactions are in non-descending order in each file. Helps identifying misplaced or misdated directives, by warning on those directives not following a non-descending order by date in the file. hoostus/beancount-asset-transfer-plugin : A plugin to automatically generate in-kind transfers between two beancount accounts, while preserving the cost basis and acquisition date. PhracturedBlue/fava-portfolio-summary (Phractured Blue): Fava Plugin to show portfolio summaries with rate of return. rename_accounts : Plugin from Red S to rename accounts. E.g.: rename \u201cExpenses:Taxes\u201d to \u201cIncome:Taxes\u201d is helpful for expense analysis. More here . Long_short capital gains classifier : Plugin from Red S to classify capital gains into long and short based on duration the assets were held, and into gains and losses based on value. Autoclose_tree : Automatically closes all of an account's descendants when an account is closed. Evernight/beancount-valuation (Vasily M) : A Beancount plugin to track total value of the opaque fund. You can use it instead of the balance operation to assert total value of the account. If the value of the account is currently different, it will instead alter price of the underlying synthetical commodity created by the plugin used for technical purposes.","title":"Plugins"},{"location":"external_contributions.html#tools","text":"alfred-beancount (Yue Wu): An add-on to the \u201cAlfred\u201d macOS tool to quickly enter transactions in one\u2019s Beancount file. Supports full account names and payees match. bean-add (Simon Volpert): A Beancount transaction entry assistant. hoostus/fincen_114 (Justus Pendleton): An FBAR / FinCEN 114 report generator. ghislainbourgeois/beancount_portfolio_allocation ( Ghislain Bourgeois ): A quick way to figure out the asset allocations in different portfolios. hoostus/portfolio-returns (Justus Pendleton): portfolio returns calculator costflow/syntax (Leplay Li): A product that allows users to keep plain text accounting from their favorite messaging apps. A syntax for converting one-line message to beancount/*ledger format. process control chart (Justus Pendleton): Spending relative to portfolio size. Thread. Pinto (Sean Leavey): Supercharged command line interface for Beancount. Supports automatic insertions of transactions in ledger file. PhracturedBlue/fava-encrypt : A docker-base solution for keeping Fava online while keeping beancount data encrypted at rest. See this thread for context. kubauk/beancount-import-gmail : beancount-import-gmail uses the gmail API and OAuth to log into your mailbox and download order details which are then used to augment your transactions for easier classification. sulemankm/budget_report : A very simple command-line budget tracking tool for beancount ledger files. dyumnin/dyu_accounting : Accounting setup to automate generation of various financial statements for Compliance with Indian Govt. Gains Minimizer (RedStreet): Automatically determine lots to sell to minimize capital gains taxes. Live example. beanahead (Marcus Read): Adds the ability to include future transactions (automatically generates regular transactions, provides for ad hoc expected transactions, expected transactions are reconciled against imported transactions; all functionality accessible via cli). autobean-format (Archimedes Smith): Yet another formatter for beancount,, powered by earlier project autobean-refactor, a library for parsing and programmatically manipulating beancount files. based on a proper parser, allowing it to format every corner of your ledger, including arithmetic expressions. akirak/flymake-bean-check (Akira Komamura): flymake support for Emacs. bean-download (Red Street): A downloader that ships with beancount-reds-importers that you can configure to run arbitrary commands to download your account statements. It now has a new feature: the needs-update subcommand. gerdemb/beanpost (Ben Gerdemann): Beanpost consists of a PostgreSQL schema and import/export commands that let you transfer data between a beancount file and a PostgreSQL database. Much of Beancount's functionality is implemented using custom PostgreSQL functions, allowing for complex queries and data manipulation. This setup provides a flexible backend that can integrate with other tools like web apps or reporting systems LaunchPlatform/beanhub-cli (Fang-Pen Lin): Command line tools for BeanHub or Beancount users. zacchiro/beangrep : Beangrep is a grep-like filter for the Beancount plain text accounting system.","title":"Tools"},{"location":"external_contributions.html#alternative-parsers","text":"","title":"Alternative Parsers"},{"location":"external_contributions.html#bison","text":"The Beancount v2 parser uses GNU flex + GNU bison (for maximum portability). The Beancount v3 parser uses RE/flex + GNU bison (for Unicode and C++).","title":"Bison"},{"location":"external_contributions.html#using-antlr","text":"jord1e/jbeancount (Jordie Biemold) / using Antlr: An alternative parser for Beancount input syntax in Java (using the Antlr4 parser generator). This provides access to parsed Beancount data - without the effect of plugins - from JVM languages. See this post for details.","title":"Using Antlr"},{"location":"external_contributions.html#using-tree-sitter","text":"polarmutex/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the Beancount syntax. https://github.com/dnicolodi/tree-sitter-beancount (Daniele Nicolodi): Another tree-sitter based parser for the Beancount syntax.","title":"Using Tree-sitter"},{"location":"external_contributions.html#in-rust","text":"jcornaz/beancount-parser (Jonathan Cornaz): A beancount file parser library for Rust. Uses nom. beancount_parser_lima (Simon Guest): A zero-copy parser for Beancount in Rust. It is intended to be a complete implementation of the Beancount file format, except for those parts which are deprecated and other features as documented here (in a list which may not be comprehensive). Uses Logos , Chumsky , and Ariadne .","title":"In Rust"},{"location":"external_contributions.html#emacs-lisp","text":"trs-80/beancount-txn-elisp/ : beancount-txn-elisp: A library to read/parse and write/insert individual Beancount transactions, implemented in Emacs Lisp.","title":"Emacs Lisp"},{"location":"external_contributions.html#importers","text":"reds importers : Simple importers and tools for several US based institutions, and various file types. Emphasizes ease of writing your own importers by providing well maintained common libraries for banks, credit cards, and investment houses, and for various file types, which minimizes the institution specific code you need to write. This is a reference implementation of the principles expressed in The Five Minute Ledger Update . Contributions welcome. By RedStreet plaid2text : An importer from Plaid which stores the transactions to a Mongo DB and is able to render it to Beancount syntax. By Micah Duke. jbms/beancount-import : A tool for semi-automatically importing transactions from external data sources, with support for merging and reconciling imported transactions with each other and with existing transactions in the beancount journal. The UI is web based. ( Announcement , link to previous version ). By Jeremy Maitin-Shepard. awesome-beancount : A collection of importers for Chinese banks + tips and tricks. By Zhuoyun Wei . beansoup : Filippo Tampieri is sharing some of his Beancount importers and auto-completer in this project. montaropdf/beancount-importers : An importer to extract overtime and vacation from a timesheet format for invoicing customers. siddhantgoel/beancount-dkb (Siddhant Goel): importer for DKB CSV files. prabusw/beancount-importer-zerodha : Importer for the Indian broker Zerodha. swapi/beancount-utils : Another importer for Zerodha. Dr-Nuke/drnuke-bean (Dr Nuke): An importer for IBKR, based on the flex query (API-like) and one for Swiss PostFinance. Beanborg (Luciano Fiandesio): Beanborg automatically imports financial transactions from external CSV files into the Beancount bookkeeping system. szabootibor/beancount-degiro ( PyPI ): Importer for the trading accounts of the Dutch broker Degiro. siddhantgoel/beancount-ing-diba ( PyPI ): ING account importer (NL). PaulsTek/csv2bean : Asimple application to preprocess csv files using google sheets in Go. ericaltendorf/magicbeans (Eric Altendorf): Beancount importers for crypto data. Detailed lot tracking and capital gains/losses reporting for crypto assets. \" I wrote it because I was not satisfied with the accuracy or transparency of existing commercial services for crypto tax reporting.\" OSadovy/uabean (Oleksii Sadovyi): A set of Beancount importers and scripts for popular Ukrainian banks and more. fdavies93/seneca (Frank Davies): Importer for Wise. Multi-currency transfers. LaunchPlatform/beanhub-import : New beancount importer with a UI. rlan/beancount-multitool (Rick Lan): Beancount Multitool is a command-line-interface (CLI) tool that converts financial data from financial institutions to Beancount files (supported: JA Bank \uff2a\uff21\u30cd\u30c3\u30c8\u30d0\u30f3\u30af, Rakuten Card \u697d\u5929\u30ab\u30fc\u30c9, Rakuten Bank \u697d\u5929\u9280\u884c, SBI Shinsei Bank \u65b0\u751f\u9280\u884c). Associated post: https://www.linkedin.com/feed/update/urn:li:activity:7198125470662500352/ LaunchPlatform/beanhub-import (Fang-Pen Lin): Beanhub-import is a simple, declarative, smart, and easy-to-use library for importing extracted transactions from beanhub-extract. It generates Beancount transactions based on predefined rules.","title":"Importers"},{"location":"external_contributions.html#converters","text":"plaid2text : Python Scripts to export Plaid transactions and transform them into Ledger or Beancount syntax formatted files. gnucash-to-beancount : A script from Henrique Bastos to convert a GNUcash SQLite database into an equivalent Beancount input file. debanjum/gnucash-to-beancount : A fork of the above. andrewStein/gnucash-to-beancount : A further fork from the above two, which fixes a lot of issues (see this thread ). hoostus/beancount-ynab : A converter from YNAB to Beancount. hoostus/beancount-ynab5 : Same convert for YNAB from the same author, but for the more recent version 5. ledger2beancount : A script to convert ledger files to beancount. It was developed by Stefano Zacchiroli and Martin Michlmayr. smart_importer : A smart importer for beancount and fava, with intelligent suggestions for account names. By Johannes Harms. beancount-export-patreon.js : JavaScript that will export your Patreon transactions so you can see details of exactly who you've been giving money to. By kanepyork@gmail. alensiljak/pta-converters (Alen \u0160iljak): GnuCash -> Beancount converter (2019). grostim/Beancount-myTools (Timothee Gros): Personal importer tools of the author for French banks.","title":"Converters"},{"location":"external_contributions.html#downloaders","text":"bean-download (RedStreet): bean-download is a tool to conveniently download your transactions from supporting institutions. You configure it with a list of your institutions and arbitrary commands to download them, typically via ofxget . It downloads all of them in parallel, names them appropriately and puts them in the directory of your choice, from which you can then import. The tool is installed as a part of beancount-reds-importers . See accompanying article . ofx-summarize (RedStreet): When building importers, it helps to be able to peek into a .ofx or a .qfx file that you are trying to import. The ofx-summarize command does just that. It ships with beancount-reds-importers , and should be available by simply invoking the command. Running the command on a file shows you a few transactions in the file. What is very useful is to be able to explore your .ofx file via the python debugger or interpreter.","title":"Downloaders"},{"location":"external_contributions.html#price-sources","text":"hoostus/beancount-price-sources : A Morningstar price fetcher which aggregates multiple exchanges, including non-US ones. andyjscott/beancount-financequote : Finance::Quote support for bean-price. aamerabbas/beancount-coinmarketcap : Price fetcher for coinmarketcap ( see post ). grostim/Beancount-myTools/.../iexcloud.py : Price fetcher for iexcloud by Timothee Gros. xuhcc/beancount-cryptoassets (Kirill Goncharov): Price sources for cryptocurrencies. xuhcc/beancount-ethereum-importer (Kirill Goncharov): Ethereum transaction importer for Beancount. Includes a script that downloads transactions from Etherscan and an importer for downloaded transactions. xuhcc/beancount-exchangerates (Kirill Goncharov): Price source for http://exchangeratesapi.io . tarioch/beancounttools (Patrick Ruckstuhl): Price sources and importers. https://gitlab.com/chrisberkhout/pricehist (Chris Berkhout): A command-line tool that can fetch daily historical prices from multiple sources and output them in several formats. Supports some sources for CoinDesk, European Central Bank, Alpha Vantage, CoinMarketCap. The user can request a specific price type such as high, low, open, close or adjusted close. It can also be used through bean-price.","title":"Price Sources"},{"location":"external_contributions.html#development","text":"Py3k type annotations : Yuchen Ying is implementing python3 type annotations for Beancount. bryall/tree-sitter-beancount (Bryan Ryall): A tree-sitter parser for the beancount syntax. jmgilman/beancount-stubs : Typing .pyi stubs for some of the Beancount source.","title":"Development"},{"location":"external_contributions.html#documentation","text":"Beancount Documentation ( Kirill Goncharov ): Official conversion of the Beancount documentation from Google Docs source to Markdown and HTML. This includes most of the Google Docs documents and is maintained in a Beancount org repo here by Kirill Goncharov. Beancount Source Code Documentation ( Dominik Aumayr ): A Sphinx-generated source code documentation of the Beancount codebase. The code to produce this is located here . SQL queries for Beancount (Dominik Aumayr): Example SQL queries. Beancount \u2014\u2014 \u547d\u4ee4\u884c\u590d\u5f0f\u7c3f\u8bb0 (Zhuoyun Wei): A tutorial (blog post) in Chinese on how to use Beancount. Managing my personal finances with Beancount (Alex Johnstone) Counting beans\u2014and more\u2014with Beancount (LWN)","title":"Documentation"},{"location":"external_contributions.html#interfaces-web","text":"fava: A web interface for Beancount (Dominik Aumayr, Jakob Schnitzer): Beancount comes with its own simple web front-end (\u201cbean-web\u201d) intended merely as a thin shell to invoke and display HTML versions of its reports. \u201cFava\u201d is an alternative web application front-end with more & different features, intended initially as a playground and proof-of-concept to explore a newer, better design for presenting the contents of a Beancount file. Fava Classy Portfolio (Vivek Gani): Classy Portfolio is an Extension for Fava, a web interface for the Beancount plaintext accounting software. The extension displays a list of different portfolios (e.g. 'taxable' vs. 'retirement'), with breakdowns using 'asset-class' and 'asset-subclass' metadata labels on commodities. Fava Investor project (RedStreet): Fava_investor aims to be a comprehensive set of reports, analyses, and tools for investments, for Beancount and Fava. It is a collection of modules, with each module offering a Fava plugin, a Beancount library, and a Beancount based CLI (command line interface). Current modules include: Visual, tree structured asset allocation by class, asset allocation by account, tax loss harvester, cash drag analysis. Fava Miler (RedStreet): Airline miles and rewards points: expiration and value reporting. Fava Envelope (Brian Ryall): A beancount fava extension to add an envelope budgeting capability to fava and beancount. It is developed as a Fava plugin and CLI. scauligi/refried (Sunjay Cauligi): An envelope budgeting plugin for Fava, inspired by YNAB: all expense accounts become individual budgeting categories, budgeting is carried out using transactions to these accounts, and the plugin automaticallyapplies a tag to all rebudget transactions so they can easily be filtered out. Provides budget and account views like YNAB. BeanHub.io : A web front-end for Beancount content. \" Since I started using Beancount, I have dreamed of making it fully automatic. For a few years now, I've been building tools for that goal. Connecting to the bank and fetching data directly from there is one of the goals I want to achieve. I built this feature and have been testing it for a while for my accounting book. Now my Beancount books are 80% fully automatic. I can open my repository, and the transactions from the bank will automatically appear as a new commit like this without me lifting a finger. The whole import system is based on our open-source beanhub-import and beanhub-extract. The only proprietary part in the import flow is the Plaid integration. So, suppose you don't trust me and still want to import transactions automatically. As long as you connect to Plaid and write CSV files based on the transactions you fetched from Plaid, you should be able to have the same automatic transaction importing system without using the BeanHub service. \" Blog posts: https://beanhub.io/blog/2024/06/24/introduction-of-beanhub-connect/ https://beanhub.io/blog/2024/04/23/how-beanhub-works-part1-sandboxing/ https://beanhub.io/blog/2024/06/26/how-beanhub-works-part2-layer-based-git-repos/ jmgilman/bdantic : A package for extending beancount with pydantic . With this package you can convert your ledger to JSON, and more. autobean/refactor (Archimedes Smith): Tooling to programmatically edit one's ledger, including formatting, sorting, refactoring, rearranging accounts, optimizing via plugins, migration from v2, inserting transactions in a ledger on import, and more. seltzered/beancolage (Vivek Gani): An Eclipse Theia (vendor-agnostic vscode) app that tries to bundle existing beancount-based packages such as vscode-beancount and Fava. aaronstacy.com/personal-finances-dashboard : HTML + D3.js visualization dashboard for Beancount data.","title":"Interfaces / Web"},{"location":"external_contributions.html#mobilephone-data-entry","text":"Beancount Mobile App (Kirill Goncharov): A mobile data entry app for Beancount. (Currently only Android is supported.) Repo: https://github.com/xuhcc/beancount-mobile ( Announcement ). http://costflow.io : Plain Text Accounting in WeChat. \" Send a message to our bot in Telegram, Facebook Messenger, Whatsapp, LINE, WeChat, etc. Costflow will transform your message into Beancount / Ledger / hledger format transaction magically. Append the transaction to the file in your Dropbox / Google Drive. With the help of their apps, the file will be synced to your computer. \"","title":"Mobile/Phone Data Entry"},{"location":"fetching_prices_in_beancount.html","text":"Prices in Beancount \uf0c1 Martin Blais , December 2015 http://furius.ca/beancount/doc/prices Introduction \uf0c1 Processing a Beancount file is, by definition, constrained to the contents of the file itself. In particular, the latest prices of commodities are never fetched automatically from the internet. This is by design, so that any run of a report is deterministic and can also be run offline. No surprises. However, we do need access to price information in order to compute market values of assets. To this end, Beancount provides a Price directive which can be used to fill its in-memory price database by inserting these price points inline in the input file: 2015-11-20 price ITOT 95.46 USD 2015-11-20 price LQD 115.63 USD 2015-11-21 price USD 1.33495 CAD \u2026 Of course, you could do this manually, looking up the prices online and writing the directives yourself. But for assets which are traded publicly you can automate it, by invoking some code that will download prices and write out the directives for you. Beancount comes with some tools to help you do this. This document describes these tools. The Problem \uf0c1 In the context of maintaining a Beancount file, we have a few particular needs to address. First, we cannot expect the user to always update the input file in a timely manner. This means that we have to be able to fetch not only the latest prices, but also historical prices, from the past. Beancount provides an interface to provide these prices and a few implementations (fetching from Yahoo! Finance and from Google Finance). Second, we don't want to store too many prices for holdings which aren't relevant to us at a particular point in time. The list of assets held in a file varies over time. Ideally we would want to include the list of prices for just those assets, while they are held. The Beancount price maintenance tool is able to figure out which commodities it needs to fetch prices for at a particular date. Third, we don't want to fetch the same price if it already appears in the input file. The tools detect this and skip those prices. There's also a caching mechanism that avoids redundant network calls. Finally, while we provide some basic implementations of price sources, we cannot provide such codes for all possible online sources and websites. The problem is analogous to that of importing and extracting data from various institutions. In order to address that, we provide a mechanism for extensibility , a way that you can implement your own price source fetcher in a Python module and point to it from your input file by specifying the module's name as the source for that commodity. The \u201cbean-price\u201d Tool \uf0c1 Beancount comes with a \u201cbean-price\u201d command-line tool that integrates the ideas above. By default, this script accepts a list of Beancount input filenames, and fetches prices required to compute latest market values for current positions held in accounts: bean-price /home/joe/finances/joe.beancount It is also possible to provide a list of specific price fetching jobs to run, e.g., bean-price -e USD:google/TSE:XUS CAD:mysources.morningstar/RBF1005 These jobs are run concurrently so it should be fairly fast. Source Strings \uf0c1 The general format of each of these \"job source strings\" is :/[^] For example: USD:beancount.prices.sources.google/NASDAQ:AAPL The \u201cquote-currency\u201d is the currency the Commodity is quoted in. For example, shares of Apple are quoted in US dollars. The \"module\" is the name of a Python module that contains a Source class which can be instantiated and which connect to a data source to extract price data. These modules are automatically imported and a Source class therein instantiated in order to pull the price from the particular online source they support. This allows you to write your own fetcher codes without having to modify this script. Your code can be placed anywhere on your Python PYTHONPATH and you should not have to modify Beancount itself for this to work. The \u201csymbol\u201d is a string that is fed to the price fetcher to lookup the currency. For example, Apple shares trade on the Nasdaq, and the corresponding symbol in the Google Finance source is \u201cNASDAQ:AAPL\u201d. Other price sources may have a different symbology, e.g., some may require the asset's CUSIP. Default implementations of price sources are provided; we provide fetchers for Yahoo! Finance or Google Finance, which cover a large universe of common public investment types (e.g. stock and some mutual funds). As a convenience, the module name is always first searched under the \" beancount.prices.sources \" package, where those implementations live. This is how, for example, in order to use the provided Yahoo! Finance data fetcher you don't have to write all of \" beancount.prices.sources.yahoo/AAPL \" but you can simply use \" yahoo/AAPL \". Fallback Sources \uf0c1 In practice, fetching prices online often fails. Data sources typically only support some limited number of assets and even then, the support may vary between assets. As an example, Google Finance supports historical prices for stocks, but does not return historical prices for currency instruments (these restrictions may be more related to contractual arrangements between them and the data providers upstream than with technical limitations). To this extent, a source string may provide multiple sources for the data, separated with commas. For example: USD:google/CURRENCY:GBPUSD,yahoo/GBPUSD Each source is tried in turn, and if one fails to return a valid price, the next source is tried as a fallback. The hope is that at least one of the specified sources will work out. Inverted Prices \uf0c1 Sometimes, prices are only available for the inverse of an instrument. This is often the case for currencies. For example, the price of Canadian dollars quoted in US dollars is provided by the USD/CAD market, which gives the price of a US dollar in Canadian dollars (the inverse). In order use this, you can prepend \" ^ \" to the instrument name to instruct the tool to compute the inverse of the fetched price: USD:google/^CURRENCY:USDCAD If a source price is to be inverted, like this, the precision could be different than what is fetched. For instance, if the price of USD/CAD is 1.32759, for the above directive to price the \u201cCAD\u201d instrument it would output this: 2015-10-28 price CAD 0.753244601119 USD By default, inverted rates will be rounded similarly to how other Price directives were rounding those numbers. As you may now, Beancount's in-memory price database works in both directions (the reciprocals of all rates are stored automatically). So if you prefer to have the output Price entries with swapped currencies instead of inverting the rate number itself, you can use the --swap-inverted option. In the previous example for the price of CAD, it would output this: 2015-10-28 price USD 1.32759 CAD Date \uf0c1 By default, the latest prices for the assets are pulled in. You can use an option to fetch prices for a desired date in the past instead: bean-price --date=2015-02-03 \u2026 If you are using an input file to specify the list of prices to be fetched, the tool will figure out the list of assets held on the books at that time and fetch historical prices for those assets only. Caching \uf0c1 Prices are automatically cached (if current and latest, prices are cached for only a short period of time, about half an hour). This is convenient when you're having to run the script multiple times in a row for troubleshooting. You can disable the cache with an option: bean-price --no-cache You can also instruct the script to clear the cache before fetching its prices: bean-price --clear-cache Prices from a Beancount Input File \uf0c1 Generally, one uses a Beancount input file to specify the list of currencies to fetch. In order to do that, you should have Commodity directives in your input file for each of the currencies you mean to fetch prices for, like this: 2007-07-20 commodity VEA price: \"USD:google/NYSEARCA:VEA\" The \"price\" metadata should contain a list of price source strings. For example, a stock product might look like this: 2007-07-20 commodity CAD price: \"USD:google/CURRENCY:USDCAD,yahoo/USDCAD\" While a currency may have multiple target currencies it needs to get converted to: 1990-01-01 commodity GBP price: \"USD:yahoo/GBPUSD CAD:yahoo/GBPCAD CHF:yahoo/GBPCHF\" Which Assets are Fetched \uf0c1 There are many ways to compute a list of commodities with needed prices from a Beancount input file: Commodity directives. The list of all Commodity directives with \u201cprice\u201d metadata present in the file. For each of those holdings, the directive is consulted and its \" price \" metadata field is used to specify where to fetch prices from. Commodities of assets held at cost. Prices for all the holdings that were seen held at cost at a particular date. Because these holdings are held at cost, we can assume there is a corresponding time-varying price for their commodity. Converted commodities. Prices for holdings which were price-converted from some other commodity in the past (i.e., converting some cash in a currency from another). By default, the list of tickers to be fetched includes only the intersection of these three lists. This is because the most common usage of this script is to fetch missing prices for a particular date, and only the needed ones. Inactive commodities. You can use the \u201c --inactive \u201d option to fetch the entire set of prices from (1), regardless of asset holdings determined in (2) and (3). Undeclared commodities. Commodities without a corresponding \u201cCommodity\u201d directive will be ignored by default. To include the full list of commodities seen in an input file, use the \u201c --undeclared \u201d option. Clobber. Existing price directives for the same data are excluded by default, since the price is already in the file. You can use \u201c --clobber \u201d to ignore existing price directives and avoid filtering out what is fetched. Finally, you can use \u201c--all\u201d to include inactive and undeclared commodities and allow clobbering existing ones. You probably don't want to use that other than for testing. If you'd like to do some troubleshooting and print out the list of seen commodities, use the \u201c --verbose \u201d option twice, i.e., \u201c -vv \u201d. You can also just print the list of prices to be fetched with the \u201c --dry-run \u201d option, which stops short of actually fetching the missing prices. Conclusion \uf0c1 Writing Your Own Script \uf0c1 If the workflow defined by this tool does not fit your needs and you would like to cook up your own script, you should not have to start from scratch; you should be able to reuse the existing price fetching code to do that. I'm hoping to provide a few examples of such scripts in the experiments directory. For example, given an existing file it would be convenient to fetch all prices of assets every Friday in order to fill up a history of missing prices. Another example would be to fetch all price directives required in order to correctly compute investment returns in the presence of contributions and distributions. Contributions \uf0c1 If this workflow suits your needs well and you'd like to contribute some price source fetcher to Beancount, please contact the mailing-list. I'm open to including very general fetchers that have been very carefully unit-tested and used for a while.","title":"Fetching Prices in Beancount"},{"location":"fetching_prices_in_beancount.html#prices-in-beancount","text":"Martin Blais , December 2015 http://furius.ca/beancount/doc/prices","title":"Prices in Beancount"},{"location":"fetching_prices_in_beancount.html#introduction","text":"Processing a Beancount file is, by definition, constrained to the contents of the file itself. In particular, the latest prices of commodities are never fetched automatically from the internet. This is by design, so that any run of a report is deterministic and can also be run offline. No surprises. However, we do need access to price information in order to compute market values of assets. To this end, Beancount provides a Price directive which can be used to fill its in-memory price database by inserting these price points inline in the input file: 2015-11-20 price ITOT 95.46 USD 2015-11-20 price LQD 115.63 USD 2015-11-21 price USD 1.33495 CAD \u2026 Of course, you could do this manually, looking up the prices online and writing the directives yourself. But for assets which are traded publicly you can automate it, by invoking some code that will download prices and write out the directives for you. Beancount comes with some tools to help you do this. This document describes these tools.","title":"Introduction"},{"location":"fetching_prices_in_beancount.html#the-problem","text":"In the context of maintaining a Beancount file, we have a few particular needs to address. First, we cannot expect the user to always update the input file in a timely manner. This means that we have to be able to fetch not only the latest prices, but also historical prices, from the past. Beancount provides an interface to provide these prices and a few implementations (fetching from Yahoo! Finance and from Google Finance). Second, we don't want to store too many prices for holdings which aren't relevant to us at a particular point in time. The list of assets held in a file varies over time. Ideally we would want to include the list of prices for just those assets, while they are held. The Beancount price maintenance tool is able to figure out which commodities it needs to fetch prices for at a particular date. Third, we don't want to fetch the same price if it already appears in the input file. The tools detect this and skip those prices. There's also a caching mechanism that avoids redundant network calls. Finally, while we provide some basic implementations of price sources, we cannot provide such codes for all possible online sources and websites. The problem is analogous to that of importing and extracting data from various institutions. In order to address that, we provide a mechanism for extensibility , a way that you can implement your own price source fetcher in a Python module and point to it from your input file by specifying the module's name as the source for that commodity.","title":"The Problem"},{"location":"fetching_prices_in_beancount.html#the-bean-price-tool","text":"Beancount comes with a \u201cbean-price\u201d command-line tool that integrates the ideas above. By default, this script accepts a list of Beancount input filenames, and fetches prices required to compute latest market values for current positions held in accounts: bean-price /home/joe/finances/joe.beancount It is also possible to provide a list of specific price fetching jobs to run, e.g., bean-price -e USD:google/TSE:XUS CAD:mysources.morningstar/RBF1005 These jobs are run concurrently so it should be fairly fast.","title":"The \u201cbean-price\u201d Tool"},{"location":"fetching_prices_in_beancount.html#source-strings","text":"The general format of each of these \"job source strings\" is :/[^] For example: USD:beancount.prices.sources.google/NASDAQ:AAPL The \u201cquote-currency\u201d is the currency the Commodity is quoted in. For example, shares of Apple are quoted in US dollars. The \"module\" is the name of a Python module that contains a Source class which can be instantiated and which connect to a data source to extract price data. These modules are automatically imported and a Source class therein instantiated in order to pull the price from the particular online source they support. This allows you to write your own fetcher codes without having to modify this script. Your code can be placed anywhere on your Python PYTHONPATH and you should not have to modify Beancount itself for this to work. The \u201csymbol\u201d is a string that is fed to the price fetcher to lookup the currency. For example, Apple shares trade on the Nasdaq, and the corresponding symbol in the Google Finance source is \u201cNASDAQ:AAPL\u201d. Other price sources may have a different symbology, e.g., some may require the asset's CUSIP. Default implementations of price sources are provided; we provide fetchers for Yahoo! Finance or Google Finance, which cover a large universe of common public investment types (e.g. stock and some mutual funds). As a convenience, the module name is always first searched under the \" beancount.prices.sources \" package, where those implementations live. This is how, for example, in order to use the provided Yahoo! Finance data fetcher you don't have to write all of \" beancount.prices.sources.yahoo/AAPL \" but you can simply use \" yahoo/AAPL \".","title":"Source Strings"},{"location":"fetching_prices_in_beancount.html#fallback-sources","text":"In practice, fetching prices online often fails. Data sources typically only support some limited number of assets and even then, the support may vary between assets. As an example, Google Finance supports historical prices for stocks, but does not return historical prices for currency instruments (these restrictions may be more related to contractual arrangements between them and the data providers upstream than with technical limitations). To this extent, a source string may provide multiple sources for the data, separated with commas. For example: USD:google/CURRENCY:GBPUSD,yahoo/GBPUSD Each source is tried in turn, and if one fails to return a valid price, the next source is tried as a fallback. The hope is that at least one of the specified sources will work out.","title":"Fallback Sources"},{"location":"fetching_prices_in_beancount.html#inverted-prices","text":"Sometimes, prices are only available for the inverse of an instrument. This is often the case for currencies. For example, the price of Canadian dollars quoted in US dollars is provided by the USD/CAD market, which gives the price of a US dollar in Canadian dollars (the inverse). In order use this, you can prepend \" ^ \" to the instrument name to instruct the tool to compute the inverse of the fetched price: USD:google/^CURRENCY:USDCAD If a source price is to be inverted, like this, the precision could be different than what is fetched. For instance, if the price of USD/CAD is 1.32759, for the above directive to price the \u201cCAD\u201d instrument it would output this: 2015-10-28 price CAD 0.753244601119 USD By default, inverted rates will be rounded similarly to how other Price directives were rounding those numbers. As you may now, Beancount's in-memory price database works in both directions (the reciprocals of all rates are stored automatically). So if you prefer to have the output Price entries with swapped currencies instead of inverting the rate number itself, you can use the --swap-inverted option. In the previous example for the price of CAD, it would output this: 2015-10-28 price USD 1.32759 CAD","title":"Inverted Prices"},{"location":"fetching_prices_in_beancount.html#date","text":"By default, the latest prices for the assets are pulled in. You can use an option to fetch prices for a desired date in the past instead: bean-price --date=2015-02-03 \u2026 If you are using an input file to specify the list of prices to be fetched, the tool will figure out the list of assets held on the books at that time and fetch historical prices for those assets only.","title":"Date"},{"location":"fetching_prices_in_beancount.html#caching","text":"Prices are automatically cached (if current and latest, prices are cached for only a short period of time, about half an hour). This is convenient when you're having to run the script multiple times in a row for troubleshooting. You can disable the cache with an option: bean-price --no-cache You can also instruct the script to clear the cache before fetching its prices: bean-price --clear-cache","title":"Caching"},{"location":"fetching_prices_in_beancount.html#prices-from-a-beancount-input-file","text":"Generally, one uses a Beancount input file to specify the list of currencies to fetch. In order to do that, you should have Commodity directives in your input file for each of the currencies you mean to fetch prices for, like this: 2007-07-20 commodity VEA price: \"USD:google/NYSEARCA:VEA\" The \"price\" metadata should contain a list of price source strings. For example, a stock product might look like this: 2007-07-20 commodity CAD price: \"USD:google/CURRENCY:USDCAD,yahoo/USDCAD\" While a currency may have multiple target currencies it needs to get converted to: 1990-01-01 commodity GBP price: \"USD:yahoo/GBPUSD CAD:yahoo/GBPCAD CHF:yahoo/GBPCHF\"","title":"Prices from a Beancount Input File"},{"location":"fetching_prices_in_beancount.html#which-assets-are-fetched","text":"There are many ways to compute a list of commodities with needed prices from a Beancount input file: Commodity directives. The list of all Commodity directives with \u201cprice\u201d metadata present in the file. For each of those holdings, the directive is consulted and its \" price \" metadata field is used to specify where to fetch prices from. Commodities of assets held at cost. Prices for all the holdings that were seen held at cost at a particular date. Because these holdings are held at cost, we can assume there is a corresponding time-varying price for their commodity. Converted commodities. Prices for holdings which were price-converted from some other commodity in the past (i.e., converting some cash in a currency from another). By default, the list of tickers to be fetched includes only the intersection of these three lists. This is because the most common usage of this script is to fetch missing prices for a particular date, and only the needed ones. Inactive commodities. You can use the \u201c --inactive \u201d option to fetch the entire set of prices from (1), regardless of asset holdings determined in (2) and (3). Undeclared commodities. Commodities without a corresponding \u201cCommodity\u201d directive will be ignored by default. To include the full list of commodities seen in an input file, use the \u201c --undeclared \u201d option. Clobber. Existing price directives for the same data are excluded by default, since the price is already in the file. You can use \u201c --clobber \u201d to ignore existing price directives and avoid filtering out what is fetched. Finally, you can use \u201c--all\u201d to include inactive and undeclared commodities and allow clobbering existing ones. You probably don't want to use that other than for testing. If you'd like to do some troubleshooting and print out the list of seen commodities, use the \u201c --verbose \u201d option twice, i.e., \u201c -vv \u201d. You can also just print the list of prices to be fetched with the \u201c --dry-run \u201d option, which stops short of actually fetching the missing prices.","title":"Which Assets are Fetched"},{"location":"fetching_prices_in_beancount.html#conclusion","text":"","title":"Conclusion"},{"location":"fetching_prices_in_beancount.html#writing-your-own-script","text":"If the workflow defined by this tool does not fit your needs and you would like to cook up your own script, you should not have to start from scratch; you should be able to reuse the existing price fetching code to do that. I'm hoping to provide a few examples of such scripts in the experiments directory. For example, given an existing file it would be convenient to fetch all prices of assets every Friday in order to fill up a history of missing prices. Another example would be to fetch all price directives required in order to correctly compute investment returns in the presence of contributions and distributions.","title":"Writing Your Own Script"},{"location":"fetching_prices_in_beancount.html#contributions","text":"If this workflow suits your needs well and you'd like to contribute some price source fetcher to Beancount, please contact the mailing-list. I'm open to including very general fetchers that have been very carefully unit-tested and used for a while.","title":"Contributions"},{"location":"fund_accounting_with_beancount.html","text":"Fund Accounting with Beancount \uf0c1 Martin Blais , Carl Hauser, August 2014 http://furius.ca/beancount/doc/proposal-funds A discussion about how to carry out fund accounting within Beancount, various approaches, solutions and possible extensions. Motivation \uf0c1 Multiple users are attempting to solve the problem of fund accounting using command-line accounting systems, partially because this type of accounting occurs in the context of non-profit organizations that have small budgets and would prefer to use free software, and partially because the flexibility and customization required appear to be a good fit for command-line bookkeeping systems. What is Fund Accounting? \uf0c1 For example, see this thread : \u201cAnother religious duty I compute is effectively tithing (we call it Huq\u00faqu'll\u00e1h, and it's computed differently, but that's another story). In order to compute the tithe owed, I accrue 19% of every deposit to a virtual account, and then subtract from that account 19% of every needful expenditure. The total remaining at the end of the year is what I owe in tithe. This tithing account is not a real account, as it exists in no financial institution; but it is real enough as a personal duty. By using virtual account, I can track this \"side-band\" Liability, and then pay it off from an assets account when the time comes. If I report with --real I will simply see how much I've paid to this Liability; and if I report without --real I see how much Huq\u00faqu'll\u00e1h is presently owed.\u201d \u2014 John Wiegley Here\u2019s another description, as a comment from another user: [...] basically the idea that you split your financial life into separate pots called \"funds\". Each fund has its own chart of accounts (to a certain extent) and each fund obeys Assets+Liabilities+Equities == 0. This is often needed in non-profits where money given for specific purposes has to be accounted separately. The one area of non-separation is that actual asset accounts (e.g. bank accounts) and actual liability accounts (credit cards) may hold or owe money on behalf of multiple funds, so you can't use entirely separate ledger files. At our church we use a program called PowerChurchPlus for this and it works really well. My wife is now treasurer for a community music organization and facing the same problem but on such a small scale that the cost of a commercial program isn't warranted. I've seen what was posted in the ledger-cli list about non-profit accounting using C++ ledger and it just looks like it requires way too much discipline to use the tags correctly and consistently. The fund idea is much easier to explain and use (and the balance account invariant naturally enforced). So I was looking at the Beancount code to see whether I could adapt it to support fund accounting. I think the answer is \"yes and relatively easily so\". Furthermore, I think this idea has uses even for individuals: a couple of scenarios present themselves. First, I live in a community property state. My wife and I are each likely to inherit property which will be ours individually, but we also have the community property. These can each be treated as a separate fund and we will be able to report on them separately but also together for understanding our overall financial situation. Similarly, it potentially makes sense to account for retirement money with a separate fund for each person. \u2014 Carl Hauser From the PowerChurchPlus 11.5 Manual (PowerChurch, Inc. 2013): \u201cIn accounting, the term fund has a very specific meaning. An Accounting Fund is a self-balancing Chart of Accounts. [...] In accounting we need to think of a fund as a division, or sub-group of your church. Each Accounting Fund has its own assets, liabilities, equity, income, transfer and expense accounts. So when would you use an additional fund? If you have an area of your church that needs to produce its own reporting, you would need to use an additional fund. For example, if your church operates a preschool or play school, you might want to set up an additional fund to keep their finances separate from the church's. Depending on your needs, you might want to setup a separate fund for the men's or women's group. You might even setup a new fund to keep track of all fixed assets separately from the daily operating accounts.\u201d This is an interesting and apparently common problem. We will describe use cases in the rest of this section. Joint Account Management \uf0c1 I have personally used this \u201cfund accounting\u201d idea to manage a joint account that I had with my ex-wife, where we would both hold individual accounts\u2014we were both working professionals\u2014 and chip in to the joint account as needed. This section describes how I did this 1 . The accounting for the joint account was held in a separate file. Two sub-accounts were created to hold each of our \u201cportions\u201d: 2010-01-01 open Assets:BofA:Joint 2010-01-01 open Assets:BofA:Joint:Martin 2010-01-01 open Assets:BofA:Joint:Judie Transfers to the joint account were directly booked into one of the two sub-account: 2012-09-07 * \"Online Xfer Transfer from CK 345\" Assets:BofA:Joint:Judie 1000.00 USD Income:Contributions:Judie When we would incur expenses, we would reduce the asset account with two legs, one for each subaccount. We often booked them 2:1 to account for difference in income, or I just booked many of the transactions to myself (the fact that it was precisely accounted for does not imply that we weren\u2019t being generous to each other in that way): 2013-04-27 * \"Urban Vets for Grumpy\" Expenses:Medical:Cat 100.00 USD Assets:BofA:Joint:Martin -50 USD Assets:BofA:Joint:Judie -50 USD 2013-05-30 * \"Takahachi\" \"Dinner\" Expenses:Food:Restaurant 65.80 USD Assets:BofA:Joint:Judie -25.00 USD Assets:BofA:Joint:Martin It was convenient to elide one of the two amounts, as we weren\u2019t being very precise about this. Handling Multiple Funds \uf0c1 (Contributed from Carl Hauser) Here\u2019s the model used in the PowerChurchPlus system that is mentioned above (replacing the account numbers it uses with Beancount-style names). \u201cFund\u201d names are prefixed to the account names. Operations:Assets:Bank:... Endowment:Assets:Bank:... Operations:Liabilities:CreditCard:... Endowment:Liabilities:CreditCard:... Operations:Income:Pledges:2014 Operations:Expenses:Salaries:... Operations:Expenses:BuildingImprovement:... Endowment:Income:Investments:... Endowment:Expenses:BuildingImprovement:... \u2026 It is required that any transaction be balanced in every fund that it uses. For example, our Endowment fund often helps pay for things that are beyond the reach of current donations income. 2014-07-25 * \"Bill\u2019s Audio\" \"Sound system upgrade\" Endowment:Assets:Bank1:Checking 800.00 USD Operations:Assets:Bank1:Checking 200.00 USD Endowment:Expenses:BuildingImprovement:Sound -800.00 USD Operations:Expenses:BuildingImprovement:Sound -200.00 USD This represents a single check to Bill\u2019s Audio paid from assets of both the Endowment and Operations funds that are kept in the single external assets account Assets:Bank1:Checking. Note 1: An empty fund name could be allowed and the following \u201c:\u201d omitted, and in fact could be the default for people who don\u2019t want to use these features. (i.e., nothing changes if you don\u2019t use these features.) The Fund with the empty string for its name is, of course, distinct from all other Funds. Note 2: balance and pad directives are not very useful with accounts that participate in more than one Fund. Their use would require knowing the allocation of the account between the different funds and account statements from external holders (banks, e.g.) will not have this information. It might be useful to allow something like 2014-07-31 balance *:Assets:Bank1:Checking 579.39 USD as a check that things were right, but automatically correcting it with pad entries seems impossible. A balance sheet report can be run on any Fund or any combination of Funds and it will balance. You can keep track of what is owned for each different purpose easily. Transfers between funds are booked as expenses and decreases in the assets of one fund and income and increases in assets of the other. The income and expense accounts used for transfers may be generic ( Operations:Income:Transfer ) or you can use accounts set up for a particular kind of income or expense ( Endowment:Expense:BuildingImprovement:Sound ) would be fine as one leg of a transfer transaction. The account name syntax here is just one way it might work and relies on Beancount\u2019s use of five specifically-named top-level accounts. Anything to the left of the first of those could be treated as a fund name, or a different separator could be used between the fund part and the account name part. Similarly, I\u2019ve only shown single-level fund names but they might be hierarchical as well. I\u2019m not sure of the value of that, but observe that if transactions balance at the leaf-level funds they will also balance for funds higher in the hierarchy and there might be some mileage there. For John W.\u2019s Huq\u00faqu'll\u00e1h example one might set up a Fund whose liabilities were \u201cmoral obligations\u201d rather than legal ones (that seems to be the objection to simply tracking the tithes in an ordinary liability account). As income comes in (say, direct deposited in a real bank checking account), book 19% of it to the \u201cmoral obligation\u201d fund\u2019s checking account with a matching liability. When needful expenses are made, take back 19% from the \u201cmoral obligation\u201d fund\u2019s checking account and reduce the liability. No virtual postings or transactions -- everything must balance. This would work well if for example we were to have a HisRetirement fund and a HerRetirement fund -- useful to have separate for estate planning purposes -- but more commonly we want to know about our combined retirement which could be defined to be a virtual fund OurRetirement equal to the sum of HisRetirement and HerRetirement . Note that this only matters when creating reports: there is no need to do anything except normal, double-entry booking with balanced transactions in each real fund. When I say the \u201csum\u201d of two funds I mean a combination of taking the union of the contained account names, after stripping off the fund names, then summing the balances of the common accounts and keeping the balances of the others. (Balance Sheet) For reporting, one wants the capability for balance by fund and balance summed over a set of funds. I also use a report that shows a subset of funds, one per column, with corresponding account names lined up horizontally and a column at the right that is the \u201csum\u201d. When all funds are included in this latter report you get a complete picture of what you own and owe and what is set aside for different purposes, or restricted in different ways. Here\u2019s a small sample of a balance sheet for a subset of the church Funds. The terminology is a little different: what Beancount calls Equity is here Net Assets. And because using large numbers of Funds in PowerChurchPlus is not so easy, the NetAssets are actually categorized for different purposes -- this is where I think the ideas we\u2019re exploring here can really shine: if Funds are really easy to create and combine for reporting then some of the mechanisms that PCP has for divvying up assets within a fund become unnecessary. (Income Statement) For the Income and Expense report, usually I only care about expenses in one Fund at a time, but adding two funds together makes perfect sense if that\u2019s what you need to do. For me, this approach to fund accounting is appealing because it relies on and preserves the fundamental principles of double-entry bookkeeping: when transactions sum to 0 the balance sheet equation is always true. Out of this we automatically get the ability to combine any set of funds (we don\u2019t have to do anything special when entering transactions or organizing the deep structure of the accounts) and have it make at least arithmetical sense, and we don\u2019t rely on any \u201cmagic\u201d associated with renaming or tagging. I don\u2019t see how this can be so easily or neatly achieved by pushing the idea of the \u201cfunds\u201d down into the account hierarchy: funds belong above the five root accounts (Assets, Liabilities, Equity, Income and Expenses), not below them. Ideas for Implementation \uf0c1 Some random ideas for now. This needs a bit more work. If multiple redundant postings are required, the generation of these can be automated using a plugin . For instance, if a technique similar to mirror accounting is used in order to \u201csend the same dollars to multiple accounts\u201d, at least the user should not have to do this manually, which would be both tedious and prone to errors. A procedure to rename accounts upon parsing could be used, in order to merge multiple files into one. (Allowing the user to install such a mapping is an idea I\u2019ve had for a while but never implemented, though it could be implemented by a plugin filter.) We can rely on the fact that the transactions of subaccounts may be joined and summed in a parent account (despite the fact that reporting is lagging behind in that matter at the moment. It will be implemented eventually). Building off the earlier remark about doing something similar to the tag stack for Funds. What if the current tag architecture were extended to allow tags to have a value, #fund=Operations, or #fund=Endowment . Call them value-tags. You would also need to allow postings to have tags. Semantics of value-tags would be that they could only occur once for any posting, that a tag explicitly on a posting overrides the value-tag inherited from the transaction, and that an explicit tag on a transaction overrides a value from the tag-stack, and that only the last value-tag (with a particular key) in the tag-stack is applied to a transaction. This makes Funds a little less first-class than the earlier proposal to stick them in front of account names, but gets around the minor parsing difficulty previously mentioned. It suggests that opening of accounts within funds is not necessary where the previous syntax suggests that it is. The strict balancing rule for each fund in a transaction can still be implemented as a plugin. And reporting for a fund (or sum of funds) looks like: select transactions with any posting matching the desired fund (or funds) collect (and sum if necessary in the obvious way) postings associated with the fund (or funds) being reported on (A) collect (and sum in the obvious way) postings from the selected transactions not associated with the desired fund (B) Format a report with (A) as the information for the desired fund or funds and (B) as OTHER. OTHER is needed to make sure that the report balances, but could be omitted by choice. From an implementation perspective this seems more orthogonal to the current status quo, requiring even less change to existing code. It adds a new feature -- value tags and that can then be used by plugins and new reports to do what we want for fund accounting. Examples \uf0c1 (Carl) Here is an example of how I might try to handle things associated with my paycheck, which involves deferred compensation (403(b) -- extremely similar to a 401(k)) and a Flexible Spending Account (somewhat similar to an HSA which has been discussed previously on the ledger-cli group). Without Funds \uf0c1 (Carl) First, without Funds (this passes a bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions in the absence of Funds. One problem is that it distributes Gross Income directly into the 403b and FSA accounts even though it recognizes that the health insurance contribution is a reduction in salary which the 403b and FSA ought to be as well. So tracking contributions to both of those is made more difficult as well as tracking taxable income. By thinking hard we could fix this -- we would come up with Income and Expense type accounts to represent the contributions, but they would end up looking kind of silly (in my opinion) because they are entirely internal to the accounts system. See the next example for how it would look using Funds. If you stick out your tongue, rub your tummy and stand on your head you will see that the Funds-based solution is equivalent to what we would have come up with in the paragraph above in terms of the complexity of its transactions -- just as many lines are required. The advantage is primarily a mental one -- it is much easier to see what to do to be both consistent and useful. option \u201ctitle\u201d \u201cPaystub - no funds\u201d 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Assets:Deferred:R-403b 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open Assets:FSA ; This way of setting up an FSA looks pretty good. It recognizes the ; rule that the designated amount for the year is immediately available ; (in the Asset account), and that we are obliged to make contributions ; to fund it over the course of the year (the Liability account). 2014-01-01 open Liabilities:FSA 2014-01-01 ! \"Set up FSA for 2014\" Assets:FSA 2000 USD Liabilities:FSA -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Assets:Deferred:R-403b 600 USD Liabilities:FSA 75 USD Expenses:SalReduction:HealthInsurance 90 USD Income:EmplContrib:Emp1:Retirement -600 USD Assets:Deferred:R-403b 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" Expenses:Medical 25 USD Assets:FSA 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Assets:FSA Using Funds \uf0c1 (Carl) And now using Funds (uses proposed features and hence can\u2019t be checked by bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions using Funds. I can straightforwardly arrange things so that contributions to the FSA and 403b are recognized as salary reductions for income tax purposes. And I can easily see how much I have contributed to the 403b and how much my employer has contributed. See the previous example for how it would look without using Funds and which is not as accurate. What this does NOT do is track taxes ultimately owed on the 403b money. I think that is a matter of one's decision about whether to do cash-basis or accrual-basis accounting. If cash basis those taxes are not a current liability and cannot be reported as such. If accrual basis, they are a current liability and need to be recorded as such when the income is booked. For cash-basis books, we'd want the ability to report the state of affairs as if taxes were owed, but that is a matter for reporting rather than booking. We need to make sure we have enough identifiable information to automate creating those reports. I believe that taking a Fund-based accounting perspective easily does this. A problem not solved: what if your basis is different for Federal and State purposes, or even for Federal and multiple different states. Yikes! I've used the convention that the Fund name precedes the root account name. Note that with appropriate standing on one's head along with pivoting rules you can put the Fund name anywhere. Putting it first emphasizes that it identifies a set of accounts that must balance, and it makes it easy for the txn processor to guarantee this property. Accounts without a fund name in front belong to the Fund whose name is the empty string. option \"title\" \"Paystub - no Funds\" 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Expenses:SalReduction:FSA 2014-07-15 open Expenses:SalReduction:R-403b 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open FSA:Assets ; FSA fund accounts 2014-01-01 open FSA:Income:Contributions 2014-01-01 open FSA:Expenses:Medical 2014-01-01 open FSA:Expenses:ReimburseMedical 2014-01-01 open FSA:Liabilities 2014-07-15 open Retirement403b:Assets:CREF ; Retirement fund accounts 2014-07-15 open Retirement403b:Income:EmployeeContrib 2014-07-15 open Retirement403b:Income:EmployerContrib 2014-07-15 open Retirement403b:Income:EarningsGainsAndLosses ; This implements the same idea as above for the FSA, of balancing ; Assets and Liabilities at the opening, but now does it using a ; separate Fund. 2014-01-01 ! \"Set up FSA for 2014\" FSA:Assets 2000 USD FSA:Liabilities -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Expenses:SalReduction:R-403b 600 USD Retirement403b:Income:EmployeeContrib -600 USD Retirement403b:Assets:CREF 600 USD Expenses:SalReduction:FSA 75 USD FSA:Income:Contributions -75 USD FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" FSA:Expenses:Medical 25 USD FSA:Assets 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets -25 USD FSA:Expenses:ReimburseMedical Transfer Accounts Proposal \uf0c1 By Carl Hauser One problem that I\u2019ve experienced using the Fund approach is that it\u2019s a bit too easy to make mistakes when transferring money between funds, such as in the very last transaction above. Formalizing the idea of Transfer accounts can help with this. The most common mistake is to end up with something that moves assets in both accounts in the same direction -- both up or both down as in this mistaken version of the transaction in question: 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets 25 USD FSA:Expenses:ReimburseMedical This balances but isn\u2019t what we intended. Suppose we add the idea of Transfer accounts. They live at the same place in the hierarchy as Income and Expenses and like those are non-balance-sheet accounts. But they really come into play only for transactions that involve multiple funds. There is an additional rule for transactions containing Transfer accounts: the sum of the transfers must also be zero (additional in the sense that the rule about transactions balancing within each fund is still there). So to use this we set things up a little differently: 2014-07-15 open FSA:Transfer:Incoming:Contribution 2014-07-15 open FSA:Transfer:Outgoing:ReimburseMedical 2014-07-15 open Transfer:Outgoing:FSAContribution 2014-07-15 open Transfer:Incoming:ReimburseMedical The incorrect transaction is now flagged because sum of the transfers is -50 USD , not zero. 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Transfer:Incoming:ReimburseMedical FSA:Assets 25 USD FSA:Transfer:Outgoing:ReimburseMedical The paycheck transaction using transfer accounts for the FSA and the retirement account amounts might look like this (after appropriate open s of course): 2014-07-15 ! \"Emp1 Paystub - using transfer accounts\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Transfer:Outgoing:Retirement403b:SalReduction 600 USD Retirement403b:Transfer:Incoming:EmployeeContrib Retirement403b:Assets:CREF 600 USD Transfer:Outgoing:FSA:SalReduction 75 USD FSA:Transfer:Incoming:Contributions FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF Some might think that this is too complicated. Without changing the Transfer accounts idea or rule, you can simplify booking to just a single account per fund, Fund:Transfer , losing some ability for precision in reporting but without losing the ability to check correctness of transfer transactions. Account Aliases \uf0c1 Simon Michael mentions that this is related to HLedger account aliases : \u201cI think this is related to the situation where you want to view entities' finances both separately and merged. Account aliases can be another way to approximate this, as in http://hledger.org/how-to-use-account-aliases .\u201d If you find yourself culturally challenged by our modern lifestyle, perhaps you can imagine the case of roommates, although I don\u2019t like the reductionist view this association brings to my mind. \u21a9","title":"Fund Accounting with Beancount"},{"location":"fund_accounting_with_beancount.html#fund-accounting-with-beancount","text":"Martin Blais , Carl Hauser, August 2014 http://furius.ca/beancount/doc/proposal-funds A discussion about how to carry out fund accounting within Beancount, various approaches, solutions and possible extensions.","title":"Fund Accounting with Beancount"},{"location":"fund_accounting_with_beancount.html#motivation","text":"Multiple users are attempting to solve the problem of fund accounting using command-line accounting systems, partially because this type of accounting occurs in the context of non-profit organizations that have small budgets and would prefer to use free software, and partially because the flexibility and customization required appear to be a good fit for command-line bookkeeping systems.","title":"Motivation"},{"location":"fund_accounting_with_beancount.html#what-is-fund-accounting","text":"For example, see this thread : \u201cAnother religious duty I compute is effectively tithing (we call it Huq\u00faqu'll\u00e1h, and it's computed differently, but that's another story). In order to compute the tithe owed, I accrue 19% of every deposit to a virtual account, and then subtract from that account 19% of every needful expenditure. The total remaining at the end of the year is what I owe in tithe. This tithing account is not a real account, as it exists in no financial institution; but it is real enough as a personal duty. By using virtual account, I can track this \"side-band\" Liability, and then pay it off from an assets account when the time comes. If I report with --real I will simply see how much I've paid to this Liability; and if I report without --real I see how much Huq\u00faqu'll\u00e1h is presently owed.\u201d \u2014 John Wiegley Here\u2019s another description, as a comment from another user: [...] basically the idea that you split your financial life into separate pots called \"funds\". Each fund has its own chart of accounts (to a certain extent) and each fund obeys Assets+Liabilities+Equities == 0. This is often needed in non-profits where money given for specific purposes has to be accounted separately. The one area of non-separation is that actual asset accounts (e.g. bank accounts) and actual liability accounts (credit cards) may hold or owe money on behalf of multiple funds, so you can't use entirely separate ledger files. At our church we use a program called PowerChurchPlus for this and it works really well. My wife is now treasurer for a community music organization and facing the same problem but on such a small scale that the cost of a commercial program isn't warranted. I've seen what was posted in the ledger-cli list about non-profit accounting using C++ ledger and it just looks like it requires way too much discipline to use the tags correctly and consistently. The fund idea is much easier to explain and use (and the balance account invariant naturally enforced). So I was looking at the Beancount code to see whether I could adapt it to support fund accounting. I think the answer is \"yes and relatively easily so\". Furthermore, I think this idea has uses even for individuals: a couple of scenarios present themselves. First, I live in a community property state. My wife and I are each likely to inherit property which will be ours individually, but we also have the community property. These can each be treated as a separate fund and we will be able to report on them separately but also together for understanding our overall financial situation. Similarly, it potentially makes sense to account for retirement money with a separate fund for each person. \u2014 Carl Hauser From the PowerChurchPlus 11.5 Manual (PowerChurch, Inc. 2013): \u201cIn accounting, the term fund has a very specific meaning. An Accounting Fund is a self-balancing Chart of Accounts. [...] In accounting we need to think of a fund as a division, or sub-group of your church. Each Accounting Fund has its own assets, liabilities, equity, income, transfer and expense accounts. So when would you use an additional fund? If you have an area of your church that needs to produce its own reporting, you would need to use an additional fund. For example, if your church operates a preschool or play school, you might want to set up an additional fund to keep their finances separate from the church's. Depending on your needs, you might want to setup a separate fund for the men's or women's group. You might even setup a new fund to keep track of all fixed assets separately from the daily operating accounts.\u201d This is an interesting and apparently common problem. We will describe use cases in the rest of this section.","title":"What is Fund Accounting?"},{"location":"fund_accounting_with_beancount.html#joint-account-management","text":"I have personally used this \u201cfund accounting\u201d idea to manage a joint account that I had with my ex-wife, where we would both hold individual accounts\u2014we were both working professionals\u2014 and chip in to the joint account as needed. This section describes how I did this 1 . The accounting for the joint account was held in a separate file. Two sub-accounts were created to hold each of our \u201cportions\u201d: 2010-01-01 open Assets:BofA:Joint 2010-01-01 open Assets:BofA:Joint:Martin 2010-01-01 open Assets:BofA:Joint:Judie Transfers to the joint account were directly booked into one of the two sub-account: 2012-09-07 * \"Online Xfer Transfer from CK 345\" Assets:BofA:Joint:Judie 1000.00 USD Income:Contributions:Judie When we would incur expenses, we would reduce the asset account with two legs, one for each subaccount. We often booked them 2:1 to account for difference in income, or I just booked many of the transactions to myself (the fact that it was precisely accounted for does not imply that we weren\u2019t being generous to each other in that way): 2013-04-27 * \"Urban Vets for Grumpy\" Expenses:Medical:Cat 100.00 USD Assets:BofA:Joint:Martin -50 USD Assets:BofA:Joint:Judie -50 USD 2013-05-30 * \"Takahachi\" \"Dinner\" Expenses:Food:Restaurant 65.80 USD Assets:BofA:Joint:Judie -25.00 USD Assets:BofA:Joint:Martin It was convenient to elide one of the two amounts, as we weren\u2019t being very precise about this.","title":"Joint Account Management"},{"location":"fund_accounting_with_beancount.html#handling-multiple-funds","text":"(Contributed from Carl Hauser) Here\u2019s the model used in the PowerChurchPlus system that is mentioned above (replacing the account numbers it uses with Beancount-style names). \u201cFund\u201d names are prefixed to the account names. Operations:Assets:Bank:... Endowment:Assets:Bank:... Operations:Liabilities:CreditCard:... Endowment:Liabilities:CreditCard:... Operations:Income:Pledges:2014 Operations:Expenses:Salaries:... Operations:Expenses:BuildingImprovement:... Endowment:Income:Investments:... Endowment:Expenses:BuildingImprovement:... \u2026 It is required that any transaction be balanced in every fund that it uses. For example, our Endowment fund often helps pay for things that are beyond the reach of current donations income. 2014-07-25 * \"Bill\u2019s Audio\" \"Sound system upgrade\" Endowment:Assets:Bank1:Checking 800.00 USD Operations:Assets:Bank1:Checking 200.00 USD Endowment:Expenses:BuildingImprovement:Sound -800.00 USD Operations:Expenses:BuildingImprovement:Sound -200.00 USD This represents a single check to Bill\u2019s Audio paid from assets of both the Endowment and Operations funds that are kept in the single external assets account Assets:Bank1:Checking. Note 1: An empty fund name could be allowed and the following \u201c:\u201d omitted, and in fact could be the default for people who don\u2019t want to use these features. (i.e., nothing changes if you don\u2019t use these features.) The Fund with the empty string for its name is, of course, distinct from all other Funds. Note 2: balance and pad directives are not very useful with accounts that participate in more than one Fund. Their use would require knowing the allocation of the account between the different funds and account statements from external holders (banks, e.g.) will not have this information. It might be useful to allow something like 2014-07-31 balance *:Assets:Bank1:Checking 579.39 USD as a check that things were right, but automatically correcting it with pad entries seems impossible. A balance sheet report can be run on any Fund or any combination of Funds and it will balance. You can keep track of what is owned for each different purpose easily. Transfers between funds are booked as expenses and decreases in the assets of one fund and income and increases in assets of the other. The income and expense accounts used for transfers may be generic ( Operations:Income:Transfer ) or you can use accounts set up for a particular kind of income or expense ( Endowment:Expense:BuildingImprovement:Sound ) would be fine as one leg of a transfer transaction. The account name syntax here is just one way it might work and relies on Beancount\u2019s use of five specifically-named top-level accounts. Anything to the left of the first of those could be treated as a fund name, or a different separator could be used between the fund part and the account name part. Similarly, I\u2019ve only shown single-level fund names but they might be hierarchical as well. I\u2019m not sure of the value of that, but observe that if transactions balance at the leaf-level funds they will also balance for funds higher in the hierarchy and there might be some mileage there. For John W.\u2019s Huq\u00faqu'll\u00e1h example one might set up a Fund whose liabilities were \u201cmoral obligations\u201d rather than legal ones (that seems to be the objection to simply tracking the tithes in an ordinary liability account). As income comes in (say, direct deposited in a real bank checking account), book 19% of it to the \u201cmoral obligation\u201d fund\u2019s checking account with a matching liability. When needful expenses are made, take back 19% from the \u201cmoral obligation\u201d fund\u2019s checking account and reduce the liability. No virtual postings or transactions -- everything must balance. This would work well if for example we were to have a HisRetirement fund and a HerRetirement fund -- useful to have separate for estate planning purposes -- but more commonly we want to know about our combined retirement which could be defined to be a virtual fund OurRetirement equal to the sum of HisRetirement and HerRetirement . Note that this only matters when creating reports: there is no need to do anything except normal, double-entry booking with balanced transactions in each real fund. When I say the \u201csum\u201d of two funds I mean a combination of taking the union of the contained account names, after stripping off the fund names, then summing the balances of the common accounts and keeping the balances of the others. (Balance Sheet) For reporting, one wants the capability for balance by fund and balance summed over a set of funds. I also use a report that shows a subset of funds, one per column, with corresponding account names lined up horizontally and a column at the right that is the \u201csum\u201d. When all funds are included in this latter report you get a complete picture of what you own and owe and what is set aside for different purposes, or restricted in different ways. Here\u2019s a small sample of a balance sheet for a subset of the church Funds. The terminology is a little different: what Beancount calls Equity is here Net Assets. And because using large numbers of Funds in PowerChurchPlus is not so easy, the NetAssets are actually categorized for different purposes -- this is where I think the ideas we\u2019re exploring here can really shine: if Funds are really easy to create and combine for reporting then some of the mechanisms that PCP has for divvying up assets within a fund become unnecessary. (Income Statement) For the Income and Expense report, usually I only care about expenses in one Fund at a time, but adding two funds together makes perfect sense if that\u2019s what you need to do. For me, this approach to fund accounting is appealing because it relies on and preserves the fundamental principles of double-entry bookkeeping: when transactions sum to 0 the balance sheet equation is always true. Out of this we automatically get the ability to combine any set of funds (we don\u2019t have to do anything special when entering transactions or organizing the deep structure of the accounts) and have it make at least arithmetical sense, and we don\u2019t rely on any \u201cmagic\u201d associated with renaming or tagging. I don\u2019t see how this can be so easily or neatly achieved by pushing the idea of the \u201cfunds\u201d down into the account hierarchy: funds belong above the five root accounts (Assets, Liabilities, Equity, Income and Expenses), not below them.","title":"Handling Multiple Funds"},{"location":"fund_accounting_with_beancount.html#ideas-for-implementation","text":"Some random ideas for now. This needs a bit more work. If multiple redundant postings are required, the generation of these can be automated using a plugin . For instance, if a technique similar to mirror accounting is used in order to \u201csend the same dollars to multiple accounts\u201d, at least the user should not have to do this manually, which would be both tedious and prone to errors. A procedure to rename accounts upon parsing could be used, in order to merge multiple files into one. (Allowing the user to install such a mapping is an idea I\u2019ve had for a while but never implemented, though it could be implemented by a plugin filter.) We can rely on the fact that the transactions of subaccounts may be joined and summed in a parent account (despite the fact that reporting is lagging behind in that matter at the moment. It will be implemented eventually). Building off the earlier remark about doing something similar to the tag stack for Funds. What if the current tag architecture were extended to allow tags to have a value, #fund=Operations, or #fund=Endowment . Call them value-tags. You would also need to allow postings to have tags. Semantics of value-tags would be that they could only occur once for any posting, that a tag explicitly on a posting overrides the value-tag inherited from the transaction, and that an explicit tag on a transaction overrides a value from the tag-stack, and that only the last value-tag (with a particular key) in the tag-stack is applied to a transaction. This makes Funds a little less first-class than the earlier proposal to stick them in front of account names, but gets around the minor parsing difficulty previously mentioned. It suggests that opening of accounts within funds is not necessary where the previous syntax suggests that it is. The strict balancing rule for each fund in a transaction can still be implemented as a plugin. And reporting for a fund (or sum of funds) looks like: select transactions with any posting matching the desired fund (or funds) collect (and sum if necessary in the obvious way) postings associated with the fund (or funds) being reported on (A) collect (and sum in the obvious way) postings from the selected transactions not associated with the desired fund (B) Format a report with (A) as the information for the desired fund or funds and (B) as OTHER. OTHER is needed to make sure that the report balances, but could be omitted by choice. From an implementation perspective this seems more orthogonal to the current status quo, requiring even less change to existing code. It adds a new feature -- value tags and that can then be used by plugins and new reports to do what we want for fund accounting.","title":"Ideas for Implementation"},{"location":"fund_accounting_with_beancount.html#examples","text":"(Carl) Here is an example of how I might try to handle things associated with my paycheck, which involves deferred compensation (403(b) -- extremely similar to a 401(k)) and a Flexible Spending Account (somewhat similar to an HSA which has been discussed previously on the ledger-cli group).","title":"Examples"},{"location":"fund_accounting_with_beancount.html#without-funds","text":"(Carl) First, without Funds (this passes a bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions in the absence of Funds. One problem is that it distributes Gross Income directly into the 403b and FSA accounts even though it recognizes that the health insurance contribution is a reduction in salary which the 403b and FSA ought to be as well. So tracking contributions to both of those is made more difficult as well as tracking taxable income. By thinking hard we could fix this -- we would come up with Income and Expense type accounts to represent the contributions, but they would end up looking kind of silly (in my opinion) because they are entirely internal to the accounts system. See the next example for how it would look using Funds. If you stick out your tongue, rub your tummy and stand on your head you will see that the Funds-based solution is equivalent to what we would have come up with in the paragraph above in terms of the complexity of its transactions -- just as many lines are required. The advantage is primarily a mental one -- it is much easier to see what to do to be both consistent and useful. option \u201ctitle\u201d \u201cPaystub - no funds\u201d 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Assets:Deferred:R-403b 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open Assets:FSA ; This way of setting up an FSA looks pretty good. It recognizes the ; rule that the designated amount for the year is immediately available ; (in the Asset account), and that we are obliged to make contributions ; to fund it over the course of the year (the Liability account). 2014-01-01 open Liabilities:FSA 2014-01-01 ! \"Set up FSA for 2014\" Assets:FSA 2000 USD Liabilities:FSA -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Assets:Deferred:R-403b 600 USD Liabilities:FSA 75 USD Expenses:SalReduction:HealthInsurance 90 USD Income:EmplContrib:Emp1:Retirement -600 USD Assets:Deferred:R-403b 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" Expenses:Medical 25 USD Assets:FSA 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Assets:FSA","title":"Without Funds"},{"location":"fund_accounting_with_beancount.html#using-funds","text":"(Carl) And now using Funds (uses proposed features and hence can\u2019t be checked by bean-check): This is how I might set up my pay stub with health insurance, an FSA and 403b contributions using Funds. I can straightforwardly arrange things so that contributions to the FSA and 403b are recognized as salary reductions for income tax purposes. And I can easily see how much I have contributed to the 403b and how much my employer has contributed. See the previous example for how it would look without using Funds and which is not as accurate. What this does NOT do is track taxes ultimately owed on the 403b money. I think that is a matter of one's decision about whether to do cash-basis or accrual-basis accounting. If cash basis those taxes are not a current liability and cannot be reported as such. If accrual basis, they are a current liability and need to be recorded as such when the income is booked. For cash-basis books, we'd want the ability to report the state of affairs as if taxes were owed, but that is a matter for reporting rather than booking. We need to make sure we have enough identifiable information to automate creating those reports. I believe that taking a Fund-based accounting perspective easily does this. A problem not solved: what if your basis is different for Federal and State purposes, or even for Federal and multiple different states. Yikes! I've used the convention that the Fund name precedes the root account name. Note that with appropriate standing on one's head along with pivoting rules you can put the Fund name anywhere. Putting it first emphasizes that it identifies a set of accounts that must balance, and it makes it easy for the txn processor to guarantee this property. Accounts without a fund name in front belong to the Fund whose name is the empty string. option \"title\" \"Paystub - no Funds\" 2014-07-15 open Assets:Bank:Checking 2014-07-15 open Assets:CreditUnion:Saving 2014-07-15 open Assets:FedIncTaxDeposits 2014-07-15 open Expenses:OASI 2014-07-15 open Expenses:Medicare 2014-07-15 open Expenses:MedicalAid 2014-07-15 open Expenses:SalReduction:HealthInsurance 2014-07-15 open Expenses:SalReduction:FSA 2014-07-15 open Expenses:SalReduction:R-403b 2014-07-15 open Income:Gross:Emp1 2014-07-15 open Income:EmplContrib:Emp1:Retirement 2014-01-01 open FSA:Assets ; FSA fund accounts 2014-01-01 open FSA:Income:Contributions 2014-01-01 open FSA:Expenses:Medical 2014-01-01 open FSA:Expenses:ReimburseMedical 2014-01-01 open FSA:Liabilities 2014-07-15 open Retirement403b:Assets:CREF ; Retirement fund accounts 2014-07-15 open Retirement403b:Income:EmployeeContrib 2014-07-15 open Retirement403b:Income:EmployerContrib 2014-07-15 open Retirement403b:Income:EarningsGainsAndLosses ; This implements the same idea as above for the FSA, of balancing ; Assets and Liabilities at the opening, but now does it using a ; separate Fund. 2014-01-01 ! \"Set up FSA for 2014\" FSA:Assets 2000 USD FSA:Liabilities -2000 USD 2014-07-15 ! \"Emp1 Paystub\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Expenses:SalReduction:R-403b 600 USD Retirement403b:Income:EmployeeContrib -600 USD Retirement403b:Assets:CREF 600 USD Expenses:SalReduction:FSA 75 USD FSA:Income:Contributions -75 USD FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF 2014-01-01 open Expenses:Medical 2014-07-20 ! \"Direct expense from FSA\" FSA:Expenses:Medical 25 USD FSA:Assets 2014-07-20 ! \"Medical expense from checking\" Expenses:Medical 25 USD Assets:Bank:Checking 2014-07-20 ! \"Medical expense reimbursed from FSA\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets -25 USD FSA:Expenses:ReimburseMedical","title":"Using Funds"},{"location":"fund_accounting_with_beancount.html#transfer-accounts-proposal","text":"By Carl Hauser One problem that I\u2019ve experienced using the Fund approach is that it\u2019s a bit too easy to make mistakes when transferring money between funds, such as in the very last transaction above. Formalizing the idea of Transfer accounts can help with this. The most common mistake is to end up with something that moves assets in both accounts in the same direction -- both up or both down as in this mistaken version of the transaction in question: 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Income:ReimburseMedical FSA:Assets 25 USD FSA:Expenses:ReimburseMedical This balances but isn\u2019t what we intended. Suppose we add the idea of Transfer accounts. They live at the same place in the hierarchy as Income and Expenses and like those are non-balance-sheet accounts. But they really come into play only for transactions that involve multiple funds. There is an additional rule for transactions containing Transfer accounts: the sum of the transfers must also be zero (additional in the sense that the rule about transactions balancing within each fund is still there). So to use this we set things up a little differently: 2014-07-15 open FSA:Transfer:Incoming:Contribution 2014-07-15 open FSA:Transfer:Outgoing:ReimburseMedical 2014-07-15 open Transfer:Outgoing:FSAContribution 2014-07-15 open Transfer:Incoming:ReimburseMedical The incorrect transaction is now flagged because sum of the transfers is -50 USD , not zero. 2014-07-20 ! \"Medical expense reimbursed from FSA - with mistake\" Assets:Bank:Checking 25 USD Transfer:Incoming:ReimburseMedical FSA:Assets 25 USD FSA:Transfer:Outgoing:ReimburseMedical The paycheck transaction using transfer accounts for the FSA and the retirement account amounts might look like this (after appropriate open s of course): 2014-07-15 ! \"Emp1 Paystub - using transfer accounts\" Income:Gross:Emp1 -6000 USD Assets:Bank:Checking 3000 USD Assets:CreditUnion:Saving 1000 USD Assets:FedIncTaxDeposits 750 USD Expenses:OASI 375 USD Expenses:Medicare 100 USD Expenses:MedicalAid 10 USD Transfer:Outgoing:Retirement403b:SalReduction 600 USD Retirement403b:Transfer:Incoming:EmployeeContrib Retirement403b:Assets:CREF 600 USD Transfer:Outgoing:FSA:SalReduction 75 USD FSA:Transfer:Incoming:Contributions FSA:Liabilities Expenses:SalReduction:HealthInsurance Retirement403b:Income:EmployerContrib -600 USD Retirement403b:Assets:CREF Some might think that this is too complicated. Without changing the Transfer accounts idea or rule, you can simplify booking to just a single account per fund, Fund:Transfer , losing some ability for precision in reporting but without losing the ability to check correctness of transfer transactions.","title":"Transfer Accounts Proposal"},{"location":"fund_accounting_with_beancount.html#account-aliases","text":"Simon Michael mentions that this is related to HLedger account aliases : \u201cI think this is related to the situation where you want to view entities' finances both separately and merged. Account aliases can be another way to approximate this, as in http://hledger.org/how-to-use-account-aliases .\u201d If you find yourself culturally challenged by our modern lifestyle, perhaps you can imagine the case of roommates, although I don\u2019t like the reductionist view this association brings to my mind. \u21a9","title":"Account Aliases"},{"location":"getting_started_with_beancount.html","text":"Getting Started with Beancount \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/getting-started Introduction \uf0c1 This document is a gentle guide to creating your first Beancount file, initializing it with some options, some guidelines for how to organize your file, and instructions for declaring accounts and making sure their initial balance does not raise errors. It also contains some material on configuring the Emacs text editor, if you use that. You will probably want to have read some of the User\u2019s Manual first in order to familiarize yourself with the syntax and kinds of available directives, or move on to the Cookbook if you\u2019ve already setup a file or know how to do that. If you\u2019re familiar with Ledger, you may want to read up on the differences between Ledger and Beancount first. Editor Support \uf0c1 Beancount ledgers are simple text files. You can use any text editor to compose your input file. However, a good text editor which understands enough of the Beancount syntax to offer focused facilities like syntax highlighting, autocompletion, and automatic indentation highly has the potential to greatly increase productivity in compiling and maintaining your ledger. Emacs \uf0c1 Support for editing Beancount ledger files in Emacs was traditionally distributed with Beancount. It now lives as its own project in this Github repository . Vim \uf0c1 Support for editing Beancount ledger files in Vim has been implemented by Nathan Grigg and is available in this Github repository . Sublime \uf0c1 Support for editing with Sublime has been contributed by Martin Andreas Andersen and is available in this github repository or as a Sublime package here . VSCode \uf0c1 There are a number of plugins for working with Beancount text files including VSCode-Beancount by Lencerf. Creating your First Input File \uf0c1 To get started, let\u2019s create a minimal input file with two accounts and a single transaction. Enter or copy the following input to a text file: 2014-01-01 open Assets:Checking 2014-01-01 open Equity:Opening-Balances 2014-01-02 * \"Deposit\" Assets:Checking 100.00 USD Equity:Opening-Balances Brief Syntax Overview \uf0c1 A few notes and an ultra brief overview of the Beancount syntax: Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Each component of an account name must begin with a capital letter or number. Description strings must be quoted, like this: \"AMEX PMNT\" . Dates are only parsed in ISO8601 format, that is, YYYY-MM-DD. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. For a complete description of the syntax, visit the User\u2019s Manual . Validating your File \uf0c1 The purpose of Beancount is to produce reports from your input file (either on the console or serve via its web interface). However, there is a tool that you can use to simply load its contents and make some validation checks on it, to ensure that your input does not contain errors. Beancount can be quite strict; this is a tool that you use while you\u2019re entering your data to ensure that your input file is legal. The tool is called \u201cbean-check\u201d and you invoke it like this: bean-check /path/to/your/file.beancount Try it now on the file you just created from the previous section. It should exit with no output. If there are errors, they will be printed on the console. The errors are printed out in a format that Emacs recognizes by default, so you can use Emacs\u2019 next-error and previous-error built-in functions to move the cursor to the location of the problem. Viewing the Web Interface \uf0c1 A convenient way to view reports is to bring up the \u201cbean-web\u201d tool on your input file. Try it: bean-web /path/to/your/file.beancount You can then point a web browser to http://localhost:8080 and click your way around the various reports generated by Beancount. You can then modify the input file and reload the web page your browser is pointing to\u2014bean-web will automatically reload the file contents. At this point, you should probably read some of the Language Syntax document. How to Organize your File \uf0c1 In this section we provide general guidelines for how to organize your file. This assumes you\u2019ve read the Language Syntax document. Preamble to your Input File \uf0c1 I recommend that you begin with just a single file 1 . My file has a header that tells Emacs what mode to open the file with, followed by some common options: ;; -*- mode: beancount; coding: utf-8; fill-column: 400; -*- option \"title\" \"My Personal Ledger\" option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" The title option is used in reports. The list of \u201coperating currencies\u201d identify those commodities which you use most commonly as \u201ccurrencies\u201d and which warrant rendering in their own dedicated columns in reports (this declaration has no other effect on the behavior of any of the calculations). Sections & Declaring Accounts \uf0c1 I like to organize my input file in sections that correspond to each real-world account. Each section defines all the accounts related to this real-world account by using an Open directive. For example, this is a checking account: 2007-02-01 open Assets:US:BofA:Savings USD 2007-02-01 open Income:US:BofA:Savings:Interest USD I like to declare the currency constraints as much as possible, to avoid mistakes. Also, note how I declare an income account specific to this account. This helps break down income in reporting for taxes, as you will likely receive a tax document in relation to that specific account\u2019s income (in the US this would be a 1099-INT form produced by your bank). Here\u2019s what the opening accounts might look like for an investment account: 2012-03-01 open Assets:US:Etrade:Main:Cash USD 2012-03-01 open Assets:US:Etrade:Main:ITOT ITOT 2012-03-01 open Assets:US:Etrade:Main:IXUS IXUS 2012-03-01 open Assets:US:Etrade:Main:IEFA IEFA 2012-03-01 open Income:US:Etrade:Main:Interest USD 2012-03-01 open Income:US:Etrade:Main:PnL USD 2012-03-01 open Income:US:Etrade:Main:Dividend USD 2012-03-01 open Income:US:Etrade:Main:DividendNoTax USD The point is that all these accounts are related somehow. The various sections of the cookbook will describe the set of accounts suggested to create for each section. Not all sections have to be that way. For example, I have the following sections: Eternal accounts. I have a section at the top dedicated to contain special and \u201ceternal\u201d accounts, such as payables and receivables. Daybook. I have a \u201cdaybook\u201d section at the bottom that contains all cash expenses, in chronological order. Expense accounts. All my expenses accounts (categories) are defined in their own section. Employers. For each employer I\u2019ve defined a section where I put the entries for their direct deposits, and track vacations, stock vesting and other job-related transactions. Taxes. I have a section for taxes, organized by taxation year. You can organize it any way you like, because Beancount doesn\u2019t care about the ordering of declarations. Closing Accounts \uf0c1 If a real-world account has closed, or is never going to have any more transactions posted to it, you can declare it \u201cclosed\u201d at a particular date by using a Close directive: ; Moving to another bank. 2013-06-13 close Assets:US:BofA:Savings This tells Beancount not to show the account in reports that don\u2019t include any date where it was active. It also avoids errors by triggering an error if you do try to post to it at a later date. De-duping \uf0c1 One problem that will occur frequently is that once you have some sort of code or process set up to automatically extract postings from downloaded files, you will end up importing postings which provide two separate sides of the same transaction. An example is the payment of a credit card balance via a transfer from a checking account. If you download the transactions for your checking account, you will extract something like this: 2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" Assets:CA:BofA:Checking -923.24 USD The credit card download will yield you this: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Many times, transactions from these accounts need to be booked to an expense account, but in this case, these are two separate legs of the same transaction: a transfer. When you import one of these, you normally look for the other side and merge them together: ;2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Assets:CA:BofA:Checking -923.24 USD I often leave one of the description lines in comments\u2014just my choice, Beancount ignores it. Also note that I had to choose one of the two dates. I just choose the one I prefer, as long as it does not break any balance assertion. In the case that you would forget to merge those two imported transactions, worry not! That\u2019s what balance assertions are for. Regularly place a balance assertion in either of these accounts, e.g., every time you import, and you will get a nice error if you end up entering the transaction twice. This is pretty common and after a while it becomes second nature to interpret that compiler error and fix it in seconds. Finally, when I know I import just one side of these, I select the other account manually and I mark the posting I know will be imported later with a flag, which tells me I haven\u2019t de-duped this transaction yet: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD ! Assets:CA:BofA:Checking Later on, when I import the checking account\u2019s transactions and go fishing for the other side of this payment, I will find this and get a good feeling that the world is operating as it should. (If you\u2019re interested in more of a discussion around de-duplicating and merging transactions, see this feature proposal . Also, you might be interested in the \u201ceffective_date\u201d plugin external contribution, which splits transactions in two.) Which Side? \uf0c1 So if you organize your account in sections the way I suggest above, which section of the file should you leave such \u201cmerged\u201d transactions in, that is, transactions that involve two separate accounts? Well, it\u2019s your call. For example, in the case of a transfer between two accounts organized such that they have their own dedicated sections, it would be nice to be able to leave both transactions there so that when you edit your input file you see them in either section, but unfortunately, the transaction must occur in only one place in your document. You have to choose one. Personally I\u2019m a little careless about being consistent which of the section I choose to leave the transaction in; sometimes I choose one section of my input file, or that of the other account, for the same pair of accounts. It hasn\u2019t been a problem, as I use Emacs and i-search liberally which makes it easy to dig around my gigantic input file. If you want to keep your input more tidy and organized, you could come up with a rule for yourself, e.g. \u201ccredit card payments are always left in the paying account\u2019s section, not in the credit card account\u2019s section\u201d, or perhaps you could leave the transaction in both sections and comment one out 2 . Padding \uf0c1 If you\u2019re just starting out\u2014and you probably are if you\u2019re reading this\u2014you will have no historical data. This means that the balances of your Assets and Liabilities accounts in Beancount will all be zero. But the first thing you should want to do after defining some accounts is establish a balance sheet and bring those amounts to their actual current value. Let\u2019s take your checking account as an example, say you opened it a while back. You don\u2019t remember exactly when, so let\u2019s use an approximate date: 2000-05-28 open Assets:CA:BofA:Checking USD The next thing you do is look up your current balance and put a balance assertion for the corresponding amount: 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Running Beancount on just this will correctly produce an error because Beancount assumes an implicit balance assertion of \u201cempty\u201d at the time you open an account. You will have to \u201cpad\u201d your account to today\u2019s balance by inserting a balance adjustment at some point in time between the opening and the balance, against some equity account, which is an arbitrary place to book \u201cwhere you received the initial balance from.\u201d For this purpose, this is usually the \u201c Equity:Opening-Balances \u201d account. So let\u2019s include this padding transaction and recap what we have so far: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD From here onwards, you would start adding entries reflecting everything that happened after 7/1. However, what if you wanted to go back in time? It is perfectly reasonable that once you\u2019ve got your chart-of-accounts set up you might want to fill in the missing history until at least the beginning of this year. Let\u2019s assume you had a single transaction in June 2014, and let\u2019s add it: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now the balance assertion fails! You would need to adjust the initialization entry to fix this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1956.35 USD Assets:CA:BofA:Checking 1956.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now this works. So basically, every single time you insert an entry in the past, you would have to adjust the balance. Isn\u2019t this annoying? Well, yes. Fortunately, we can provide some help: you can use a Pad directive to replace and automatically synthesize the balance adjustment to match the next balance check, like this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 pad Assets:CA:BofA:Checking Equity:Opening-Balances 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Note that this is only needed for balance sheet accounts (Assets and Liabilities) because we don\u2019t care about the initial balances of the Income and Expenses accounts, we only care about their transitional value (the changes they post during a period). For example, it makes no sense to bring up the Expenses:Restaurant account to the sum total value of all the costs of the meals you consumed since you were born. So you will probably want to get started with Open & Pad directives for each Assets and Liabilities accounts. What\u2019s Next? \uf0c1 At this point you will probably move onwards to the Cookbook , or read the User\u2019s Manual if you haven\u2019t already done that. It is tempting to want to break down a large file into many smaller ones, but especially at first, the convenience of having everything in a single place is great. \u21a9 Some people have suggested that Beancount automatically detect duplicated transactions based on a heuristic and automatically ignore (remove) one of the two, but this has not been tried out yet. In particular, this would lend itself well to organizing transactions not just per section, but in separate files, i.e., all files would contain all the transactions for the accounts they represent. If you\u2019re interested in adding this feature, you could easily implement this as a plugin, without disrupting the rest of the system. \u21a9","title":"Getting Started with Beancount"},{"location":"getting_started_with_beancount.html#getting-started-with-beancount","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/getting-started","title":"Getting Started with Beancount"},{"location":"getting_started_with_beancount.html#introduction","text":"This document is a gentle guide to creating your first Beancount file, initializing it with some options, some guidelines for how to organize your file, and instructions for declaring accounts and making sure their initial balance does not raise errors. It also contains some material on configuring the Emacs text editor, if you use that. You will probably want to have read some of the User\u2019s Manual first in order to familiarize yourself with the syntax and kinds of available directives, or move on to the Cookbook if you\u2019ve already setup a file or know how to do that. If you\u2019re familiar with Ledger, you may want to read up on the differences between Ledger and Beancount first.","title":"Introduction"},{"location":"getting_started_with_beancount.html#editor-support","text":"Beancount ledgers are simple text files. You can use any text editor to compose your input file. However, a good text editor which understands enough of the Beancount syntax to offer focused facilities like syntax highlighting, autocompletion, and automatic indentation highly has the potential to greatly increase productivity in compiling and maintaining your ledger.","title":"Editor Support"},{"location":"getting_started_with_beancount.html#emacs","text":"Support for editing Beancount ledger files in Emacs was traditionally distributed with Beancount. It now lives as its own project in this Github repository .","title":"Emacs"},{"location":"getting_started_with_beancount.html#vim","text":"Support for editing Beancount ledger files in Vim has been implemented by Nathan Grigg and is available in this Github repository .","title":"Vim"},{"location":"getting_started_with_beancount.html#sublime","text":"Support for editing with Sublime has been contributed by Martin Andreas Andersen and is available in this github repository or as a Sublime package here .","title":"Sublime"},{"location":"getting_started_with_beancount.html#vscode","text":"There are a number of plugins for working with Beancount text files including VSCode-Beancount by Lencerf.","title":"VSCode"},{"location":"getting_started_with_beancount.html#creating-your-first-input-file","text":"To get started, let\u2019s create a minimal input file with two accounts and a single transaction. Enter or copy the following input to a text file: 2014-01-01 open Assets:Checking 2014-01-01 open Equity:Opening-Balances 2014-01-02 * \"Deposit\" Assets:Checking 100.00 USD Equity:Opening-Balances","title":"Creating your First Input File"},{"location":"getting_started_with_beancount.html#brief-syntax-overview","text":"A few notes and an ultra brief overview of the Beancount syntax: Currencies must be entirely in capital letters (allowing numbers and some special characters, like \u201c_\u201d or \u201c-\u201d). Currency symbols (such as $ or \u20ac) are not supported. Account names do not admit spaces (though you can use dashes), and must have at least two components, separated by colons. Each component of an account name must begin with a capital letter or number. Description strings must be quoted, like this: \"AMEX PMNT\" . Dates are only parsed in ISO8601 format, that is, YYYY-MM-DD. Tags must begin with \u201c#\u201d, and links with \u201c^\u201d. For a complete description of the syntax, visit the User\u2019s Manual .","title":"Brief Syntax Overview"},{"location":"getting_started_with_beancount.html#validating-your-file","text":"The purpose of Beancount is to produce reports from your input file (either on the console or serve via its web interface). However, there is a tool that you can use to simply load its contents and make some validation checks on it, to ensure that your input does not contain errors. Beancount can be quite strict; this is a tool that you use while you\u2019re entering your data to ensure that your input file is legal. The tool is called \u201cbean-check\u201d and you invoke it like this: bean-check /path/to/your/file.beancount Try it now on the file you just created from the previous section. It should exit with no output. If there are errors, they will be printed on the console. The errors are printed out in a format that Emacs recognizes by default, so you can use Emacs\u2019 next-error and previous-error built-in functions to move the cursor to the location of the problem.","title":"Validating your File"},{"location":"getting_started_with_beancount.html#viewing-the-web-interface","text":"A convenient way to view reports is to bring up the \u201cbean-web\u201d tool on your input file. Try it: bean-web /path/to/your/file.beancount You can then point a web browser to http://localhost:8080 and click your way around the various reports generated by Beancount. You can then modify the input file and reload the web page your browser is pointing to\u2014bean-web will automatically reload the file contents. At this point, you should probably read some of the Language Syntax document.","title":"Viewing the Web Interface"},{"location":"getting_started_with_beancount.html#how-to-organize-your-file","text":"In this section we provide general guidelines for how to organize your file. This assumes you\u2019ve read the Language Syntax document.","title":"How to Organize your File"},{"location":"getting_started_with_beancount.html#preamble-to-your-input-file","text":"I recommend that you begin with just a single file 1 . My file has a header that tells Emacs what mode to open the file with, followed by some common options: ;; -*- mode: beancount; coding: utf-8; fill-column: 400; -*- option \"title\" \"My Personal Ledger\" option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" The title option is used in reports. The list of \u201coperating currencies\u201d identify those commodities which you use most commonly as \u201ccurrencies\u201d and which warrant rendering in their own dedicated columns in reports (this declaration has no other effect on the behavior of any of the calculations).","title":"Preamble to your Input File"},{"location":"getting_started_with_beancount.html#sections-declaring-accounts","text":"I like to organize my input file in sections that correspond to each real-world account. Each section defines all the accounts related to this real-world account by using an Open directive. For example, this is a checking account: 2007-02-01 open Assets:US:BofA:Savings USD 2007-02-01 open Income:US:BofA:Savings:Interest USD I like to declare the currency constraints as much as possible, to avoid mistakes. Also, note how I declare an income account specific to this account. This helps break down income in reporting for taxes, as you will likely receive a tax document in relation to that specific account\u2019s income (in the US this would be a 1099-INT form produced by your bank). Here\u2019s what the opening accounts might look like for an investment account: 2012-03-01 open Assets:US:Etrade:Main:Cash USD 2012-03-01 open Assets:US:Etrade:Main:ITOT ITOT 2012-03-01 open Assets:US:Etrade:Main:IXUS IXUS 2012-03-01 open Assets:US:Etrade:Main:IEFA IEFA 2012-03-01 open Income:US:Etrade:Main:Interest USD 2012-03-01 open Income:US:Etrade:Main:PnL USD 2012-03-01 open Income:US:Etrade:Main:Dividend USD 2012-03-01 open Income:US:Etrade:Main:DividendNoTax USD The point is that all these accounts are related somehow. The various sections of the cookbook will describe the set of accounts suggested to create for each section. Not all sections have to be that way. For example, I have the following sections: Eternal accounts. I have a section at the top dedicated to contain special and \u201ceternal\u201d accounts, such as payables and receivables. Daybook. I have a \u201cdaybook\u201d section at the bottom that contains all cash expenses, in chronological order. Expense accounts. All my expenses accounts (categories) are defined in their own section. Employers. For each employer I\u2019ve defined a section where I put the entries for their direct deposits, and track vacations, stock vesting and other job-related transactions. Taxes. I have a section for taxes, organized by taxation year. You can organize it any way you like, because Beancount doesn\u2019t care about the ordering of declarations.","title":"Sections & Declaring Accounts"},{"location":"getting_started_with_beancount.html#closing-accounts","text":"If a real-world account has closed, or is never going to have any more transactions posted to it, you can declare it \u201cclosed\u201d at a particular date by using a Close directive: ; Moving to another bank. 2013-06-13 close Assets:US:BofA:Savings This tells Beancount not to show the account in reports that don\u2019t include any date where it was active. It also avoids errors by triggering an error if you do try to post to it at a later date.","title":"Closing Accounts"},{"location":"getting_started_with_beancount.html#de-duping","text":"One problem that will occur frequently is that once you have some sort of code or process set up to automatically extract postings from downloaded files, you will end up importing postings which provide two separate sides of the same transaction. An example is the payment of a credit card balance via a transfer from a checking account. If you download the transactions for your checking account, you will extract something like this: 2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" Assets:CA:BofA:Checking -923.24 USD The credit card download will yield you this: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Many times, transactions from these accounts need to be booked to an expense account, but in this case, these are two separate legs of the same transaction: a transfer. When you import one of these, you normally look for the other side and merge them together: ;2014-06-08 * \"ONLINE PAYMENT - THANK YOU\" 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD Assets:CA:BofA:Checking -923.24 USD I often leave one of the description lines in comments\u2014just my choice, Beancount ignores it. Also note that I had to choose one of the two dates. I just choose the one I prefer, as long as it does not break any balance assertion. In the case that you would forget to merge those two imported transactions, worry not! That\u2019s what balance assertions are for. Regularly place a balance assertion in either of these accounts, e.g., every time you import, and you will get a nice error if you end up entering the transaction twice. This is pretty common and after a while it becomes second nature to interpret that compiler error and fix it in seconds. Finally, when I know I import just one side of these, I select the other account manually and I mark the posting I know will be imported later with a flag, which tells me I haven\u2019t de-duped this transaction yet: 2014-06-10 * \"AMEX EPAYMENT ACH PMT\" Liabilities:US:Amex:Platinum 923.24 USD ! Assets:CA:BofA:Checking Later on, when I import the checking account\u2019s transactions and go fishing for the other side of this payment, I will find this and get a good feeling that the world is operating as it should. (If you\u2019re interested in more of a discussion around de-duplicating and merging transactions, see this feature proposal . Also, you might be interested in the \u201ceffective_date\u201d plugin external contribution, which splits transactions in two.)","title":"De-duping"},{"location":"getting_started_with_beancount.html#which-side","text":"So if you organize your account in sections the way I suggest above, which section of the file should you leave such \u201cmerged\u201d transactions in, that is, transactions that involve two separate accounts? Well, it\u2019s your call. For example, in the case of a transfer between two accounts organized such that they have their own dedicated sections, it would be nice to be able to leave both transactions there so that when you edit your input file you see them in either section, but unfortunately, the transaction must occur in only one place in your document. You have to choose one. Personally I\u2019m a little careless about being consistent which of the section I choose to leave the transaction in; sometimes I choose one section of my input file, or that of the other account, for the same pair of accounts. It hasn\u2019t been a problem, as I use Emacs and i-search liberally which makes it easy to dig around my gigantic input file. If you want to keep your input more tidy and organized, you could come up with a rule for yourself, e.g. \u201ccredit card payments are always left in the paying account\u2019s section, not in the credit card account\u2019s section\u201d, or perhaps you could leave the transaction in both sections and comment one out 2 .","title":"Which Side?"},{"location":"getting_started_with_beancount.html#padding","text":"If you\u2019re just starting out\u2014and you probably are if you\u2019re reading this\u2014you will have no historical data. This means that the balances of your Assets and Liabilities accounts in Beancount will all be zero. But the first thing you should want to do after defining some accounts is establish a balance sheet and bring those amounts to their actual current value. Let\u2019s take your checking account as an example, say you opened it a while back. You don\u2019t remember exactly when, so let\u2019s use an approximate date: 2000-05-28 open Assets:CA:BofA:Checking USD The next thing you do is look up your current balance and put a balance assertion for the corresponding amount: 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Running Beancount on just this will correctly produce an error because Beancount assumes an implicit balance assertion of \u201cempty\u201d at the time you open an account. You will have to \u201cpad\u201d your account to today\u2019s balance by inserting a balance adjustment at some point in time between the opening and the balance, against some equity account, which is an arbitrary place to book \u201cwhere you received the initial balance from.\u201d For this purpose, this is usually the \u201c Equity:Opening-Balances \u201d account. So let\u2019s include this padding transaction and recap what we have so far: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD From here onwards, you would start adding entries reflecting everything that happened after 7/1. However, what if you wanted to go back in time? It is perfectly reasonable that once you\u2019ve got your chart-of-accounts set up you might want to fill in the missing history until at least the beginning of this year. Let\u2019s assume you had a single transaction in June 2014, and let\u2019s add it: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1256.35 USD Assets:CA:BofA:Checking 1256.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now the balance assertion fails! You would need to adjust the initialization entry to fix this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 * \"Initialize account\" Equity:Opening-Balances -1956.35 USD Assets:CA:BofA:Checking 1956.35 USD 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Now this works. So basically, every single time you insert an entry in the past, you would have to adjust the balance. Isn\u2019t this annoying? Well, yes. Fortunately, we can provide some help: you can use a Pad directive to replace and automatically synthesize the balance adjustment to match the next balance check, like this: 2000-05-28 open Assets:CA:BofA:Checking USD 2000-05-28 pad Assets:CA:BofA:Checking Equity:Opening-Balances 2014-06-28 * \"Paid credit card bill\" Assets:CA:BofA:Checking -700.00 USD Liabilities:US:Amex:Platinum 700.00 USD 2014-07-01 balance Assets:CA:BofA:Checking 1256.35 USD Note that this is only needed for balance sheet accounts (Assets and Liabilities) because we don\u2019t care about the initial balances of the Income and Expenses accounts, we only care about their transitional value (the changes they post during a period). For example, it makes no sense to bring up the Expenses:Restaurant account to the sum total value of all the costs of the meals you consumed since you were born. So you will probably want to get started with Open & Pad directives for each Assets and Liabilities accounts.","title":"Padding"},{"location":"getting_started_with_beancount.html#whats-next","text":"At this point you will probably move onwards to the Cookbook , or read the User\u2019s Manual if you haven\u2019t already done that. It is tempting to want to break down a large file into many smaller ones, but especially at first, the convenience of having everything in a single place is great. \u21a9 Some people have suggested that Beancount automatically detect duplicated transactions based on a heuristic and automatically ignore (remove) one of the two, but this has not been tried out yet. In particular, this would lend itself well to organizing transactions not just per section, but in separate files, i.e., all files would contain all the transactions for the accounts they represent. If you\u2019re interested in adding this feature, you could easily implement this as a plugin, without disrupting the rest of the system. \u21a9","title":"What\u2019s Next?"},{"location":"health_care_expenses.html","text":"Health Care Expenses \uf0c1 Martin Blais , July 2014 This is incomplete, work-in-progress, not released yet. Accounting for your health care expenses is a little different than regular expenses because of the various maximums imposed by your insurance plan. For the purpose of this section, we will assume a context of privatized health care system as is present in the USA, but the same principles may apply to other countries (if anything, it will be simpler). Accounting With No Insurance Plan - The Naive Way \uf0c1 So what\u2019s so different about health care expenses? You might argue that they should be treated the same as other expenses. Say, we could define a few categories like these: 1973-04-27 open Expenses:Health:Medical 1973-04-27 open Expenses:Health:Dental 1973-04-27 open Expenses:Health:Vision 1973-04-27 open Expenses:Health:Drugs 1973-04-27 open Expenses:Health:Acupuncture 1973-04-27 open Expenses:Health:Massage-Therapy 1973-04-27 open Expenses:Health:Physical-Therapy And simply book outflows of moneys to them when you spend on one of these categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Physical-Therapy 25 USD In fact, this would be work just fine if you paid for all of your health care costs out-of-pocket. If that is your situation, this is what you should do. It\u2019s straightforward. The problem is that in practice, the great majority of people don\u2019t pay for health care costs themselves. The great majority of your health-related costs are paid for by your insurance. This does not mean that there are no costs: your insurance usually only pays for a portion of the actual expenses, depending on the service. In particular, depending on your insurance plan, in any calendar year you pay for 100% of your health care costs up to a fixed amount (usually a few hundred dollars). This is called the deductible amount. Heretofore, you pay a percentage of the service (the copayment amounts ) , and after a higher limit amount (the out-of-pocket maximum amount), you don\u2019t pay anything anymore; plans with such limits guarantee that you will never pay more than this amount. If you booked the amounts that you pay for your deductible, the scale of the expenses that would get reflected on your income statement would depend mostly on which type of service you happened to use first after January 1st. It would not accurately reflect the cost of each service that you use. It would not be that useful. Counting the Cash Payments - The Incorrect Way \uf0c1 So you might argue that you should instead book the same outflows of money to categories that reflect their true nature: 1973-04-27 open Expenses:Health:Medical:Deductible 1973-04-27 open Expenses:Health:Medical:Copayments 1973-04-27 open Expenses:Health:Vision:Deductible 1973-04-27 open Expenses:Health:Vision:Copayments 1973-04-27 open Expenses:Health:Dental:Deductible 1973-04-27 open Expenses:Health:Dental:Copayments Then booking the payments to those categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Copayments 25 USD But this is largely unsatisfying, for a few reasons: while this tracks how much of was paid in total in deductible and copayments, it does not reflect anything about how much of a particular health provider services was used. More importantly, it is incorrect, because deductible and copayment limits apply to each calendar year for dates when the service was provided, not when it was paid for. In other words, if you received the service on December 28th, 2013 but paid for it the year following on January 8th, 2014, the copayment accrual is going towards the wrong year. In order to do this correctly, you must accrue the deductible and copayment amounts on the date of service , which is always provided on the documents your insurance company provides (the Explanation of Benefits , more later about this). The date of service is the date you actually were seen by the provider. Another solution would be to define accounts for each year, similarly to tax accounts (see chapter on Taxes): 2012-01-01 open Expenses:Health:Medical:Y2012:Deductible 2012-01-01 open Expenses:Health:Medical:Y2012:Copayments 2013-01-01 open Expenses:Health:Medical:Y2013:Deductible 2013-01-01 open Expenses:Health:Medical:Y2013:Copayments 2014-01-01 open Expenses:Health:Medical:Y2014:Deductible 2014-01-91 open Expenses:Health:Medical:Y2014:Copayments 2014-01-01 open Expenses:Health:Dental:Y2012:Deductible 2014-01-91 open Expenses:Health:Dental:Y2012:Copayments \u2026 Then when you book your expense, you could use the account corresponding to the year the service was received: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Y2013:Copayments 25 USD This is not a nice solution however, due to the proliferation of accounts that need to get created for each year, and it still does not tell us how much of each service we\u2019ve consumed. We clearly need a better solution. How Health Care Insurance Payments Work \uf0c1 Let\u2019s first review how insurance payments work, as it will be needed to have a basic understanding of claims payments to correctly account for these costs. For each service, your insurance covers a portion of it and you normally pay the difference. The way that this works is that your doctor sends a bill to the insurance company and the insurance company either (a) responds to them directly with payment for the covered part of the service, or (b) the insurance company sends you a check and you are meant to sign it over to your doctor (usually after some annoying reminders for you to do so). Sometimes, but not always, your insurance company sends you a report of the doctor\u2019s claim. This document is called an explanation of benefits or \u201cEOB\u201d, and it details the portion of the service that the insurance paid for, the portion that you\u2019re responsible to pay for, and other amounts. Service providers that have no direct relationship with your insurance company will need this detail. This is a useful document for the purpose of accounting: make sure to keep a copy for yourself 1 . Here is a sample from an EOB, with the important areas highlighted: The example above exhibits the following amounts: Claims/Billed. How much the doctor asked the insurance for the service. Patient Savings. How much the service was reduced as a result of pre-established negotiations with the provider (for in-network providers). Applied to Deductible. How much of this service you need to cover yourself as part of this calendar year\u2019s deductible amount. You need to pay this. Copayments. The portion of the service not covered by the insurance. You have to pay this. Claims Payments. How much the insurance actually sent a check to the provider for. The terminology used on the EOB provided by your insurance company may vary slightly, but you should be able to find corresponding amounts easily. The doctor\u2019s billing dept. then sends you a bill for the uncovered portion 2 and you issue them a payment, by credit card, check or otherwise. Consider this a payable when the bill or EOB arrives at your door. All in all, there are four dates relevant to a claim: Date of Service. That is the date you visited the doctor\u2019s office, the date you received the service. This is the date relevant for accounting for deductibles and copayments. Date of Issue. The date that the claim was processed by the insurance. You may ignore this. Billing Date. The date the provider receive payments for its claim and issue you a bill for the remaining portion. We don\u2019t care much about this either. Date of Payment. The date you made the payment. This will automatically appear on your credit card or checking account statement, or if you paid cash, you need to enter this manually (as you do all cash transactions you care about). The most important date when you refer to any claim is the date of service . If you scan and file your EOBs to documents, it is wise to rename them to include this date in the filename. Provider Networks \uf0c1 In the USA, each medical professional (or \u201cservice provider\u201d) decides whether to maintain an established arrangement with each insurance company, depending on their rates of payment and on how much of a pain they are about paying up bills. When they do, they are considered an \u201cin-network provider\u201d by the insurance company. Otherwise they are considered an \u201cout-of-network provider\u201d and the proportion of the services that the insurance covers is much smaller. That\u2019s the only difference. The list of providers that an insurance has in their network\u2014and how difficult they are about refusing to pay bills\u2014is usually a major consideration in the selection of an insurance plan, for someone who has a choice. But because most employers compete on compensation benefits by paying for their employees\u2019 health care insurance costs, as well as the fact that they are usually able to negotiate better rates from insurance companies than an individual can because they represent a large pool of customers (the employees), the great majority of people with jobs end up choosing their employer\u2019s plan and then try to go to in-network providers. This is not always possible, however: your family doctor may decide to stop accepting your insurance as in-network during the course of the year, and you might prefer to maintain an established relationship with your doctor rather than switch, so you end up having to pay a bit more. The bottom line is that in a typical year, you usually use some services of both in-network and out-of-network professionals and you have to account for both of these costs. Accruing on Service Date - The Correct Way \uf0c1 Ideally, we would like to obtain balances for the following amounts: The amount of money that each service cost, regardless of who paid The amount of deductible and copayments that are used in each calendar year What we will do, is enter two entries: An entry for each EOB, at the date of service, for which we obtain a payable. An entry for each payments, that comes out of an Asset or Liability account. In-Network Providers \uf0c1 For in-network providers a typical entry looks like this: 2013-04-01 * \"DR RAPPOPORT\" \"Office visit\" ^anthem-claim-8765937424 Expenses:Health:Medical:Claims 225.00 USD Expenses:Health:Medical:PatientSavings -151.53 USD Liabilities:US:Accounts-Payable -10.00 USD ; Copay Expenses:Health:Medical:ClaimsPayment -63.47 USD Once you\u2019ve figured out which numbers to pull out of of these EOBs, it becomes simpler to enter others, because they are very regular and all look the same. Note that an in-network EOB will include a \u201cPatient Savings\u201d number. This is meant to reflect how much lower you\u2019re paying due to your insurance plan selection. (In practice, this is a bit of smoke and mirrors, because all the claims made by doctors are inflated to reflect the amount they actually get paid for, but I think the insurance likes to show these to you anyway.) I like to immediately reflect the missing portion to an \u201caccounts payable\u201d liability, which tells me I have this registered as an upcoming payment: 1973-04-27 open Liabilities:US:Accounts-Payable You can then eliminate this payable as the payment entry comes in through your credit card import: 2014-01-08 * \"Rappoport Medical Office\" ^anthem-claim-8765937424 Liabilities:US:BofA:Credit-Card -10.00 USD Liabilities:US:Accounts-Payable 10.00 USD Note how I \u201clink\u201d both of the transactions to each other with \u201c ^anthem-claim-8765937424 \u201d so they can easily be found and verified later on. For in-network doctors, because the payments from the insurance are generally predictable, the doctor\u2019s office will sometimes require the copayment on the same day you visit. Just book this as a cash entry against the same liability account. If you have a doctor that you visit regularly, you may create dedicated Liabilities accounts for them: 2009-09-17 open Liabilities:US:Accounts-Payable:DrRappoport Out-of-Network Providers \uf0c1 For out-of-network providers, the EOBs are slightly different: What we noticed is that there is an \u201cother amount\u201d section, which is essentially what will be written off by the provider, and the Patient Savings section is empty. A typical entry corresponding to this EOB would look like this: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Expenses:Health:PhysicalTherapy:Uncovered -155.52 USD Liabilities:US:Accounts-Payable:CityPT -49.34 USD You will typically receive the check for your service provider\u2019s payment (in the example, that is a check for $115.14) and have to send it over to them yourself. You can either deposit the check right away and make the full payment with your credit card, or send the check and just pay the difference (this is what most people do): 2014-01-08 * \"City PhysioTherapy\" ^anthem-claim-17646398 Liabilities:US:BofA:Credit-Card -49.34 USD Liabilities:US:Accounts-Payable:CityPT 49.34 USD One interesting twist is that many out-of-network providers will accept the lower amount that insurance companies pay for out-of-network services and write off the excess amount, or charge you nominal amount only. For example, the \u201cdeal\u201d with my favorite physical therapy place is that they charge me 25$ per session. Once again, the claim is inflated, of course, and the apparent amount the insurance says you have to pay has to be largely written off. This is how you\u2019d book such an entry\u2019s EOB: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Liabilities:US:Accounts-Payable:CityPT -25.00 USD Expenses:Health:PhysicalTherapy:WriteOffs The provider\u2019s administrator does not charge me on every visit. She accrues the amounts and whenever decides to process her accounting, she makes a charge for the total, which clears the account to zero. This might look like this: 2014-02-26 * \"Payment for 3 Physical Therapy treatments\" Liabilities:US:BofA:CreditCard -75.00 USD Liabilities:US:Accounts-Payable:CityPT Tracking Deductible and Copayment Limits \uf0c1 As we\u2019ve seen previously, there are limits on how much you end up paying for health care services. These limits are applied by calendar year, so they need to be applied at the date of service. For this reasons, we can attach corresponding entries to the EOB entries, and then filter transactions by year to calculate the balances for that year. I like to keep tabs on these limits and ensure that they are applied properly. According to my insurance plan, the limits are defined like this: Deductible. \u201cThe amount you pay out-of-pocket within a calendar year before your health insurance begins to pay for covered service.\u201d Out-of-pocket maximum. \u201cThe maximum amount of money you could pay out-of-pocket in a calendar year for covered health care services. There are exclusions, e.g. for prescription drugs and some services.\u201d Co-payment. \u201cA fixed fee that you pay out-of-pocket for a service. This fee does not vary according to the actual cost of the service.\u201d It is not straightforward, however, because there are distinct limits for in-network and out-of-network providers, both for deductible amounts and copayment amounts: Amounts paid for in-network deductibles count towards your limit for out-of-network deductibles. Amounts paid for in-network copayments count towards your limit for out-of-network copayments. Amounts paid for deductibles count towards your limit for copayments. I carry this out using the \u201cmirror accounting\u201d technique I describe in another document. The idea is to use an alternative currency to count for these amounts. TODO: complete this Insurance Premiums \uf0c1 Then you have to track how much you spend on your insurance premiums. These are the fixed semi-monthly payments you make\u2014usually directly processed out of your paycheck\u2014for getting the insurance policy itself. As for most people with jobs in the US, my employer offers a few health coverage plans and pays for most of it, but there is always a portion I have to pay myself: 1973-04-27 open Expenses:Health:Insurance A fixed premium is automatically deducted from my paycheck entries, as it appears on my pay stub: 2014-02-08 * \"Acme Corp - Salary\" \u2026 Expenses:Health:Insurance 42.45 USD \u2026 On your pay stub, be careful not to confuse this with the \u201cMedicare tax,\u201d which is a tax used to pay for the bit of socialized medicine costs the US has for older people. This is just a tax and has little to do with your own health care expenses. Drugs \uf0c1 Drugs are accounted for separately. TODO: complete this You do not always receive these through the mail, but insurance companies are now finally coming up with websites where you can download all the claims that were made on your behalf (and account for them), even those which weren\u2019t mailed to you. \u21a9 This is a tremendous annoyance because each doctor has a different billing department, they are typically antiquated and their online payment options are almost always broken (you end up having to call a phone number and convince a grumpy administrator to pay by credit card because they are still used to receiving checks ), and all the different service providers that collaborate together to offer you a health service will bill you separately (with different billing departments as well). For example, if you undergo surgery, over the following 6 months you can expect to get different bills from your surgeon, his assistant, the anesthesiologist, the person who monitored your brain activity while asleep, the lab who did the blood work, the person who read an X-ray, and so on. And the bills may be sent more than 6 months after the operation took place, well into the following year. It\u2019s completely Kafkaesque. You have to really wonder why the insurance company does not always pay the full service to the providers and then bill you , just once, for the deductibles. That would make the process a lot simpler and reduce the exhorbitant cost of administering health care. \u21a9","title":"Health Care Expenses"},{"location":"health_care_expenses.html#health-care-expenses","text":"Martin Blais , July 2014 This is incomplete, work-in-progress, not released yet. Accounting for your health care expenses is a little different than regular expenses because of the various maximums imposed by your insurance plan. For the purpose of this section, we will assume a context of privatized health care system as is present in the USA, but the same principles may apply to other countries (if anything, it will be simpler).","title":"Health Care Expenses"},{"location":"health_care_expenses.html#accounting-with-no-insurance-plan-the-naive-way","text":"So what\u2019s so different about health care expenses? You might argue that they should be treated the same as other expenses. Say, we could define a few categories like these: 1973-04-27 open Expenses:Health:Medical 1973-04-27 open Expenses:Health:Dental 1973-04-27 open Expenses:Health:Vision 1973-04-27 open Expenses:Health:Drugs 1973-04-27 open Expenses:Health:Acupuncture 1973-04-27 open Expenses:Health:Massage-Therapy 1973-04-27 open Expenses:Health:Physical-Therapy And simply book outflows of moneys to them when you spend on one of these categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Physical-Therapy 25 USD In fact, this would be work just fine if you paid for all of your health care costs out-of-pocket. If that is your situation, this is what you should do. It\u2019s straightforward. The problem is that in practice, the great majority of people don\u2019t pay for health care costs themselves. The great majority of your health-related costs are paid for by your insurance. This does not mean that there are no costs: your insurance usually only pays for a portion of the actual expenses, depending on the service. In particular, depending on your insurance plan, in any calendar year you pay for 100% of your health care costs up to a fixed amount (usually a few hundred dollars). This is called the deductible amount. Heretofore, you pay a percentage of the service (the copayment amounts ) , and after a higher limit amount (the out-of-pocket maximum amount), you don\u2019t pay anything anymore; plans with such limits guarantee that you will never pay more than this amount. If you booked the amounts that you pay for your deductible, the scale of the expenses that would get reflected on your income statement would depend mostly on which type of service you happened to use first after January 1st. It would not accurately reflect the cost of each service that you use. It would not be that useful.","title":"Accounting With No Insurance Plan - The Naive Way"},{"location":"health_care_expenses.html#counting-the-cash-payments-the-incorrect-way","text":"So you might argue that you should instead book the same outflows of money to categories that reflect their true nature: 1973-04-27 open Expenses:Health:Medical:Deductible 1973-04-27 open Expenses:Health:Medical:Copayments 1973-04-27 open Expenses:Health:Vision:Deductible 1973-04-27 open Expenses:Health:Vision:Copayments 1973-04-27 open Expenses:Health:Dental:Deductible 1973-04-27 open Expenses:Health:Dental:Copayments Then booking the payments to those categories: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Copayments 25 USD But this is largely unsatisfying, for a few reasons: while this tracks how much of was paid in total in deductible and copayments, it does not reflect anything about how much of a particular health provider services was used. More importantly, it is incorrect, because deductible and copayment limits apply to each calendar year for dates when the service was provided, not when it was paid for. In other words, if you received the service on December 28th, 2013 but paid for it the year following on January 8th, 2014, the copayment accrual is going towards the wrong year. In order to do this correctly, you must accrue the deductible and copayment amounts on the date of service , which is always provided on the documents your insurance company provides (the Explanation of Benefits , more later about this). The date of service is the date you actually were seen by the provider. Another solution would be to define accounts for each year, similarly to tax accounts (see chapter on Taxes): 2012-01-01 open Expenses:Health:Medical:Y2012:Deductible 2012-01-01 open Expenses:Health:Medical:Y2012:Copayments 2013-01-01 open Expenses:Health:Medical:Y2013:Deductible 2013-01-01 open Expenses:Health:Medical:Y2013:Copayments 2014-01-01 open Expenses:Health:Medical:Y2014:Deductible 2014-01-91 open Expenses:Health:Medical:Y2014:Copayments 2014-01-01 open Expenses:Health:Dental:Y2012:Deductible 2014-01-91 open Expenses:Health:Dental:Y2012:Copayments \u2026 Then when you book your expense, you could use the account corresponding to the year the service was received: 2014-01-08 * \"CityPT\" \"Copay for PT session w/ Rob on 12/28\" Liabilities:US:BofA:Credit-Card -25 USD Expenses:Health:Medical:Y2013:Copayments 25 USD This is not a nice solution however, due to the proliferation of accounts that need to get created for each year, and it still does not tell us how much of each service we\u2019ve consumed. We clearly need a better solution.","title":"Counting the Cash Payments - The Incorrect Way"},{"location":"health_care_expenses.html#how-health-care-insurance-payments-work","text":"Let\u2019s first review how insurance payments work, as it will be needed to have a basic understanding of claims payments to correctly account for these costs. For each service, your insurance covers a portion of it and you normally pay the difference. The way that this works is that your doctor sends a bill to the insurance company and the insurance company either (a) responds to them directly with payment for the covered part of the service, or (b) the insurance company sends you a check and you are meant to sign it over to your doctor (usually after some annoying reminders for you to do so). Sometimes, but not always, your insurance company sends you a report of the doctor\u2019s claim. This document is called an explanation of benefits or \u201cEOB\u201d, and it details the portion of the service that the insurance paid for, the portion that you\u2019re responsible to pay for, and other amounts. Service providers that have no direct relationship with your insurance company will need this detail. This is a useful document for the purpose of accounting: make sure to keep a copy for yourself 1 . Here is a sample from an EOB, with the important areas highlighted: The example above exhibits the following amounts: Claims/Billed. How much the doctor asked the insurance for the service. Patient Savings. How much the service was reduced as a result of pre-established negotiations with the provider (for in-network providers). Applied to Deductible. How much of this service you need to cover yourself as part of this calendar year\u2019s deductible amount. You need to pay this. Copayments. The portion of the service not covered by the insurance. You have to pay this. Claims Payments. How much the insurance actually sent a check to the provider for. The terminology used on the EOB provided by your insurance company may vary slightly, but you should be able to find corresponding amounts easily. The doctor\u2019s billing dept. then sends you a bill for the uncovered portion 2 and you issue them a payment, by credit card, check or otherwise. Consider this a payable when the bill or EOB arrives at your door. All in all, there are four dates relevant to a claim: Date of Service. That is the date you visited the doctor\u2019s office, the date you received the service. This is the date relevant for accounting for deductibles and copayments. Date of Issue. The date that the claim was processed by the insurance. You may ignore this. Billing Date. The date the provider receive payments for its claim and issue you a bill for the remaining portion. We don\u2019t care much about this either. Date of Payment. The date you made the payment. This will automatically appear on your credit card or checking account statement, or if you paid cash, you need to enter this manually (as you do all cash transactions you care about). The most important date when you refer to any claim is the date of service . If you scan and file your EOBs to documents, it is wise to rename them to include this date in the filename.","title":"How Health Care Insurance Payments Work"},{"location":"health_care_expenses.html#provider-networks","text":"In the USA, each medical professional (or \u201cservice provider\u201d) decides whether to maintain an established arrangement with each insurance company, depending on their rates of payment and on how much of a pain they are about paying up bills. When they do, they are considered an \u201cin-network provider\u201d by the insurance company. Otherwise they are considered an \u201cout-of-network provider\u201d and the proportion of the services that the insurance covers is much smaller. That\u2019s the only difference. The list of providers that an insurance has in their network\u2014and how difficult they are about refusing to pay bills\u2014is usually a major consideration in the selection of an insurance plan, for someone who has a choice. But because most employers compete on compensation benefits by paying for their employees\u2019 health care insurance costs, as well as the fact that they are usually able to negotiate better rates from insurance companies than an individual can because they represent a large pool of customers (the employees), the great majority of people with jobs end up choosing their employer\u2019s plan and then try to go to in-network providers. This is not always possible, however: your family doctor may decide to stop accepting your insurance as in-network during the course of the year, and you might prefer to maintain an established relationship with your doctor rather than switch, so you end up having to pay a bit more. The bottom line is that in a typical year, you usually use some services of both in-network and out-of-network professionals and you have to account for both of these costs.","title":"Provider Networks"},{"location":"health_care_expenses.html#accruing-on-service-date-the-correct-way","text":"Ideally, we would like to obtain balances for the following amounts: The amount of money that each service cost, regardless of who paid The amount of deductible and copayments that are used in each calendar year What we will do, is enter two entries: An entry for each EOB, at the date of service, for which we obtain a payable. An entry for each payments, that comes out of an Asset or Liability account.","title":"Accruing on Service Date - The Correct Way"},{"location":"health_care_expenses.html#in-network-providers","text":"For in-network providers a typical entry looks like this: 2013-04-01 * \"DR RAPPOPORT\" \"Office visit\" ^anthem-claim-8765937424 Expenses:Health:Medical:Claims 225.00 USD Expenses:Health:Medical:PatientSavings -151.53 USD Liabilities:US:Accounts-Payable -10.00 USD ; Copay Expenses:Health:Medical:ClaimsPayment -63.47 USD Once you\u2019ve figured out which numbers to pull out of of these EOBs, it becomes simpler to enter others, because they are very regular and all look the same. Note that an in-network EOB will include a \u201cPatient Savings\u201d number. This is meant to reflect how much lower you\u2019re paying due to your insurance plan selection. (In practice, this is a bit of smoke and mirrors, because all the claims made by doctors are inflated to reflect the amount they actually get paid for, but I think the insurance likes to show these to you anyway.) I like to immediately reflect the missing portion to an \u201caccounts payable\u201d liability, which tells me I have this registered as an upcoming payment: 1973-04-27 open Liabilities:US:Accounts-Payable You can then eliminate this payable as the payment entry comes in through your credit card import: 2014-01-08 * \"Rappoport Medical Office\" ^anthem-claim-8765937424 Liabilities:US:BofA:Credit-Card -10.00 USD Liabilities:US:Accounts-Payable 10.00 USD Note how I \u201clink\u201d both of the transactions to each other with \u201c ^anthem-claim-8765937424 \u201d so they can easily be found and verified later on. For in-network doctors, because the payments from the insurance are generally predictable, the doctor\u2019s office will sometimes require the copayment on the same day you visit. Just book this as a cash entry against the same liability account. If you have a doctor that you visit regularly, you may create dedicated Liabilities accounts for them: 2009-09-17 open Liabilities:US:Accounts-Payable:DrRappoport","title":"In-Network Providers"},{"location":"health_care_expenses.html#out-of-network-providers","text":"For out-of-network providers, the EOBs are slightly different: What we noticed is that there is an \u201cother amount\u201d section, which is essentially what will be written off by the provider, and the Patient Savings section is empty. A typical entry corresponding to this EOB would look like this: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Expenses:Health:PhysicalTherapy:Uncovered -155.52 USD Liabilities:US:Accounts-Payable:CityPT -49.34 USD You will typically receive the check for your service provider\u2019s payment (in the example, that is a check for $115.14) and have to send it over to them yourself. You can either deposit the check right away and make the full payment with your credit card, or send the check and just pay the difference (this is what most people do): 2014-01-08 * \"City PhysioTherapy\" ^anthem-claim-17646398 Liabilities:US:BofA:Credit-Card -49.34 USD Liabilities:US:Accounts-Payable:CityPT 49.34 USD One interesting twist is that many out-of-network providers will accept the lower amount that insurance companies pay for out-of-network services and write off the excess amount, or charge you nominal amount only. For example, the \u201cdeal\u201d with my favorite physical therapy place is that they charge me 25$ per session. Once again, the claim is inflated, of course, and the apparent amount the insurance says you have to pay has to be largely written off. This is how you\u2019d book such an entry\u2019s EOB: 2014-02-13 * \"ROB BOONAN\" | \"Physical Therapy\" ^anthem-claim-17646398 Expenses:Health:PhysicalTherapy:Claims 320.00 USD Expenses:Health:PhysicalTherapy:ClaimsPayment -115.14 USD Liabilities:US:Accounts-Payable:CityPT -25.00 USD Expenses:Health:PhysicalTherapy:WriteOffs The provider\u2019s administrator does not charge me on every visit. She accrues the amounts and whenever decides to process her accounting, she makes a charge for the total, which clears the account to zero. This might look like this: 2014-02-26 * \"Payment for 3 Physical Therapy treatments\" Liabilities:US:BofA:CreditCard -75.00 USD Liabilities:US:Accounts-Payable:CityPT","title":"Out-of-Network Providers"},{"location":"health_care_expenses.html#tracking-deductible-and-copayment-limits","text":"As we\u2019ve seen previously, there are limits on how much you end up paying for health care services. These limits are applied by calendar year, so they need to be applied at the date of service. For this reasons, we can attach corresponding entries to the EOB entries, and then filter transactions by year to calculate the balances for that year. I like to keep tabs on these limits and ensure that they are applied properly. According to my insurance plan, the limits are defined like this: Deductible. \u201cThe amount you pay out-of-pocket within a calendar year before your health insurance begins to pay for covered service.\u201d Out-of-pocket maximum. \u201cThe maximum amount of money you could pay out-of-pocket in a calendar year for covered health care services. There are exclusions, e.g. for prescription drugs and some services.\u201d Co-payment. \u201cA fixed fee that you pay out-of-pocket for a service. This fee does not vary according to the actual cost of the service.\u201d It is not straightforward, however, because there are distinct limits for in-network and out-of-network providers, both for deductible amounts and copayment amounts: Amounts paid for in-network deductibles count towards your limit for out-of-network deductibles. Amounts paid for in-network copayments count towards your limit for out-of-network copayments. Amounts paid for deductibles count towards your limit for copayments. I carry this out using the \u201cmirror accounting\u201d technique I describe in another document. The idea is to use an alternative currency to count for these amounts. TODO: complete this","title":"Tracking Deductible and Copayment Limits"},{"location":"health_care_expenses.html#insurance-premiums","text":"Then you have to track how much you spend on your insurance premiums. These are the fixed semi-monthly payments you make\u2014usually directly processed out of your paycheck\u2014for getting the insurance policy itself. As for most people with jobs in the US, my employer offers a few health coverage plans and pays for most of it, but there is always a portion I have to pay myself: 1973-04-27 open Expenses:Health:Insurance A fixed premium is automatically deducted from my paycheck entries, as it appears on my pay stub: 2014-02-08 * \"Acme Corp - Salary\" \u2026 Expenses:Health:Insurance 42.45 USD \u2026 On your pay stub, be careful not to confuse this with the \u201cMedicare tax,\u201d which is a tax used to pay for the bit of socialized medicine costs the US has for older people. This is just a tax and has little to do with your own health care expenses.","title":"Insurance Premiums"},{"location":"health_care_expenses.html#drugs","text":"Drugs are accounted for separately. TODO: complete this You do not always receive these through the mail, but insurance companies are now finally coming up with websites where you can download all the claims that were made on your behalf (and account for them), even those which weren\u2019t mailed to you. \u21a9 This is a tremendous annoyance because each doctor has a different billing department, they are typically antiquated and their online payment options are almost always broken (you end up having to call a phone number and convince a grumpy administrator to pay by credit card because they are still used to receiving checks ), and all the different service providers that collaborate together to offer you a health service will bill you separately (with different billing departments as well). For example, if you undergo surgery, over the following 6 months you can expect to get different bills from your surgeon, his assistant, the anesthesiologist, the person who monitored your brain activity while asleep, the lab who did the blood work, the person who read an X-ray, and so on. And the bills may be sent more than 6 months after the operation took place, well into the following year. It\u2019s completely Kafkaesque. You have to really wonder why the insurance company does not always pay the full service to the providers and then bill you , just once, for the deductibles. That would make the process a lot simpler and reduce the exhorbitant cost of administering health care. \u21a9","title":"Drugs"},{"location":"how_inventories_work.html","text":"How Inventories Work \uf0c1 Martin Blais , December 2016 http://furius.ca/beancount/doc/booking This document explains how we accumulate commodities and match sales (reductions) against accumulated inventory contents. Introduction \uf0c1 Beyond the ability to track and list the postings made to each of the accounts (an operation that produces a journal of entries), one of the most common and useful operations of Beancount is to sum up the positions of arbitrary sets of postings. These aggregations are at the heart of how Beancount works, and are implemented in an object called \u201cinventory.\u201d This document explains how this aggregation process works. If you\u2019re going to track investments, it\u2019s necessary to understand what follows. Matches & Booking Methods \uf0c1 In order to get the big picture, let\u2019s walk through the various booking features by way of a simple examples. This should expose you to all the main ideas in one go. Simple Postings \u2014 No Cost \uf0c1 Consider a set of simple postings to an account, e.g., 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 221.23 USD \u2026 2016-04-29 * \"ATM Withdrawal\" Assets:Bank:Checking -100.00 USD \u2026 2016-04-29 * \"Debit card payment\" Assets:Bank:Checking -45.67 USD \u2026 The inventory of the Checking account begins empty. After the first transaction, its contents are 221.23 USD. After the second, 121.23 USD. Finally, the third transaction brings this balance to 75.56 USD. This seems very natural; the numbers simply add to. It might be obvious, but note also that the numbers are allowed to change the sign (go negative). Multiple Commodities \uf0c1 An inventory may contain more than one type of commodity. It is equivalent to a mapping from commodity to some number of units. For example, 2016-07-24 * \"Dinner before leaving NYC\" Expenses:Restaurants 34.58 USD \u2026 2016-07-26 * \"Food with friends after landing\" Expenses:Restaurants 62.11 CAD \u2026 After those two transactions, the Restaurants account contains 34.58 USD and 62.11 CAD. Its contents are said to be of mixed commodities. And naturally, postings are applied to just the currencies they affect. For instance, the following transaction 2016-07-27 * \"Brunch\" Expenses:Restaurants 23.91 CAD \u2026 brings the balance of that account to 34.58 USD and 86.02 CAD. The number of units USD hasn\u2019t changed. Note that accounts may contain any number of commodities, and this is also true for commodities held at cost, which we\u2019ll see shortly. While this is made possible, I recommend that you define enough accounts to keep a single commodity in each; this can be enforced with the \u201c onecommodity \u201d plugin. Cost Basis \uf0c1 Things get a little more hairy when we consider the tracking of investments with a cost basis. Beancount allows you to associate a cost basis and an optional label with a particular lot acquired. Consider these two purchases to an investment account: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest 25 HOOL {23.00 USD, \"first-lot\"} \u2026 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest 35 HOOL {27.00 USD} \u2026 So now, the investment account\u2019s inventory contains units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Those two lots were not merged, they are still two distinct positions in the inventory. Inventories merge lots together and adjust the units only if the commodity and all of its cost attributes exactly match. (In practice, it\u2019s pretty rare that two augmentations will have the same cost and date attributes.) Note how Beancount automatically associated the acquisition date to each lot; you can override it if desired, by adding the date similar to the optional label. this is useful for making cost basis adjustments). Postings that add to the content of an inventory are called augmentations . Reductions \uf0c1 But how do we remove commodities from an inventory? You could eat away at an existing lot by selling some of it. You do this by posting a reduction to the account, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {23.00 USD} \u2026 Just to be clear, what makes this posting a reduction is the mere fact that the sign (-) is opposite that of the balance of the account (+25) for that commodity. This posting tells Beancount to find all lots with a cost basis of 23.00 USD and remove 12 units from it. The resulting inventory will be units ccy cost cost-ccy lot-date label 13 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Note how the first posting was reduced by 12 units. We didn\u2019t have to specify all of the lot\u2019s attributes, just the cost. We could have equivalently used the date to specify which lot to reduce: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 Or the label: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {\"first-lot\"} \u2026 Or a combination of these. Any combination of attributes will be matched against the inventory contents to find which lot to reduce. In fact, if the inventory happened to have just a single lot in it, you could reduce it like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Ambiguous Matches \uf0c1 But what happens if multiple lots match the reduction? For example, with the previous inventory containing two lots, if you wrote your sale like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Beancount wouldn\u2019t be able to figure out which lot needs to get reduced. We have an ambiguous match. Partially ambiguous matches are also possible. For example, if you have the following inventory to reduce from: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, None} 30 HOOL {25.00 USD, 2015-04-01, None} 35 HOOL {27.00 USD, 2015-05-01, None} And you attempted to reduce like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 The first two lots are selected as matches. Strict Booking \uf0c1 What does Beancount do with ambiguous matches? By default, it issues an error. More precisely, what happens is that Beancount invokes the booking method and it handles the ambiguous match depending on what it is set. The default booking method is \u201c STRICT \u201d and it just gives up and spits out an error, telling you you need to refine your input in order to disambiguate your inventory reduction. FIFO and LIFO Booking \uf0c1 Other booking methods are available. They can be configured using options, like this: option \"booking_method\" \"FIFO\" The \u201c FIFO \u201d method automatically selects the oldest of the matching lots up to the requested size of the reduction. For example, given our previous inventory: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Attempting to reduce 28 shares like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -28 HOOL {} \u2026 would match both lots, completely reduce the first one to zero units and remove the remaining 3 units from the second lot, to result in the following inventory: units ccy cost cost-ccy lot-date label 32 HOOL {27.00 USD, 2015-05-01, None} The \u201cLIFO\u201d method works similarly, but consumes the youngest (latest) lots first, working its way backward in time to remove the volume. Per-account Booking Method \uf0c1 You don\u2019t have to make all accounts follow the same booking method; the option in the previous section sets the default method for all accounts. In order to override the booking method for a particular account, you can use an optional string option on the account\u2019s Open directive, like this: 2014-01-01 open Assets:Invest \"FIFO\" This allows you to treat different accounts with a different booking resolution. Total Matches \uf0c1 There is an exception to strict booking: if the entire inventory is being reduced by exactly the total number of units, it\u2019s clear that all the matching lots are to be selected and this is considered unambiguous, even under \u201cSTRICT\u201d booking. For example, under \u201cSTRICT\u201d booking, this reduction would empty up the previous inventory without raising an error, because there are 25 + 35 shares matching: 2015-05-15 * \"Sell all my shares\" Assets:Invest -60 HOOL {} \u2026 Average Booking \uf0c1 Retirement accounts created by government incentive programs (such as the 401k plan in the US or the RRSP in Canada) typically consist in pre-tax money. For these types of accounts, brokers usually disregard the calculation of cost basis because the taxation is to be made upon distributing money outside the account. These accounts are often managed to the extent that they are fully invested; therefore, fees are often taken as shares of the investment portfolio, priced on the day the fee is paid out. This makes it awkward to track the cost basis of individual lots. The correct way to deal with this is to treat the number of units and the cost basis of each commodity\u2019s entire set of lots separately. For example, the following two transactions: 2016-07-28 * \"Buy some shares of retirement fund\" Assets:Invest 45.0045 VBMPX {11.11 USD} \u2026 2016-10-12 * \"Buy some shares of retirement fund\" Assets:Invest 54.5951 VBMPX {10.99 USD} \u2026 Should result in a single lot with the total number of units and the averaged cost: units ccy cost cost-ccy lot-date label 99.5996 VBMPX {11.0442 USD, 2016-07-28, None} A fee taken in this account might look like this: 2016-12-30 * \"Quarterly management fee\" Assets:Invest -1.4154 VBMPX {10.59 USD} Expenses:Fees Even with negative units the number and cost get aggregated separately: units ccy cost cost-ccy lot-date label 98.1842 VBMPX {11.0508 USD, 2016-07-28, None} This feature isn\u2019t yet supported in Beancount; it\u2019s fairly tricky to implement, and will be the subject in a minor release in the future. No Booking \uf0c1 However, there is another way to deal with non-taxable accounts in the meantime: you can simply disable the booking. There is a booking method called \u201c NONE \u201d which implements a very liberal strategy which accepts any new lot.. New lots are always appended unconditionally to the inventory. Using this strategy on the transactions from the previous section would result in this inventory: units ccy cost cost-ccy lot-date label 45.0045 VBMPX {11.11 USD, 2016-07-28, None} 54.5951 VBMPX {10.99 USD, 2016-10-12, None} -1.4154 VBMPX {10.59 USD, 2016-12-30, None} Observe how the resulting inventory has a mix of signs; normally this is not allowed, but it is tolerated under this degenerate booking method. Note that under this method, the only meaningful values are the total number of units and the total or average cost amounts. The individual lots aren\u2019t really lots, they only represent the list of all postings made to that account. Note: If you are familiar with Ledger, this is the default and only booking method that it supports. Summary \uf0c1 In summary, here\u2019s what we presented in the walkthrough. Augmentations are never problematic; they always add a new position to an existing inventory. On the other hand, reductions may result in a few outcomes: Single match. Only one position matches the reduction; it is reduced. Total match. The total number of units requested matches the total number of units of the positions matched. These positions are reduced away. No match. None of the positions matches the reducing posting. An error is raised. Ambiguous matches. More than one position in the inventory matches the reducing posting; the booking method is invoked to handle this . There are a few booking methods available to handle the last case: STRICT. An error is raised. FIFO. Units from oldest (earliest) lots are selected until the reduction is complete. LIFO. Units from youngest (latest) lots are selected until the reduction is complete. AVERAGE. After every reduction, all the units of the affected commodity are merged and their new average cost is recalculated. NONE. Booking is disabled; the reducing lots is simply added to the inventory. This results in an inventory with mixed signs and only the total number of units and total cost basis are sensible numbers. Beancount has a default booking method for all accounts, which can be overridden with an option: option \"booking_method\" \"FIFO\" The default value for the booking method is \u201cSTRICT\u201d. I recommend that you leave it that way and override the booking method for specific accounts. The method can be specified for each account by adding a string to its Open directive: 2016-05-01 open Assets:Vanguard:RGAGX \"AVERAGE\" How Prices are Used \uf0c1 The short answer is that prices aren\u2019t used nor affect the booking algorithm at all. However, it is relevant to discuss what they do in this context because users invariably get confused about their interpretation. There are two use cases for prices: making conversions between commodities and tagging a reducing lot with its sale price in order to record it and optionally balance the proceeds. Commodity Conversions \uf0c1 Conversions are used to exchange one currency for another. They look like this: 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 220.00 USD @ 1.3 CAD Income:Payment -286.00 CAD For the purpose of booking it against the Checking account\u2019s inventory, the posting with the price attached to it is treated just the same as if there was no price: the Checking account simply receives a deposit of 220.00 units of USD and will match against positions of commodity \u201cUSD\u201d. The price is used only to verify that the transaction balances and ensure the double-entry accounting rule is respected (220.00 x 1.3 CAD + -286.00 CAD = 0.00). It is otherwise ignored for the purpose of modifying the inventory contents. In a sense, after the postings have been applied to the account inventories, the price is forgotten and the inventory balance retains no memory of the deposit having occurred from a conversion. Price vs. Cost Basis \uf0c1 One might wonder how the price is used if there is a cost basis specification, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest:HOOL -12 HOOL {23.00 USD} @ 24.70 USD Assets:Invest:Cash 296.40 USD Income:Invest:Gains The answer is often surprising to many users: the price is not used by the balancing algorithm if there is a cost basis; the cost basis is the number used to balance the postings. This is a useful property that allows us to compute capital gains automatically. In the previous example, the balance algorithm would sum up -12 x 23.00 + 296.40 = -20.40 USD, which is the capital gain, (24.70 - 23.00) * 12. It would complete the last posting and assign it this value. In general, the way that profits on sales are calculated is by weighing the proceedings, i.e., the cash deposits, against the cost basis of the sold lots, and this is sufficient to establish the gain difference. Also, if an augmenting posting happens to have a price annotation on it, it is also unused. The price is an annotation for your records. It remains attached to the Posting objects and if you want to make use of it somehow, you can always do that by writing some Python code. There are already two plugins which make use of this annotation: beancount.plugins.implicit_prices : This plugin takes the prices attached to the postings and automatically creates and inserts Price directives for each of them, in order to feed the global price database. beancount.plugins.sellgains : This plugin implements an additional balancing check: it uses the prices to compute the expected proceeds and weighs them against all the other postings of the transaction excluding any postings to Income accounts. In our example, it would check that (-12 x 24.70 + 296.40) = 0. This provides yet another means of verifying the correctness of your input. See the Trading with Beancount document for more details on this topic. Trades \uf0c1 The combination of acquiring some asset and selling it back is what we call a \u201ctrade.\u201d In Beancount we consider only assets with a cost basis to be the subject of trades. Since booking reductions against accumulated inventory contents happens during the booking process, this is where trades should be identified and recorded. As of now [Dec 2016], trade recording has not been implemented. Some prototypes for it have been tested previously and I believe it will be very easy to add in the near future. This will be documented here. Watch this space. The way trades will be implemented is by allowing the booking process to insert matching metadata with unique UUIDs on both the augmenting and reducing postings, in the stream of transactions. Functions and reports will be provided that are able to easily extract the pairs of postings for each reducing postings and filter those out in different ways. Ultimately, one should be able to extract a list of all trades to a table, with the acquisition and sale price, as well as other fees. Debugging Booking Issues \uf0c1 If you\u2019re experiencing difficulties in recording your sales due to the matching process, there are tools you can use to view an account\u2019s detailed inventory contents before and after applying a Transaction to it. To do this, you can use the bean-doctor command. You invoke the program providing it with the file and line number close to the Transaction you want to select, like this: bean-doctor context The resulting output will show the list of inventory contents of all affected accounts prior to the transaction being applied, including cost basis, acquisition date, and optional label fully rendered. Note that some failures are typically handled by throwing away an invalid Transaction\u2019s effects (but never quietly). From Emacs or VI, placing the cursor near a transaction and invoking the corresponding command is the easiest way to invoke the command, as it inserts the line number automatically. Appendix \uf0c1 The rest of this document delves into more technical details. You should feel free to ignore this entirely, it\u2019s not necessary reading to understand how Beancount works. Only bother if you\u2019re interested in the details. Data Representation \uf0c1 It is useful to know how positions are represented in an inventory object. A Position is essentially some number of units of a commodity with some optional information about its acquisition: Cost. Its per-unit acquisition cost (the \u201ccost basis\u201d). Date. The date at which the units were acquired. Label. Some user-specified label which can be used to refer to the lot). We often refer to these position objects as \u201clots\u201d or \u201clegs.\u201d Schematically, a position object looks like this: The structure of a Position object. There are two different types of positions, discussed in detail in the sections that follow: Simple positions. These are positions with no cost basis. The \u201ccost\u201d attribute is set to a null value. (\u201c None \u201d in Python.) Positions held at cost. These are positions with an associated cost basis and acquisition details. An Inventory is simply an accumulation of such positions, represented as a list. We sometimes talk of the ante-inventory to refer to the contents of the inventory before a transaction\u2019s postings have been applied to it, and the ex-inventory to the resulting inventory after they have been applied. A Posting is an object which is a superset of a position: in addition to units and cost, it has an associated account and an optional price attributes. If present, the price has the same type as units. It represents one of the legs of a transaction in the input. Postings imply positions, and these positions are added to inventories. We can say that a position is posted to an account. For more details on the internal data structures used in Beancount, please refer to the Design Doc which expands on this topic further. Why Booking is Not Simple \uf0c1 The complexity of the reduction process shows itself when we consider how to keep track of the cost basis of various lots. To demonstrate how this works, let us consider a simple example that we shall reuse in the different sections below: 25 shares are bought on 4/1 at $23/share. 35 shares are bought on 5/1 at $27/share. 30 shares are sold on 5/15; at that time, the price is $26/share. We\u2019ll ignore commissions for now, they don\u2019t introduce any additional complexity. In Beancount, this scenario would be recorded like this: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest:HOOL 25 HOOL {23.00 USD} Assets:Invest:Cash -575.00 USD 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {...} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains Now, the entire question revolves around which of the shares are selected to be sold. I\u2019ve rendered this input as a red ellipsis (\u201c\u2026\u201d). Whatever the user puts in that spot will be used to determine which lot we want to use. Whichever lot(s) we elect to be the ones sold will determine the amount of gains, because that is a function of the cost basis of those shares. This is why this matters. Augmentations vs. Reductions \uf0c1 The most important observation is that there are two distinct kinds of lot specifications which look very similar in the input but which are processed very differently. When we buy, as in the first transaction above, the {...} cost basis syntax provides Beancount with information about a new lot: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD We call this an \u201caugmentation\u201d to the inventory, because it will simply add a new position to it. The cost basis that you provide is attached to this position and preserved through time, in the inventory. In addition, there are a few other pieces of data you can provide for an augmenting lot. Let\u2019s have a look at all the data that can be provided in the cost spec: Cost basis. This consists in per-unit and total cost numbers\u2014which are combined into a single per-unit number\u2014and a currency. Acquisition date. A lot has an acquisition date. By default, the date attached of its parent transaction will be set as its acquisition date automatically. You may override this date by providing one. This comes in handy to handle stock splits or wash sales and preserve the original acquisition date of the replacement shares, as we\u2019ll see later. Label. You can provide a unique label for it, so that you can more easily refer to it later on, when you sell some or all of it. Merge. An indicator (a flag) that the lot should be merged (this will be useful for average cost booking which will be implemented later). For an augmenting postings , these informations must be either provided or inferred automatically. They can be provided in any order: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {2015-04-25, 27.00 USD, \"hooli-123\"} Assets:Invest:Cash -945.00 USD If you omit the date, the date of the Transaction is attached to it. If you omit the cost, the rest of the postings must be filled in such that the cost amount can be inferred from them. Since the label is optional anyway, an unspecified label field is left as a null value. You might wonder why it is allowed to override the date of an augmentation; it is useful when making cost basis adjustments to preserve the original acquisition date of a posting: You remove the posting, and then replace it with its original date and a new cost basis. Now, when we sell those shares, we will refer to the posting as a \u201creducing\u201d posting , a \u201c reduction \u201d. Note that the terms \u201caugmenting\u201d and \u201creducing\u201d are just terminology I\u2019m came up with in the context of designing how Beancount processes inventories; they\u2019re not general accounting terms. It\u2019s a \u201creduction\u201d because we\u2019re removing shares from the inventory that has been accumulated up to the date of its transaction. For example, if we were to sell 30 shares from that lot of 35 shares, the input might look like this: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {27.00 USD} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains While the input looks the same as on the augmenting posting, Beancount handles this quite differently: it looks at the state of the account\u2019s inventory before applying the transaction and finds all the positions that match the lot data you provided. It then uses the details of the matched lots as the cost basis information for the reducing posting. In this example, it would simply match all the positions which have a cost basis of $27.00. This example\u2019s inventory before the sale contains a single lot of 35 shares at $27.00, so there is a single position matching it and that lot is reduced by 30 shares and 5 shares remain. We\u2019ll see later what happens in the case of multiple lots matching the specification. Note that you could have provide other subsets of lot information to match against, like just providing the label, for example: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {\"hooli-123\"} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains This is also a valid way to identify the particular lot you wish to reduce. If you had provided a date here, it would also only be used to match against the inventory contents, to disambiguate between lots acquired at different dates, not to attach the date anywhere. And furthermore, if there was a single lot in the inventory you could have also just provided just an empty cost basis spec like this: \u201c {} \u201d. The Booking Methods section below will delve into the detail of what happens when the matches are ambiguous. In summary: When you\u2019re adding something to an account\u2019s inventory (augmenting), the information you provide is used to create a new lot and is attached to it. When you\u2019re removing from an account\u2019s inventory (reducing), the information you provide is used to filter the inventory contents to select which of the lot(s) to reduce, and information from the selected lots is filled in. Homogeneous and Mixed Inventories \uf0c1 So far in the example and in the vast majority of the examples in the documentation, \u201caugmenting\u201d means adding a positive number of shares. But in Beancount many of the accounts normally have a negative balance, e.g., liabilities accounts. It\u2019s fair to ask if it makes sense to hold a negative balance of commodities held at cost. The answer is yes. These would correspond to \u201cshort\u201d positions. Most people are unlikely to be selling short, but Beancount inventories support it. How we define \u201caugmenting\u201d is in relation to the existing balance of lots of a particular commodity. For example, if an account\u2019s inventory contains the following positions: 25 HOOL {23.00 USD, 2016-04-01} 35 HOOL {27.00 USD, 2016-05-01} Then \u201cadding\u201d means a positive number of shares. On the other hand, if the account contains only short positions, like this: -20 HOOL {23.00 USD, 2016-04-15} -10 HOOL {27.00 USD, 2016-05-15} Then \u201cadding\u201d means a negative number of shares, and \u201creducing\u201d would be carried out by matching a positive number of shares against it. The two inventories portrayed above are homogeneous in units of HOOL, that is, all of the positions have the same sign. With of the most booking methods we will see further, Beancount makes it impossible to create a non-homogeneous, or \u201cmixed,\u201d inventory. But the \u201cNONE\u201d method allows it. A mixed inventory might have the following contents, for example: 25 HOOL {23.00 USD, 2016-04-01} -20 HOOL {23.00 USD, 2016-04-15} As you may intuit, the notion of \u201caugmenting\u201d or \u201creducing\u201d only makes sense for homogeneous inventories. Original Proposal \uf0c1 If you\u2019re interested in the design doc that led to this implementation, you can find the document here . I hope the resulting implementation is simple enough yet general.","title":"How Inventories Work"},{"location":"how_inventories_work.html#how-inventories-work","text":"Martin Blais , December 2016 http://furius.ca/beancount/doc/booking This document explains how we accumulate commodities and match sales (reductions) against accumulated inventory contents.","title":"How Inventories Work"},{"location":"how_inventories_work.html#introduction","text":"Beyond the ability to track and list the postings made to each of the accounts (an operation that produces a journal of entries), one of the most common and useful operations of Beancount is to sum up the positions of arbitrary sets of postings. These aggregations are at the heart of how Beancount works, and are implemented in an object called \u201cinventory.\u201d This document explains how this aggregation process works. If you\u2019re going to track investments, it\u2019s necessary to understand what follows.","title":"Introduction"},{"location":"how_inventories_work.html#matches-booking-methods","text":"In order to get the big picture, let\u2019s walk through the various booking features by way of a simple examples. This should expose you to all the main ideas in one go.","title":"Matches & Booking Methods"},{"location":"how_inventories_work.html#simple-postings-no-cost","text":"Consider a set of simple postings to an account, e.g., 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 221.23 USD \u2026 2016-04-29 * \"ATM Withdrawal\" Assets:Bank:Checking -100.00 USD \u2026 2016-04-29 * \"Debit card payment\" Assets:Bank:Checking -45.67 USD \u2026 The inventory of the Checking account begins empty. After the first transaction, its contents are 221.23 USD. After the second, 121.23 USD. Finally, the third transaction brings this balance to 75.56 USD. This seems very natural; the numbers simply add to. It might be obvious, but note also that the numbers are allowed to change the sign (go negative).","title":"Simple Postings \u2014 No Cost"},{"location":"how_inventories_work.html#multiple-commodities","text":"An inventory may contain more than one type of commodity. It is equivalent to a mapping from commodity to some number of units. For example, 2016-07-24 * \"Dinner before leaving NYC\" Expenses:Restaurants 34.58 USD \u2026 2016-07-26 * \"Food with friends after landing\" Expenses:Restaurants 62.11 CAD \u2026 After those two transactions, the Restaurants account contains 34.58 USD and 62.11 CAD. Its contents are said to be of mixed commodities. And naturally, postings are applied to just the currencies they affect. For instance, the following transaction 2016-07-27 * \"Brunch\" Expenses:Restaurants 23.91 CAD \u2026 brings the balance of that account to 34.58 USD and 86.02 CAD. The number of units USD hasn\u2019t changed. Note that accounts may contain any number of commodities, and this is also true for commodities held at cost, which we\u2019ll see shortly. While this is made possible, I recommend that you define enough accounts to keep a single commodity in each; this can be enforced with the \u201c onecommodity \u201d plugin.","title":"Multiple Commodities"},{"location":"how_inventories_work.html#cost-basis","text":"Things get a little more hairy when we consider the tracking of investments with a cost basis. Beancount allows you to associate a cost basis and an optional label with a particular lot acquired. Consider these two purchases to an investment account: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest 25 HOOL {23.00 USD, \"first-lot\"} \u2026 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest 35 HOOL {27.00 USD} \u2026 So now, the investment account\u2019s inventory contains units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Those two lots were not merged, they are still two distinct positions in the inventory. Inventories merge lots together and adjust the units only if the commodity and all of its cost attributes exactly match. (In practice, it\u2019s pretty rare that two augmentations will have the same cost and date attributes.) Note how Beancount automatically associated the acquisition date to each lot; you can override it if desired, by adding the date similar to the optional label. this is useful for making cost basis adjustments). Postings that add to the content of an inventory are called augmentations .","title":"Cost Basis"},{"location":"how_inventories_work.html#reductions","text":"But how do we remove commodities from an inventory? You could eat away at an existing lot by selling some of it. You do this by posting a reduction to the account, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {23.00 USD} \u2026 Just to be clear, what makes this posting a reduction is the mere fact that the sign (-) is opposite that of the balance of the account (+25) for that commodity. This posting tells Beancount to find all lots with a cost basis of 23.00 USD and remove 12 units from it. The resulting inventory will be units ccy cost cost-ccy lot-date label 13 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Note how the first posting was reduced by 12 units. We didn\u2019t have to specify all of the lot\u2019s attributes, just the cost. We could have equivalently used the date to specify which lot to reduce: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 Or the label: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {\"first-lot\"} \u2026 Or a combination of these. Any combination of attributes will be matched against the inventory contents to find which lot to reduce. In fact, if the inventory happened to have just a single lot in it, you could reduce it like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026","title":"Reductions"},{"location":"how_inventories_work.html#ambiguous-matches","text":"But what happens if multiple lots match the reduction? For example, with the previous inventory containing two lots, if you wrote your sale like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {} \u2026 Beancount wouldn\u2019t be able to figure out which lot needs to get reduced. We have an ambiguous match. Partially ambiguous matches are also possible. For example, if you have the following inventory to reduce from: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, None} 30 HOOL {25.00 USD, 2015-04-01, None} 35 HOOL {27.00 USD, 2015-05-01, None} And you attempted to reduce like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -12 HOOL {2015-04-01} \u2026 The first two lots are selected as matches.","title":"Ambiguous Matches"},{"location":"how_inventories_work.html#strict-booking","text":"What does Beancount do with ambiguous matches? By default, it issues an error. More precisely, what happens is that Beancount invokes the booking method and it handles the ambiguous match depending on what it is set. The default booking method is \u201c STRICT \u201d and it just gives up and spits out an error, telling you you need to refine your input in order to disambiguate your inventory reduction.","title":"Strict Booking"},{"location":"how_inventories_work.html#fifo-and-lifo-booking","text":"Other booking methods are available. They can be configured using options, like this: option \"booking_method\" \"FIFO\" The \u201c FIFO \u201d method automatically selects the oldest of the matching lots up to the requested size of the reduction. For example, given our previous inventory: units ccy cost cost-ccy lot-date label 25 HOOL {23.00 USD, 2015-04-01, \"first-lot\"} 35 HOOL {27.00 USD, 2015-05-01, None} Attempting to reduce 28 shares like this: 2015-05-15 * \"Sell some shares\" Assets:Invest -28 HOOL {} \u2026 would match both lots, completely reduce the first one to zero units and remove the remaining 3 units from the second lot, to result in the following inventory: units ccy cost cost-ccy lot-date label 32 HOOL {27.00 USD, 2015-05-01, None} The \u201cLIFO\u201d method works similarly, but consumes the youngest (latest) lots first, working its way backward in time to remove the volume.","title":"FIFO and LIFO Booking"},{"location":"how_inventories_work.html#per-account-booking-method","text":"You don\u2019t have to make all accounts follow the same booking method; the option in the previous section sets the default method for all accounts. In order to override the booking method for a particular account, you can use an optional string option on the account\u2019s Open directive, like this: 2014-01-01 open Assets:Invest \"FIFO\" This allows you to treat different accounts with a different booking resolution.","title":"Per-account Booking Method"},{"location":"how_inventories_work.html#total-matches","text":"There is an exception to strict booking: if the entire inventory is being reduced by exactly the total number of units, it\u2019s clear that all the matching lots are to be selected and this is considered unambiguous, even under \u201cSTRICT\u201d booking. For example, under \u201cSTRICT\u201d booking, this reduction would empty up the previous inventory without raising an error, because there are 25 + 35 shares matching: 2015-05-15 * \"Sell all my shares\" Assets:Invest -60 HOOL {} \u2026","title":"Total Matches"},{"location":"how_inventories_work.html#average-booking","text":"Retirement accounts created by government incentive programs (such as the 401k plan in the US or the RRSP in Canada) typically consist in pre-tax money. For these types of accounts, brokers usually disregard the calculation of cost basis because the taxation is to be made upon distributing money outside the account. These accounts are often managed to the extent that they are fully invested; therefore, fees are often taken as shares of the investment portfolio, priced on the day the fee is paid out. This makes it awkward to track the cost basis of individual lots. The correct way to deal with this is to treat the number of units and the cost basis of each commodity\u2019s entire set of lots separately. For example, the following two transactions: 2016-07-28 * \"Buy some shares of retirement fund\" Assets:Invest 45.0045 VBMPX {11.11 USD} \u2026 2016-10-12 * \"Buy some shares of retirement fund\" Assets:Invest 54.5951 VBMPX {10.99 USD} \u2026 Should result in a single lot with the total number of units and the averaged cost: units ccy cost cost-ccy lot-date label 99.5996 VBMPX {11.0442 USD, 2016-07-28, None} A fee taken in this account might look like this: 2016-12-30 * \"Quarterly management fee\" Assets:Invest -1.4154 VBMPX {10.59 USD} Expenses:Fees Even with negative units the number and cost get aggregated separately: units ccy cost cost-ccy lot-date label 98.1842 VBMPX {11.0508 USD, 2016-07-28, None} This feature isn\u2019t yet supported in Beancount; it\u2019s fairly tricky to implement, and will be the subject in a minor release in the future.","title":"Average Booking"},{"location":"how_inventories_work.html#no-booking","text":"However, there is another way to deal with non-taxable accounts in the meantime: you can simply disable the booking. There is a booking method called \u201c NONE \u201d which implements a very liberal strategy which accepts any new lot.. New lots are always appended unconditionally to the inventory. Using this strategy on the transactions from the previous section would result in this inventory: units ccy cost cost-ccy lot-date label 45.0045 VBMPX {11.11 USD, 2016-07-28, None} 54.5951 VBMPX {10.99 USD, 2016-10-12, None} -1.4154 VBMPX {10.59 USD, 2016-12-30, None} Observe how the resulting inventory has a mix of signs; normally this is not allowed, but it is tolerated under this degenerate booking method. Note that under this method, the only meaningful values are the total number of units and the total or average cost amounts. The individual lots aren\u2019t really lots, they only represent the list of all postings made to that account. Note: If you are familiar with Ledger, this is the default and only booking method that it supports.","title":"No Booking"},{"location":"how_inventories_work.html#summary","text":"In summary, here\u2019s what we presented in the walkthrough. Augmentations are never problematic; they always add a new position to an existing inventory. On the other hand, reductions may result in a few outcomes: Single match. Only one position matches the reduction; it is reduced. Total match. The total number of units requested matches the total number of units of the positions matched. These positions are reduced away. No match. None of the positions matches the reducing posting. An error is raised. Ambiguous matches. More than one position in the inventory matches the reducing posting; the booking method is invoked to handle this . There are a few booking methods available to handle the last case: STRICT. An error is raised. FIFO. Units from oldest (earliest) lots are selected until the reduction is complete. LIFO. Units from youngest (latest) lots are selected until the reduction is complete. AVERAGE. After every reduction, all the units of the affected commodity are merged and their new average cost is recalculated. NONE. Booking is disabled; the reducing lots is simply added to the inventory. This results in an inventory with mixed signs and only the total number of units and total cost basis are sensible numbers. Beancount has a default booking method for all accounts, which can be overridden with an option: option \"booking_method\" \"FIFO\" The default value for the booking method is \u201cSTRICT\u201d. I recommend that you leave it that way and override the booking method for specific accounts. The method can be specified for each account by adding a string to its Open directive: 2016-05-01 open Assets:Vanguard:RGAGX \"AVERAGE\"","title":"Summary"},{"location":"how_inventories_work.html#how-prices-are-used","text":"The short answer is that prices aren\u2019t used nor affect the booking algorithm at all. However, it is relevant to discuss what they do in this context because users invariably get confused about their interpretation. There are two use cases for prices: making conversions between commodities and tagging a reducing lot with its sale price in order to record it and optionally balance the proceeds.","title":"How Prices are Used"},{"location":"how_inventories_work.html#commodity-conversions","text":"Conversions are used to exchange one currency for another. They look like this: 2016-04-24 * \"Deposit check\" Assets:Bank:Checking 220.00 USD @ 1.3 CAD Income:Payment -286.00 CAD For the purpose of booking it against the Checking account\u2019s inventory, the posting with the price attached to it is treated just the same as if there was no price: the Checking account simply receives a deposit of 220.00 units of USD and will match against positions of commodity \u201cUSD\u201d. The price is used only to verify that the transaction balances and ensure the double-entry accounting rule is respected (220.00 x 1.3 CAD + -286.00 CAD = 0.00). It is otherwise ignored for the purpose of modifying the inventory contents. In a sense, after the postings have been applied to the account inventories, the price is forgotten and the inventory balance retains no memory of the deposit having occurred from a conversion.","title":"Commodity Conversions"},{"location":"how_inventories_work.html#price-vs-cost-basis","text":"One might wonder how the price is used if there is a cost basis specification, like this: 2015-05-15 * \"Sell some shares\" Assets:Invest:HOOL -12 HOOL {23.00 USD} @ 24.70 USD Assets:Invest:Cash 296.40 USD Income:Invest:Gains The answer is often surprising to many users: the price is not used by the balancing algorithm if there is a cost basis; the cost basis is the number used to balance the postings. This is a useful property that allows us to compute capital gains automatically. In the previous example, the balance algorithm would sum up -12 x 23.00 + 296.40 = -20.40 USD, which is the capital gain, (24.70 - 23.00) * 12. It would complete the last posting and assign it this value. In general, the way that profits on sales are calculated is by weighing the proceedings, i.e., the cash deposits, against the cost basis of the sold lots, and this is sufficient to establish the gain difference. Also, if an augmenting posting happens to have a price annotation on it, it is also unused. The price is an annotation for your records. It remains attached to the Posting objects and if you want to make use of it somehow, you can always do that by writing some Python code. There are already two plugins which make use of this annotation: beancount.plugins.implicit_prices : This plugin takes the prices attached to the postings and automatically creates and inserts Price directives for each of them, in order to feed the global price database. beancount.plugins.sellgains : This plugin implements an additional balancing check: it uses the prices to compute the expected proceeds and weighs them against all the other postings of the transaction excluding any postings to Income accounts. In our example, it would check that (-12 x 24.70 + 296.40) = 0. This provides yet another means of verifying the correctness of your input. See the Trading with Beancount document for more details on this topic.","title":"Price vs. Cost Basis"},{"location":"how_inventories_work.html#trades","text":"The combination of acquiring some asset and selling it back is what we call a \u201ctrade.\u201d In Beancount we consider only assets with a cost basis to be the subject of trades. Since booking reductions against accumulated inventory contents happens during the booking process, this is where trades should be identified and recorded. As of now [Dec 2016], trade recording has not been implemented. Some prototypes for it have been tested previously and I believe it will be very easy to add in the near future. This will be documented here. Watch this space. The way trades will be implemented is by allowing the booking process to insert matching metadata with unique UUIDs on both the augmenting and reducing postings, in the stream of transactions. Functions and reports will be provided that are able to easily extract the pairs of postings for each reducing postings and filter those out in different ways. Ultimately, one should be able to extract a list of all trades to a table, with the acquisition and sale price, as well as other fees.","title":"Trades"},{"location":"how_inventories_work.html#debugging-booking-issues","text":"If you\u2019re experiencing difficulties in recording your sales due to the matching process, there are tools you can use to view an account\u2019s detailed inventory contents before and after applying a Transaction to it. To do this, you can use the bean-doctor command. You invoke the program providing it with the file and line number close to the Transaction you want to select, like this: bean-doctor context The resulting output will show the list of inventory contents of all affected accounts prior to the transaction being applied, including cost basis, acquisition date, and optional label fully rendered. Note that some failures are typically handled by throwing away an invalid Transaction\u2019s effects (but never quietly). From Emacs or VI, placing the cursor near a transaction and invoking the corresponding command is the easiest way to invoke the command, as it inserts the line number automatically.","title":"Debugging Booking Issues"},{"location":"how_inventories_work.html#appendix","text":"The rest of this document delves into more technical details. You should feel free to ignore this entirely, it\u2019s not necessary reading to understand how Beancount works. Only bother if you\u2019re interested in the details.","title":"Appendix"},{"location":"how_inventories_work.html#data-representation","text":"It is useful to know how positions are represented in an inventory object. A Position is essentially some number of units of a commodity with some optional information about its acquisition: Cost. Its per-unit acquisition cost (the \u201ccost basis\u201d). Date. The date at which the units were acquired. Label. Some user-specified label which can be used to refer to the lot). We often refer to these position objects as \u201clots\u201d or \u201clegs.\u201d Schematically, a position object looks like this: The structure of a Position object. There are two different types of positions, discussed in detail in the sections that follow: Simple positions. These are positions with no cost basis. The \u201ccost\u201d attribute is set to a null value. (\u201c None \u201d in Python.) Positions held at cost. These are positions with an associated cost basis and acquisition details. An Inventory is simply an accumulation of such positions, represented as a list. We sometimes talk of the ante-inventory to refer to the contents of the inventory before a transaction\u2019s postings have been applied to it, and the ex-inventory to the resulting inventory after they have been applied. A Posting is an object which is a superset of a position: in addition to units and cost, it has an associated account and an optional price attributes. If present, the price has the same type as units. It represents one of the legs of a transaction in the input. Postings imply positions, and these positions are added to inventories. We can say that a position is posted to an account. For more details on the internal data structures used in Beancount, please refer to the Design Doc which expands on this topic further.","title":"Data Representation"},{"location":"how_inventories_work.html#why-booking-is-not-simple","text":"The complexity of the reduction process shows itself when we consider how to keep track of the cost basis of various lots. To demonstrate how this works, let us consider a simple example that we shall reuse in the different sections below: 25 shares are bought on 4/1 at $23/share. 35 shares are bought on 5/1 at $27/share. 30 shares are sold on 5/15; at that time, the price is $26/share. We\u2019ll ignore commissions for now, they don\u2019t introduce any additional complexity. In Beancount, this scenario would be recorded like this: 2015-04-01 * \"Buy some shares of Hooli in April\" Assets:Invest:HOOL 25 HOOL {23.00 USD} Assets:Invest:Cash -575.00 USD 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {...} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains Now, the entire question revolves around which of the shares are selected to be sold. I\u2019ve rendered this input as a red ellipsis (\u201c\u2026\u201d). Whatever the user puts in that spot will be used to determine which lot we want to use. Whichever lot(s) we elect to be the ones sold will determine the amount of gains, because that is a function of the cost basis of those shares. This is why this matters.","title":"Why Booking is Not Simple"},{"location":"how_inventories_work.html#augmentations-vs-reductions","text":"The most important observation is that there are two distinct kinds of lot specifications which look very similar in the input but which are processed very differently. When we buy, as in the first transaction above, the {...} cost basis syntax provides Beancount with information about a new lot: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {27.00 USD} Assets:Invest:Cash -945.00 USD We call this an \u201caugmentation\u201d to the inventory, because it will simply add a new position to it. The cost basis that you provide is attached to this position and preserved through time, in the inventory. In addition, there are a few other pieces of data you can provide for an augmenting lot. Let\u2019s have a look at all the data that can be provided in the cost spec: Cost basis. This consists in per-unit and total cost numbers\u2014which are combined into a single per-unit number\u2014and a currency. Acquisition date. A lot has an acquisition date. By default, the date attached of its parent transaction will be set as its acquisition date automatically. You may override this date by providing one. This comes in handy to handle stock splits or wash sales and preserve the original acquisition date of the replacement shares, as we\u2019ll see later. Label. You can provide a unique label for it, so that you can more easily refer to it later on, when you sell some or all of it. Merge. An indicator (a flag) that the lot should be merged (this will be useful for average cost booking which will be implemented later). For an augmenting postings , these informations must be either provided or inferred automatically. They can be provided in any order: 2015-05-01 * \"Buy some more shares of Hooli in May\" Assets:Invest:HOOL 35 HOOL {2015-04-25, 27.00 USD, \"hooli-123\"} Assets:Invest:Cash -945.00 USD If you omit the date, the date of the Transaction is attached to it. If you omit the cost, the rest of the postings must be filled in such that the cost amount can be inferred from them. Since the label is optional anyway, an unspecified label field is left as a null value. You might wonder why it is allowed to override the date of an augmentation; it is useful when making cost basis adjustments to preserve the original acquisition date of a posting: You remove the posting, and then replace it with its original date and a new cost basis. Now, when we sell those shares, we will refer to the posting as a \u201creducing\u201d posting , a \u201c reduction \u201d. Note that the terms \u201caugmenting\u201d and \u201creducing\u201d are just terminology I\u2019m came up with in the context of designing how Beancount processes inventories; they\u2019re not general accounting terms. It\u2019s a \u201creduction\u201d because we\u2019re removing shares from the inventory that has been accumulated up to the date of its transaction. For example, if we were to sell 30 shares from that lot of 35 shares, the input might look like this: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {27.00 USD} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains While the input looks the same as on the augmenting posting, Beancount handles this quite differently: it looks at the state of the account\u2019s inventory before applying the transaction and finds all the positions that match the lot data you provided. It then uses the details of the matched lots as the cost basis information for the reducing posting. In this example, it would simply match all the positions which have a cost basis of $27.00. This example\u2019s inventory before the sale contains a single lot of 35 shares at $27.00, so there is a single position matching it and that lot is reduced by 30 shares and 5 shares remain. We\u2019ll see later what happens in the case of multiple lots matching the specification. Note that you could have provide other subsets of lot information to match against, like just providing the label, for example: 2015-05-15 * \"Sell 30 shares\" Assets:Invest:HOOL -30 HOOL {\"hooli-123\"} @ 26.00 USD Assets:Invest:Cash 780.00 USD Income:Invest:HOOL:Gains This is also a valid way to identify the particular lot you wish to reduce. If you had provided a date here, it would also only be used to match against the inventory contents, to disambiguate between lots acquired at different dates, not to attach the date anywhere. And furthermore, if there was a single lot in the inventory you could have also just provided just an empty cost basis spec like this: \u201c {} \u201d. The Booking Methods section below will delve into the detail of what happens when the matches are ambiguous. In summary: When you\u2019re adding something to an account\u2019s inventory (augmenting), the information you provide is used to create a new lot and is attached to it. When you\u2019re removing from an account\u2019s inventory (reducing), the information you provide is used to filter the inventory contents to select which of the lot(s) to reduce, and information from the selected lots is filled in.","title":"Augmentations vs. Reductions"},{"location":"how_inventories_work.html#homogeneous-and-mixed-inventories","text":"So far in the example and in the vast majority of the examples in the documentation, \u201caugmenting\u201d means adding a positive number of shares. But in Beancount many of the accounts normally have a negative balance, e.g., liabilities accounts. It\u2019s fair to ask if it makes sense to hold a negative balance of commodities held at cost. The answer is yes. These would correspond to \u201cshort\u201d positions. Most people are unlikely to be selling short, but Beancount inventories support it. How we define \u201caugmenting\u201d is in relation to the existing balance of lots of a particular commodity. For example, if an account\u2019s inventory contains the following positions: 25 HOOL {23.00 USD, 2016-04-01} 35 HOOL {27.00 USD, 2016-05-01} Then \u201cadding\u201d means a positive number of shares. On the other hand, if the account contains only short positions, like this: -20 HOOL {23.00 USD, 2016-04-15} -10 HOOL {27.00 USD, 2016-05-15} Then \u201cadding\u201d means a negative number of shares, and \u201creducing\u201d would be carried out by matching a positive number of shares against it. The two inventories portrayed above are homogeneous in units of HOOL, that is, all of the positions have the same sign. With of the most booking methods we will see further, Beancount makes it impossible to create a non-homogeneous, or \u201cmixed,\u201d inventory. But the \u201cNONE\u201d method allows it. A mixed inventory might have the following contents, for example: 25 HOOL {23.00 USD, 2016-04-01} -20 HOOL {23.00 USD, 2016-04-15} As you may intuit, the notion of \u201caugmenting\u201d or \u201creducing\u201d only makes sense for homogeneous inventories.","title":"Homogeneous and Mixed Inventories"},{"location":"how_inventories_work.html#original-proposal","text":"If you\u2019re interested in the design doc that led to this implementation, you can find the document here . I hope the resulting implementation is simple enough yet general.","title":"Original Proposal"},{"location":"how_we_share_expenses.html","text":"How We Share Expenses \uf0c1 This document explains how I share expenses with my wife. This is a bit involved and I\u2019ve developed a good working system, but it\u2019s not so simple for most people to do that, so I figured I would take the time to describe it to help others with designing similar processes for themselves. Context \uf0c1 We\u2019re both working professionals and have decided to share all expenses roughly 70%/30%. This is what we shoot for, this is not a hard rule, but we track it as if it were rigid, and accept it as a good approximation. We have two types of shared expenses: Shared. Common expenses between us, e.g., rent, a dinner out with friends, etc. Kyle. Expenses for our child, e.g., daycare, diapers, nanny, baby food. These get handled very differently, because we book our child's expenses as if it were a separate project on its own. (If you prefer pictures, there\u2019s a diagram at the end of this document that provides an overview of the system.) Shared Expenses \uf0c1 For our shared expenses, I maintain an account for her on my personal ledger file. This is simple and I\u2019m not too interested in maintaining a separate ledger for the totality of our common shared expenses. It\u2019s sufficient for me to just keep track of her balance on that one account. You can imagine that account like a credit card (I\u2019m the credit provider) that she pays off with transfers and also by making her own shared expenses. I just declare an Assets:US:Share:Carolyn account on my personal ledger ( blais.beancount ). My Shared Expenses \uf0c1 Whenever I incur an expense that is for both of us, I book it normally but I will tag it with #carolyn: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery This gets automatically converted to: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery 19.60 USD Assets:US:Share:Carolyn 13.06 USD share: TRUE This is done by a custom plugin I built that splits the expenses according to some rules that we have between us (see also this plugin by Akkukis ). In this example, 40% of 32.66 (13.06) gets rerouted to her account. Note that this is an asset account for me, because she owes this. Her Shared Expenses \uf0c1 We also have to keep track of the money she spends on her own for shared expenses. Since she\u2019s not a Beancount user, I\u2019ve set up a Google Sheets doc in which she can add rows to a particular sheet. This sheet has fields: Date, Description, Account, Amount. I try to keep it simple. Then, I built an extract_sheets.py script that can pull down this data automatically and it writes it to a dedicated file for this, overwriting the entire contents each time. The contents of this ledger ( carolyn.beancount ) look like this: pushtag #carolyn ... 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" Expenses:Home:Furniture 199.99 USD Assets:US:Share:Carolyn -199.99 USD ... poptag #carolyn All of those transactions are tagged as #carolyn from the pushtag/poptag directive. They get translated by the plugin to reduce the amount by the portion I\u2019m supposed to pay: 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" #carolyn Expenses:Home:Furniture 119.99 USD Assets:US:Share:Carolyn -119.99 USD share: TRUE These postings typically reduce her asset account by that much, and thus remove the portion she paid for me on these shared expenses. I generate the file with a single command like this: extract_sheets.py --tag='carolyn' --sheet='Shared Expenses' '' 'Assets:US:Share:Carolyn' > carolyn.beancount In my personal ledger, I include this file to merge those expenses with my own directives flow. I also define a query to generate a statement for her account. In blais.beancount : include \"carolyn.beancount\" 2020-01-01 query \"carolyn\" \" select date, description, position, balance from open on 2017-01-01 where account ~ 'Assets:US:Share:Carolyn' \" Reviewing & Statement \uf0c1 In order to produce a statement for her to review (and spot the occasional mistake in data entry), I simply produce a journal of that account to a CSV file and upload that to another sheet in the same Google Sheets doc she inputs her expenses: bean-query -f csv -o carolyn.csv --numberify $L run carolyn upload-to-sheets -v --docid=\"\" carolyn.csv:\"Shared Account (Read-Only)\" This makes it easy for her to eyeball all the amounts posted to her balance with me and point out errors if they occur (some always appear). Moreover, all the information is in one place\u2014it\u2019s important to keep it simple. Finally, we can use the collaborative features of Sheets to communicate, e.g. comments, highlighting text, etc. Note that this system has the benefit of correctly accruing my expenses, by reducing my portion on categories for the stuff I pay and by including her portion on categories for the stuff she pays for. Reconciling our Shared Expenses \uf0c1 Finally, in order to reconcile this account, my wife (or I, but usually she\u2019s behind) just makes a bank transfer to my account, which I book to reduce her running account balance: 2019-01-30 * \"HERBANK EXT TRNSFR; DIRECTDEP\" Assets:US:MyBank:Checking 3000 USD Assets:US:Share:Carolyn Typically she'll do this every month or two. She'll be fiddling on her laptop and ask casually \"Hey, what's my balance I can do a transfer now?\" It\u2019s all fine to relax about the particulars since the system is keeping track of everything precisely, so she can send some approximate amount, it doesn't matter, it'll post to her account. Child Expenses \uf0c1 I designed a very different system to track our child\u2019s expenses. For Kyle, I\u2019m definitely interested in tracking the total cash flows and expenses related to him, regardless of who paid for them. It\u2019s interesting to be able to ask (our ledger) a question like: \u201cHow much did his schooling cost?\u201d, for example, or \u201cHow much did we pay in diapers, in total?\u201d. Furthermore, we tend to pay for different things for Kyle, e.g. I deal with the daycare expenses (I\u2019m the accounting nerd after all, so this shouldn\u2019t be surprising), and his mother tends to buy all the clothing and prepare his food. To have a global picture of all costs related to him, we need to account for these things correctly. One interesting detail is that it would be difficult to do this with the previously described method, because I\u2019d have to have a mirror of all expense accounts I\u2019d use for him. This would make my personal ledger really ugly. For example, I want to book diapers to Expenses:Pharmacy , but I also have my own personal pharmacy expenses. So in theory, to do that I\u2019d like to have separate accounts for him and me, e.g., Expenses:Kyle:Pharmacy and Expenses:Pharmacy . This would have to be done for all the accounts we use for him. I don't do that. My Child Expenses on my Personal Ledger \uf0c1 Instead of doing that, what I want is for my personal ledger to book all the expenses I make for him to a single category: Expenses:Kyle , and to track all the detail in a shared ledger. But I still have to book all the expenses to some category, and these appear on my personal ledger, there\u2019s no option (I won\u2019t maintain a separate credit card to pay for his expenses, that would be overkill, so I have to find a way). I accomplish this by booking the expenses to my own expenses account, as usual, but tagging the transaction with #kyle: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Pharmacy And I have a different plugin that automatically makes the conversion of those transactions to: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Kyle 49.99 USD diverted_account: \"Expenses:Pharmacy\" So from my personal side, all those expenses get booked to my \u201cKyle project\u201d account. This is accomplished by the divert_expenses plugin, with this configuration: plugin \"beancount.plugins.divert_expenses\" \"{ 'tag': 'kyle', 'account': 'Expenses:Kyle' }\" The \u201cdiverted_account\u201d metadata is used to keep track of the original account, and this is used later by another script that generates a ledger file decided to my expenses for him (more below). My Child Expenses in Kyle\u2019s own Ledger \uf0c1 Now, because we\u2019re considering Kyle\u2019s expenses a project of his own, I have to maintain a set of ledgers for him. I automatically pull the transactions I described in the previous section from my personal ledger and automatically convert them to a file dedicated to his dad (me). This is done by calling the extract_tagged script: extract_tagged.py blais.beancount '#kyle' 'Income:Dad' --translate \"Expenses:Kyle:Mom=Income:Mom\" > dad.beancount The matching transaction from the previous section would look like this in it: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Income:Dad -49.99 USD Expenses:Pharmacy 49.99 USD As you can see, the script was able to reconstruct the original account name in Kyle\u2019s ledger by using the metadata saved by the divert_expenses plugin in the previous section. It also books the source of the payment to a single account showing it came from his father ( Income:Dad ). There\u2019s no need for me to keep track of which payment method I used on Kyle\u2019s side (e.g. by credit card), that\u2019s not important to him. This ledger contains the sum total of all expenses I\u2019ve made for him to date. The file gets entirely overwritten each time I run this (this is a purely generated output, no hand editing is ever done here, if I change anything I change it in my personal ledger file). Her Child Expenses \uf0c1 In order to keep track of her expenses for Kyle, we use the same method (and programs) as we use for our shared accounts in order to pull a set of \u201cCarolyn\u2019s expenses for Kyle\u201d from another sheet in the same Google Sheets doc: extract_sheets.py --sheet='Kyle Expenses (Regular)' '' 'Income:Mom' > mom.beancount This pulls in transactions that look like this: 2018-09-23 * \"SPROUT SAN FRANCISCO\" \"Clothing for Kyle 9-12 months\" Expenses:Clothing 118.30 USD Income:Mom -118.30 USD The expenses accounts are pulled from the sheet\u2014sometimes I have to go fix that by hand a bit, as they may not track precisely those from our ledger\u2014and the income shows that the contribution was made by his mother. Putting it All Together \uf0c1 Finally, we need to put together all these files. I created a top-level kyle.beancount file that simple declares all of his account categories and includes his mom and dad files. We have three files: dad.beancount mom.beancount kyle.beancount -> includes transactions from dad.beancount and mom.beancount I can then run bean-web or bean-query on kyle.beancount. There are two things which are interesting to see on that ledger: The distribution of Kyle\u2019s expenses, in other words, what\u2019s it costing us to raise a child (regardless of who pays). The difference between our contributions. In order to reconcile (2), we basically compare the balances of the Income:Mom and Income:Dad accounts. This can be done \u201cby hand\u201d, visually (using a calculator), but since Beancount\u2019s API make it so easy to pull any number from a ledger, I wrote a simple script which does that, computes the total amount of Income contributions, breaks it down to the expected numbers based on our chosen breakdown, compares it to each parent\u2019s actual contribution, and simply prints out the difference: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42906.58 Mom expected contribution: 28403.72 Mom actual contribution: 28102.72 Mom OWES Dad: 301.00 After reconciling, the final number should be zero. Reconciling the Child Ledger \uf0c1 In order to account for the difference and make the contributions to Kyle\u2019s upbringing in line with our mutual arrangement of 60%/40%, in the previous section, my wife would need to transfer $301 to me. Of course, we don\u2019t actually transfer anything in practice, I just add a virtual transfer to book the difference to her shared account on my ledger. To do this, all I have to do is insert a transaction in blais.beancount that looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Assets:US:Share:Carolyn 301.00 USD Expenses:Kyle:Mom When this gets pulled into dad.beancount (as explained previously), it looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Income:Mom -301.00 USD Income:Dad 301.00 USD After that, going back to the Kyle ledger, pulling in all the transactions again (this is done by using a Makefile) would show an updated balance of 0: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42605.58 Mom expected contribution: 28403.72 Mom actual contribution: 28403.72 Mom OWES Dad: 0.00 Summary of the System \uf0c1 Here\u2019s a diagram that puts in perspective the entire system together: I (\u201cDad\u201d) use Beancount via Emacs, exclusively. Carolyn (\u201cMom\u201d) only interacts with a single Google Sheets doc with three sheets in it. I pull in Carolyn\u2019s shared expenses from a sheet that she fills in to a ledger which gets included in my personal ledger. I also pull in her expenses for Kyle in a similar document, and from my personal ledger I generate my own expenses for Kyle. Both of these documents are merged in a top-level ledger dedicated to Kyle\u2019s expenses. I\u2019m not going to claim it\u2019s simple and that it\u2019s always up-to-date. I tend to update all of these things once/quarter and fix a few input errors each time we review it. I automate much of it via Makefiles, but frankly I don\u2019t update it frequently enough to just remember where all the moving pieces are, so every time, there\u2019s a bit of scrambling and reading my makefiles and figuring out what\u2019s what (in fact, I was motivated to write this to cement my understanding solidly for the future). Amazingly enough, it hasn\u2019t broken at all and I find all the pieces every time and make it work. And we have enough loose change between the two of us that it\u2019s never that urgent to reconcile every week. My wife tends to pay more shared expenses and I tend to pay more Kyle\u2019s expenses, so I often end up doing a transfer from one to the other to even things out and it\u2019s relatively close\u2014the shortfall in Kyle expenses makes up for my shortfall on the shared expenses. Conclusion \uf0c1 I hope this was useful to some of you trying to solve problems using the double-entry bookkeeping method. Please write questions and comments on the Beancount mailing-list, or as comments on this doc.","title":"How We Share Expenses"},{"location":"how_we_share_expenses.html#how-we-share-expenses","text":"This document explains how I share expenses with my wife. This is a bit involved and I\u2019ve developed a good working system, but it\u2019s not so simple for most people to do that, so I figured I would take the time to describe it to help others with designing similar processes for themselves.","title":"How We Share Expenses"},{"location":"how_we_share_expenses.html#context","text":"We\u2019re both working professionals and have decided to share all expenses roughly 70%/30%. This is what we shoot for, this is not a hard rule, but we track it as if it were rigid, and accept it as a good approximation. We have two types of shared expenses: Shared. Common expenses between us, e.g., rent, a dinner out with friends, etc. Kyle. Expenses for our child, e.g., daycare, diapers, nanny, baby food. These get handled very differently, because we book our child's expenses as if it were a separate project on its own. (If you prefer pictures, there\u2019s a diagram at the end of this document that provides an overview of the system.)","title":"Context"},{"location":"how_we_share_expenses.html#shared-expenses","text":"For our shared expenses, I maintain an account for her on my personal ledger file. This is simple and I\u2019m not too interested in maintaining a separate ledger for the totality of our common shared expenses. It\u2019s sufficient for me to just keep track of her balance on that one account. You can imagine that account like a credit card (I\u2019m the credit provider) that she pays off with transfers and also by making her own shared expenses. I just declare an Assets:US:Share:Carolyn account on my personal ledger ( blais.beancount ).","title":"Shared Expenses"},{"location":"how_we_share_expenses.html#my-shared-expenses","text":"Whenever I incur an expense that is for both of us, I book it normally but I will tag it with #carolyn: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery This gets automatically converted to: 2018-12-23 * \"WHISK\" \"Water refill\" #carolyn Liabilities:US:Amex:BlueCash -32.66 USD Expenses:Food:Grocery 19.60 USD Assets:US:Share:Carolyn 13.06 USD share: TRUE This is done by a custom plugin I built that splits the expenses according to some rules that we have between us (see also this plugin by Akkukis ). In this example, 40% of 32.66 (13.06) gets rerouted to her account. Note that this is an asset account for me, because she owes this.","title":"My Shared Expenses"},{"location":"how_we_share_expenses.html#her-shared-expenses","text":"We also have to keep track of the money she spends on her own for shared expenses. Since she\u2019s not a Beancount user, I\u2019ve set up a Google Sheets doc in which she can add rows to a particular sheet. This sheet has fields: Date, Description, Account, Amount. I try to keep it simple. Then, I built an extract_sheets.py script that can pull down this data automatically and it writes it to a dedicated file for this, overwriting the entire contents each time. The contents of this ledger ( carolyn.beancount ) look like this: pushtag #carolyn ... 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" Expenses:Home:Furniture 199.99 USD Assets:US:Share:Carolyn -199.99 USD ... poptag #carolyn All of those transactions are tagged as #carolyn from the pushtag/poptag directive. They get translated by the plugin to reduce the amount by the portion I\u2019m supposed to pay: 2017-05-21 * \"Amazon\" \"Cotton Mattress Protector in White\" #carolyn Expenses:Home:Furniture 119.99 USD Assets:US:Share:Carolyn -119.99 USD share: TRUE These postings typically reduce her asset account by that much, and thus remove the portion she paid for me on these shared expenses. I generate the file with a single command like this: extract_sheets.py --tag='carolyn' --sheet='Shared Expenses' '' 'Assets:US:Share:Carolyn' > carolyn.beancount In my personal ledger, I include this file to merge those expenses with my own directives flow. I also define a query to generate a statement for her account. In blais.beancount : include \"carolyn.beancount\" 2020-01-01 query \"carolyn\" \" select date, description, position, balance from open on 2017-01-01 where account ~ 'Assets:US:Share:Carolyn' \"","title":"Her Shared Expenses"},{"location":"how_we_share_expenses.html#reviewing-statement","text":"In order to produce a statement for her to review (and spot the occasional mistake in data entry), I simply produce a journal of that account to a CSV file and upload that to another sheet in the same Google Sheets doc she inputs her expenses: bean-query -f csv -o carolyn.csv --numberify $L run carolyn upload-to-sheets -v --docid=\"\" carolyn.csv:\"Shared Account (Read-Only)\" This makes it easy for her to eyeball all the amounts posted to her balance with me and point out errors if they occur (some always appear). Moreover, all the information is in one place\u2014it\u2019s important to keep it simple. Finally, we can use the collaborative features of Sheets to communicate, e.g. comments, highlighting text, etc. Note that this system has the benefit of correctly accruing my expenses, by reducing my portion on categories for the stuff I pay and by including her portion on categories for the stuff she pays for.","title":"Reviewing & Statement"},{"location":"how_we_share_expenses.html#reconciling-our-shared-expenses","text":"Finally, in order to reconcile this account, my wife (or I, but usually she\u2019s behind) just makes a bank transfer to my account, which I book to reduce her running account balance: 2019-01-30 * \"HERBANK EXT TRNSFR; DIRECTDEP\" Assets:US:MyBank:Checking 3000 USD Assets:US:Share:Carolyn Typically she'll do this every month or two. She'll be fiddling on her laptop and ask casually \"Hey, what's my balance I can do a transfer now?\" It\u2019s all fine to relax about the particulars since the system is keeping track of everything precisely, so she can send some approximate amount, it doesn't matter, it'll post to her account.","title":"Reconciling our Shared Expenses"},{"location":"how_we_share_expenses.html#child-expenses","text":"I designed a very different system to track our child\u2019s expenses. For Kyle, I\u2019m definitely interested in tracking the total cash flows and expenses related to him, regardless of who paid for them. It\u2019s interesting to be able to ask (our ledger) a question like: \u201cHow much did his schooling cost?\u201d, for example, or \u201cHow much did we pay in diapers, in total?\u201d. Furthermore, we tend to pay for different things for Kyle, e.g. I deal with the daycare expenses (I\u2019m the accounting nerd after all, so this shouldn\u2019t be surprising), and his mother tends to buy all the clothing and prepare his food. To have a global picture of all costs related to him, we need to account for these things correctly. One interesting detail is that it would be difficult to do this with the previously described method, because I\u2019d have to have a mirror of all expense accounts I\u2019d use for him. This would make my personal ledger really ugly. For example, I want to book diapers to Expenses:Pharmacy , but I also have my own personal pharmacy expenses. So in theory, to do that I\u2019d like to have separate accounts for him and me, e.g., Expenses:Kyle:Pharmacy and Expenses:Pharmacy . This would have to be done for all the accounts we use for him. I don't do that.","title":"Child Expenses"},{"location":"how_we_share_expenses.html#my-child-expenses-on-my-personal-ledger","text":"Instead of doing that, what I want is for my personal ledger to book all the expenses I make for him to a single category: Expenses:Kyle , and to track all the detail in a shared ledger. But I still have to book all the expenses to some category, and these appear on my personal ledger, there\u2019s no option (I won\u2019t maintain a separate credit card to pay for his expenses, that would be overkill, so I have to find a way). I accomplish this by booking the expenses to my own expenses account, as usual, but tagging the transaction with #kyle: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Pharmacy And I have a different plugin that automatically makes the conversion of those transactions to: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Liabilities:US:Amex:BlueCash -49.99 USD Expenses:Kyle 49.99 USD diverted_account: \"Expenses:Pharmacy\" So from my personal side, all those expenses get booked to my \u201cKyle project\u201d account. This is accomplished by the divert_expenses plugin, with this configuration: plugin \"beancount.plugins.divert_expenses\" \"{ 'tag': 'kyle', 'account': 'Expenses:Kyle' }\" The \u201cdiverted_account\u201d metadata is used to keep track of the original account, and this is used later by another script that generates a ledger file decided to my expenses for him (more below).","title":"My Child Expenses on my Personal Ledger"},{"location":"how_we_share_expenses.html#my-child-expenses-in-kyles-own-ledger","text":"Now, because we\u2019re considering Kyle\u2019s expenses a project of his own, I have to maintain a set of ledgers for him. I automatically pull the transactions I described in the previous section from my personal ledger and automatically convert them to a file dedicated to his dad (me). This is done by calling the extract_tagged script: extract_tagged.py blais.beancount '#kyle' 'Income:Dad' --translate \"Expenses:Kyle:Mom=Income:Mom\" > dad.beancount The matching transaction from the previous section would look like this in it: 2019-02-01 * \"AMAZON.COM\" \"MERCHANDISE - Diapers size 4 for Kyle\" #kyle Income:Dad -49.99 USD Expenses:Pharmacy 49.99 USD As you can see, the script was able to reconstruct the original account name in Kyle\u2019s ledger by using the metadata saved by the divert_expenses plugin in the previous section. It also books the source of the payment to a single account showing it came from his father ( Income:Dad ). There\u2019s no need for me to keep track of which payment method I used on Kyle\u2019s side (e.g. by credit card), that\u2019s not important to him. This ledger contains the sum total of all expenses I\u2019ve made for him to date. The file gets entirely overwritten each time I run this (this is a purely generated output, no hand editing is ever done here, if I change anything I change it in my personal ledger file).","title":"My Child Expenses in Kyle\u2019s own Ledger"},{"location":"how_we_share_expenses.html#her-child-expenses","text":"In order to keep track of her expenses for Kyle, we use the same method (and programs) as we use for our shared accounts in order to pull a set of \u201cCarolyn\u2019s expenses for Kyle\u201d from another sheet in the same Google Sheets doc: extract_sheets.py --sheet='Kyle Expenses (Regular)' '' 'Income:Mom' > mom.beancount This pulls in transactions that look like this: 2018-09-23 * \"SPROUT SAN FRANCISCO\" \"Clothing for Kyle 9-12 months\" Expenses:Clothing 118.30 USD Income:Mom -118.30 USD The expenses accounts are pulled from the sheet\u2014sometimes I have to go fix that by hand a bit, as they may not track precisely those from our ledger\u2014and the income shows that the contribution was made by his mother.","title":"Her Child Expenses"},{"location":"how_we_share_expenses.html#putting-it-all-together","text":"Finally, we need to put together all these files. I created a top-level kyle.beancount file that simple declares all of his account categories and includes his mom and dad files. We have three files: dad.beancount mom.beancount kyle.beancount -> includes transactions from dad.beancount and mom.beancount I can then run bean-web or bean-query on kyle.beancount. There are two things which are interesting to see on that ledger: The distribution of Kyle\u2019s expenses, in other words, what\u2019s it costing us to raise a child (regardless of who pays). The difference between our contributions. In order to reconcile (2), we basically compare the balances of the Income:Mom and Income:Dad accounts. This can be done \u201cby hand\u201d, visually (using a calculator), but since Beancount\u2019s API make it so easy to pull any number from a ledger, I wrote a simple script which does that, computes the total amount of Income contributions, breaks it down to the expected numbers based on our chosen breakdown, compares it to each parent\u2019s actual contribution, and simply prints out the difference: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42906.58 Mom expected contribution: 28403.72 Mom actual contribution: 28102.72 Mom OWES Dad: 301.00 After reconciling, the final number should be zero.","title":"Putting it All Together"},{"location":"how_we_share_expenses.html#reconciling-the-child-ledger","text":"In order to account for the difference and make the contributions to Kyle\u2019s upbringing in line with our mutual arrangement of 60%/40%, in the previous section, my wife would need to transfer $301 to me. Of course, we don\u2019t actually transfer anything in practice, I just add a virtual transfer to book the difference to her shared account on my ledger. To do this, all I have to do is insert a transaction in blais.beancount that looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Assets:US:Share:Carolyn 301.00 USD Expenses:Kyle:Mom When this gets pulled into dad.beancount (as explained previously), it looks like this: 2019-02-09 * \"Transfer to reconcile Kyle's expenses\" #kyle Income:Mom -301.00 USD Income:Dad 301.00 USD After that, going back to the Kyle ledger, pulling in all the transactions again (this is done by using a Makefile) would show an updated balance of 0: $ reconcile_shared.py kyle.beancount Total contributions: 71009.30 Dad expected contribution: 42605.58 Dad actual contribution: 42605.58 Mom expected contribution: 28403.72 Mom actual contribution: 28403.72 Mom OWES Dad: 0.00","title":"Reconciling the Child Ledger"},{"location":"how_we_share_expenses.html#summary-of-the-system","text":"Here\u2019s a diagram that puts in perspective the entire system together: I (\u201cDad\u201d) use Beancount via Emacs, exclusively. Carolyn (\u201cMom\u201d) only interacts with a single Google Sheets doc with three sheets in it. I pull in Carolyn\u2019s shared expenses from a sheet that she fills in to a ledger which gets included in my personal ledger. I also pull in her expenses for Kyle in a similar document, and from my personal ledger I generate my own expenses for Kyle. Both of these documents are merged in a top-level ledger dedicated to Kyle\u2019s expenses. I\u2019m not going to claim it\u2019s simple and that it\u2019s always up-to-date. I tend to update all of these things once/quarter and fix a few input errors each time we review it. I automate much of it via Makefiles, but frankly I don\u2019t update it frequently enough to just remember where all the moving pieces are, so every time, there\u2019s a bit of scrambling and reading my makefiles and figuring out what\u2019s what (in fact, I was motivated to write this to cement my understanding solidly for the future). Amazingly enough, it hasn\u2019t broken at all and I find all the pieces every time and make it work. And we have enough loose change between the two of us that it\u2019s never that urgent to reconcile every week. My wife tends to pay more shared expenses and I tend to pay more Kyle\u2019s expenses, so I often end up doing a transfer from one to the other to even things out and it\u2019s relatively close\u2014the shortfall in Kyle expenses makes up for my shortfall on the shared expenses.","title":"Summary of the System"},{"location":"how_we_share_expenses.html#conclusion","text":"I hope this was useful to some of you trying to solve problems using the double-entry bookkeeping method. Please write questions and comments on the Beancount mailing-list, or as comments on this doc.","title":"Conclusion"},{"location":"importing_external_data.html","text":"Importing External Data in Beancount \uf0c1 Martin Blais , March 2016 http://furius.ca/beancount/doc/ingest This document is about Beancount v2; Beancount v3 is in development and uses a completely different build and installation system. For instructions on importing v3, see this document (Beangulp). Introduction The Importing Process Automating Network Downloads Typical Downloads Extracting Data from PDF Files Tools Invocation Configuration Configuring from an Input File Writing an Importer Regression Testing your Importers Generating Test Input Making Incremental Improvements Running the Tests Caching Data In-Memory Caching On-Disk Caching Organizing your Files Example Importers Cleaning Up Automatic Categorization Cleaning up Payees Future Work Related Discussion Threads Historical Note Introduction \uf0c1 This is the user\u2019s manual for the library and tools in Beancount which can help you automate the importing of external transaction data into your Beancount input file and manage the documents you download from your financial institutions\u2019 websites. The Importing Process \uf0c1 People often wonder how we do this, so let me describe candidly and in more detail what we\u2019re talking about doing here. The essence of the task at hand is to transcribe the transactions that occur in a person\u2019s entire set of accounts to a single text file: the Beancount input file. Having the entire set of transactions ingested in a single system is what we need to do in order to generate comprehensive reports about one\u2019s wealth and expenses. Some people call this \u201creconciling\u201d. We could transcribe all the transactions manually from paper statements by typing them in. However nowadays most financial institutions have a website where you can download a statement of historical transactions in a number of data formats which you can parse to output Beancount syntax for them. Importing transactions from these documents involves: Manually reviewing the transactions for correctness or even fraud; Merging new transactions with previous transactions imported from another account. For example, a payment from a bank account to pay off one\u2019s credit card will typically be imported from both the bank AND the credit card account. You must manually merge the corresponding transactions together 1 . Assigning the right category to an expense transaction Organizing your file by moving the resulting directives to the right place in your file. Verifying balances either visually or inserting a Balance directive which asserts what the final account balance should be after the new transactions are inserted. If my importers work without bugs, this is a process that takes me 30-60 minutes to update the majority of my active accounts. Less active accounts are updated every quarter or when I feel like it. I tend to do this on Saturday morning maybe twice per month, or sometimes weekly. If you maintain a well-organized input file with lots of assertions, mismatches are easily found, it\u2019s a pleasant and easy process, and after you\u2019re done generating an updated balance sheet is rewarding (I typically re-export to a Google Finance portfolio). Automating Network Downloads \uf0c1 The downloading of files is not something I automate, and Beancount provides no tools to connect to the network and fetch your files. There is simply too great a variety of protocols out there to make a meaningful contribution to this problem 2 . Given the nature of today's secure websites and the castles of JavaScript used to implement them, it would be a nightmare to implement. Web scraping is probably too much to be a worthwhile, viable solution. I manually log into the various websites with my usernames & passwords and click the right buttons to generate the downloaded files I need. These files are recognized automatically by the importers and extracting transactions and filing the documents in a well-organized directory hierarchy is automated using the tools described in this document. While I\u2019m not scripting the fetching, I think it\u2019s possible to do so on some sites. That work is left for you to implement where you think it\u2019s worth the time. Typical Downloads \uf0c1 Here\u2019s a description of the typical kinds of files involved; this describes my use case and what I\u2019ve managed to do. This should give you a qualitative sense of what\u2019s involved. Credit cards and banks provide fairly good quality historical statement downloads in OFX or CSV file formats but I need to categorize the other side of those transactions manually and merge some of the transactions together. Investment accounts provide me with great quality of processable statements and the extraction of purchase transactions is fully automated, but I need to manually edit sales transactions in order to associate the correct cost basis. Some institutions for specialized products (e.g., P2P lending) provide only PDF files and those are translated manually. Payroll stubs and vesting events are usually provided only as PDFs and I don't bother trying to extract data automatically; I transcribe those manually, keeping the input very regular and with postings in the same order as they appear on the statements. This makes it easier. Cash transactions : I have to enter those by hand. I only book non-food expenses as individual transactions directly, and for food maybe once every six months I'll count my wallet balance and insert a summarizing transaction for each month to debit away the cash account towards food to make it balance. If you do this, you end up with surprisingly little transactions to type manually, maybe just a few each week (it depends on lifestyle choices, for me this works). When I\u2019m on the go, I just note those on my phone in Google Keep and eventually transcribe them after they accumulate. Extracting Data from PDF Files \uf0c1 I've made some headway toward converting data from PDF files, which is a common need, but it's incomplete; it turns out that fully automating table extraction from PDF isn't easy in the general case. I have some code that is close to working and will release it when the time is right. Otherwise, the best FOSS solution I\u2019ve found for this is a tool called TabulaPDF but you still need to manually identify where the tables of data are located on the page; you may be able to automate some fetching using its sister project tabula-java . Nevertheless, I usually have good success with my importers grepping around PDF statements converted to ugly text in order to identify what institution they are for and extracting the date of issuance of the document. Finally, there are a number of different tools used to extract text from PDF documents, such as PDFMiner , LibreOffice , the xpdf library, the poppler library 3 and more... but none of them works consistently on all input documents; you will likely end up installing many and relying on different ones for different input files. For this reason, I\u2019m not requiring a dependency on PDF conversion tools from within Beancount. You should test what works on your specific documents and invoke those tools from your importer implementations. Tools \uf0c1 There are three Beancount tools provided to orchestrate the three stages of importing: bean-identify : Given a messy list of downloaded files (e.g. in ~/Downloads), automatically identify which of your configured importers is able to handle them and print them out. This is to be used for debugging and figuring out if your configuration is properly associating a suitable importer for each of the files you downloaded; bean-extract : Extracting transactions and statement date from each file, if at all possible. This produces some Beancount input text to be moved to your input file; bean-file : Filing away the downloaded files to a directory hierarchy which mirrors the chart of accounts, for preservation, e.g. in a personal git repo. The filenames are cleaned, the files are moved and an appropriate statement date is prepended to each of them so that Beancount may produce corresponding Document directives. Invocation \uf0c1 All tools accept the same input parameters: bean- For example, bean-extract blais.config ~/Downloads The filing tool accepts an extra option that lets the user decide where to move the files, e.g., bean-file -o ~/accounting/documents blais.config ~/Downloads Its default behavior is to move the files to the same directory as that of the configuration file. Configuration \uf0c1 The tools introduced previously orchestrate the processes, but they don\u2019t do all that much of the concrete work of groking the individual downloads themselves. They call methods on importer objects. You must provide a list of such importers; this list is the configuration for the importing process (without it, those tools don\u2019t do anything useful). For each file found, each of the importers is called to assert whether it can or cannot handle that file. If it deems that it can, methods can be called to produce a list of transactions, extract a date, or produce a cleaned up filename for the downloaded file. The configuration should be a Python3 module in which you instantiate the importers and assign the list to the module-level \u201c CONFIG \u201d variable, like this: #!/usr/bin/env python3 from myimporters.bank import acmebank from myimporters.bank import chase \u2026 CONFIG = [ acmebank.Importer(), chase.Importer(), \u2026 ] Of course, since you\u2019re crafting a Python script, you can insert whatever other code in there you like. All that matters is that this \u201c CONFIG \u201d variable refers to a list of objects which comply with the importer protocol (described in the next section). Their order does not matter. In particular, it\u2019s a good idea to write your importers as generically as possible and to parameterize them with the particular account names you use in your input file. This helps keep your code independent of the particular accounts and forces you to define logical accounts, and I\u2019ve found that this helps with clarity. Or not\u2026 At the end of the day, these importer codes live in some of your own personal place, not with Beancount. If you so desire, you can keep them as messy and unshareable as you like. Configuring from an Input File \uf0c1 An interesting idea that I haven\u2019t tested yet is to use one\u2019s Beancount input file to infer the configuration of importers. If you want to try this out and hack something, you can load your input file from the import configuration Python config, by using the API\u2019s beancount.loader.load_file() function. Writing an Importer \uf0c1 Each of the importers must comply with a particular protocol and implement at least some of its methods. The full detail of this protocol is best found in the source code itself: importer.py . The tools above will take care of finding the downloads and invoking the appropriate methods on your importer objects. Here\u2019s a brief summary of the methods you need to, or may want to, implement: name(): This method provides a unique id for each importer instance. It\u2019s convenient to be able to refer to your importers with a unique name; it gets printed out by the identification process, for instance. identify(): This method just returns true if this importer can handle the given file. You must implement this method, and all the tools invoke it to figure out the list of (file, importer) pairs. extract(): This is called to attempt to extract some Beancount directives from the file contents. It must create the directives by instantiating the objects defined in beancount.core.data and return them. file_account(): This method returns the root account associated with this importer. This is where the downloaded file will be moved by the filing script file_date(): If a date can be extracted from the statement\u2019s contents, return it here. This is useful for dated PDF statements\u2026 it\u2019s often possible using regular expressions to grep out the date from a PDF converted to text. This allows the filing script to prepend a relevant date instead of using the date when the file was downloaded (the default). file_name(): It\u2019s most convenient not to bother renaming downloaded files. Oftentimes, the files generated from your bank all have a unique name and they end up getting renamed by your browser when you download multiple ones and the names collide. This function is used for the importer to provide a \u201cnice\u201d name to file the download under. So basically, you create some module somewhere on your PYTHONPATH\u2014anywhere you like, somewhere private\u2014and you implement a class, something like this: from beancount.ingest import importer class Importer(importer.ImporterProtocol): def identify(self, file): \u2026 # Override other methods\u2026 Typically I create my importer module files in directories dedicated to each importer, so that I can place example input files all in that directory for regression testing. Regression Testing your Importers \uf0c1 I've found over time that regression testing is key to maintaining your importer code working. Importers are often written against file formats with no official spec and unexpected surprises routinely occur. For example, I have XML files with some unescaped \"&\" characters, which require a custom fix just for that bank 4 . I\u2019ve also witnessed a discount brokerage switching its dates format between MM/DD/YY and DD/MM/YY; that importer now needs to be able to handle both types. So you make the necessary adjustment, and eventually you find out that something else breaks; this isn\u2019t great. And the timing is particularly annoying: usually things break when you\u2019re trying to update your ledger: you have other things to do. The easiest, laziest and most relevant way to test those importers is to use some real data files and compare what your importer extracts from them to expected outputs. For the importers to be at least somewhat reliable, you really need to be able to reproduce the extractions on a number of real inputs. And since the inputs are so unpredictable and poorly defined, it\u2019s not practical to write exhaustive tests on what they could be. In practice, I have to make at least some fix to some of my importers every couple of months, and with this process, it only sinks about a half-hour of my time: I add the new downloaded file which causes breakage to the importer directory, I fix the code by running it there locally as a test. And I also run the tests over all the previously downloaded test inputs in that directory (old and new) to ensure my importer is still working as intended on the older files. There is some support for automating this process in beancount.ingest.regression . What we want is some routine that will list the importer\u2019s package directory, identify the input files which are to be used for testing, and generate a suite of unit tests which compares the output produced by importer methods to the contents of \u201cexpected files\u201d placed next to the test file. For example, given a package with an implementation of an importer and two sample input files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample2.csv You can place this code in the Python module (the __init__.py file): from beancount.ingest import regression \u2026 def test(): importer = Importer(...) yield from regression.compare_sample_files(importer) If your importer overrides the extract() and file_date() methods, this will generate four unit tests which get run automatically by pytest : A test which calls extract() on sample1.csv , prints the extracted entries to a string, and compares this string with the contents of sample1.csv.extract A test which calls file_date() on sample1.csv and compares the date with the one found in the sample1.csv.file_date file. A test like (1) but on sample2.csv A test like (2) but on sample2.csv Generating Test Input \uf0c1 At first, the files containing the expected outputs do not exist. When an expected output file is absent like this, the regression tests automatically generate those files from the extracted output. This would result in the following list of files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample1.csv.extract /home/joe/importers/acmebank/sample1.csv.file_date /home/joe/importers/acmebank/sample2.csv /home/joe/importers/acmebank/sample2.csv.extract /home/joe/importers/acmebank/sample2.csv.file_date You should inspect the contents of the expected output files to visually assert that they represent the contents of the downloaded files. If you run the tests again with those files present, the expected output files will be used as inputs to the tests. If the contents differ in the future, the test will fail and an error will be generated. (You can test this out now if you want, by manually editing and inserting some unexpected data in one of those files.) When you edit your source code, you can always re-run the tests to make sure it still works on those older files. When a newly downloaded file fails, you repeat the process above: You make a copy of it in that directory, fix the importer, run it, check the expected files. That\u2019s it 5 . Making Incremental Improvements \uf0c1 Sometimes I make improvements to the importers that result in more or better output being generated even in the older files, so that all the old tests will now fail. A good way to deal with this is to keep all of these files under source control, locally delete all the expected files, run the tests to regenerate new ones, and then diff against the most recent commit to check that the changes are as expected. Caching Data \uf0c1 Some of the data conversions for binary files can be costly and slow. This is usually the case for converting PDF files to text 6 . This is particularly painful, since in the process of ingesting our downloaded data we\u2019re typically going to run the tools multiple times\u2014at least twice if everything works without flaw: once to extract, twice to file\u2014and usually many more times if there are problems. For this reason, we want to cache these conversions, so that a painful 40 second PDF-to-text conversion doesn\u2019t have to be run twice, for example. Beancount aims to provide two levels of caching for conversions on downloaded files: An in-memory caching of conversions so that multiple importers requesting the same conversion runs them only once, and An on-disk caching of conversions so that multiple invocations of the tools get reused. In-Memory Caching \uf0c1 In-memory caching works like this: Your methods receive a wrapper object for a given file and invoke the wrapper\u2019s convert() method, providing a converter callable/function. class MyImporter(ImporterProtocol): ... def extract(self, file): text = file.convert(slow_convert_pdf_to_text) match = re.search(..., text) This conversion is automatically memoized: if two importers or two different methods use the same converter on the file, the conversion is only run once. This is a simple way of handling redundant conversions in-memory. Make sure to always call those through the .convert() method and share the converter functions to take advantage of this. On-Disk Caching \uf0c1 At the moment. Beancount only implements (1). On-disk caching will be implemented later. Track this ticket for status updates. Organizing your Files \uf0c1 The tools described in this document are pretty flexible in terms of letting you specify Import configuration : The Python file which provides the list of importer objects as a configuration; Importers implementation : The Python modules which implement the individual importers and their regression testing files; Downloads directory : Which directory the downloaded files are to be found in; Filing directory : Which directory the downloaded files are intended to be filed to. You can specify these from any location you want. Despite this, some people are often asking how to organize their files, so I provide a template example under beancount/examples/ingest/office , and I describe this here. I recommend that you create a Git or Mercurial 7 source-controlled repository following this structure: office \u251c\u2500\u2500 documents \u2502 \u251c\u2500\u2500 Assets \u2502 \u251c\u2500\u2500 Liabilities \u2502 \u251c\u2500\u2500 Income \u2502 \u2514\u2500\u2500 Expenses \u251c\u2500\u2500 importers \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u2514\u2500\u2500 \u2026 \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u251c\u2500\u2500 sample-download-1.csv \u2502 \u251c\u2500\u2500 sample-download-1.extract \u2502 \u251c\u2500\u2500 sample-download-1.file_date \u2502 \u2514\u2500\u2500 sample-download-1.file_name \u251c\u2500\u2500 personal.beancount \u2514\u2500\u2500 personal.import The root \u201coffice\u201d directory is your repository. It contains your ledger file (\u201c personal.beancount \u201d), your importer configuration (\u201c personal.import \u201d), your custom importers source code (\u201c importers/ \u201d) and your history of documents (\u201c documents/ \u201d), which should be well-organized by bean-file. You always run the commands from this root directory. An advantage of storing your documents in the same repository as your importers source code is that you can just symlink your regression tests to some files under the documents/ directory. You can check your configuration by running identify: bean-identify example.import ~/Downloads If it works, you can extract transactions from your downloaded files at once: bean-extract -e example.beancount example.import ~/Downloads > tmp.beancount You then open tmp.beancount and move its contents to your personal.beancount file. Once you\u2019re finished, you can stash away the downloaded files for posterity like this: bean-file example.import ~/Downloads -o documents If my importers work, I usually don\u2019t even bother opening those files. You can use the --dry-run option to test moving destinations before doing so. To run the regression tests of the custom importers, use the following command: pytest -v importers Personally, I have a Makefile in my root directory with these targets to make my life easier. Note that you will have to install \u201cpytest\u201d, which is a test runner; it is often packaged as \u201cpython3-pytest\u201d or \u201cpytest\u201d. Example Importers \uf0c1 Beyond the documentation above, I cooked up an example importer for a made-up CSV file format for a made-up investment account. See this directory . There\u2019s also an example of an importer which uses an external tool (PDFMiner2) to convert a PDF file to text to identify it and to extract the statement date from it. See this directory . Beancount also comes with some very basic generic importers. See this directory . There is a simple OFX importer that has worked for me for a long time. Though it\u2019s pretty simple, I\u2019ve used it for years, it\u2019s good enough to pull info out of most credit card accounts. There are also a couple of mixin classes you can mix into your importer implementation to make it more convenient; these are relics from the LedgerHub project\u2014you don\u2019t really need to use them\u2014which can help in the transition to it. Eventually I plan to build and provide a generic CSV file parser in this framework, as well as a parser for QIF files which should allow one to transition from Quicken to Beancount. (I need example inputs to do this; if you\u2019re comfortable sharing your file I could use it to build this, as I don\u2019t have any real input, I don\u2019t use Quicken.) It would also be nice to build a converter from GnuCash at some point; this would go here as well. Cleaning Up \uf0c1 Automatic Categorization \uf0c1 A frequently asked question and common idea from first-time users is \u201cHow do I automatically assign a category to transactions I\u2019ve imported which have only one side?\u201d For example, importing transactions from a credit card account usually provides only one posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD For which you must manually insert an Expenses posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD Expenses:Food:Grocery People often have the impression that it is time-consuming to do this. My standard answer is that while it would be fun to have, if you have a text editor with account name completion configured properly, it\u2019s a breeze to do this manually and you don\u2019t really need it. You wouldn\u2019t save much time by automating this away. And personally I like to go over each of the transactions to check what they are and sometimes add comments (e.g., who I had dinner with, what that Amazon charge was for, etc.) and that\u2019s when I categorize. It\u2019s something that could eventually be solved by letting the user provide some simple rules, or by using the history of past transactions to feed into a simple learning classifier. Beancount does not currently provide a mechanism to automatically categorize transactions. You can build this into your importer code. I want to provide a hook for the user to register a completion function that could run across all the importers where you could hook that code in. Cleaning up Payees \uf0c1 The payees that one can find in the downloads are usually ugly names: They are sometimes the legal names of the business, which often does not reflect the street name of the place you went, for various reasons. For example, I recently ate at a restaurant called the \u201cLucky Bee\u201d in New York, and the memo from the OFX file was \u201cKING BEE\u201d. The names are sometimes abbreviated or contain some crud. In the previous example, the actual memo was \u201cKING BEE NEW YO\u201d, where \u201cNEW YO\u201d is a truncated location string. The amount of ugliness is inconsistent between data sources. It would be nice to be able to normalize the payee names by translating them at import time. I think you can do most of it using some simple rules mapping regular expressions to names provided by the user. There\u2019s really no good automated way to obtain the \u201cclean name\u201d of the payee. Beancount does not provide a hook for letting you do this this yet. It will eventually. You could also build a plugin to rename those accounts when loading your ledger. I\u2019ll build that too\u2014it\u2019s easy and would result in much nicer output. Future Work \uf0c1 A list of things I\u2019d really want to add, beyond fortifying what\u2019s already there: A generic, configurable CSV importer which you can instantiate. I plan to play with this a bit and build a sniffer that could automatically figure out the role of each column. A hook to allow you to register a callback for post-processing transactions that works across all importers. Related Discussion Threads \uf0c1 Getting started; assigning accounts to bank .csv data Status of LedgerHub\u2026 how can I get started? Rekon wants your CSV files Historical Note \uf0c1 There once was a first implementation of the process described in this document. The project was called LedgerHub and has been decommissioned in February 2016, rewritten and the resulting code integrated in Beancount itself, into this beancount.ingest library. The original project was intended to include the implementation of various importers to share them with other people, but this sharing was not very successful, and so the rewrite includes only the scaffolding for building your own importers and invoking them, and only a very limited number of example importer implementations. Documents about LedgerHub are preserved, and can help you understand the origins and design choices for Beancount\u2019s importer support. They can be found here: Original design Original instructions & final status (the old version of this doc) An analysis of the reasons why it the project was terminated (post-mortem) There are essentially three conceptual modes of entering such transactions: (1) a user crafts a single transaction manually, (2) another where a user inputs the two sides as a single transaction to transfer accounts, and (3) the two separate transactions get merged into a single one automatically. These are dual modes of each other. The twist in this story is that the same transaction often posts at different dates in each of its accounts. Beancount currently [March 2016] does not support multiple dates for a single transaction\u2019s postings, but a discussion is ongoing to implement support for these input modes. See this document for more details. \u21a9 The closest to universal downloader you will find in the free software world is ofxclient for OFX files, and in the commercial world, Yodlee provides a service that connects to many financial institutions. \u21a9 The \u2018pdftotext\u2019 utility in poppler provides the useful \u2018-layout\u2019 flag which outputs a text file without mangling tables, which can be helpful in the normal case of \u2018transaction-per-row\u2019 \u21a9 After sending them a few detailed emails about this and getting no response nor seeing any change in the downloaded files, I have given up on them fixing the issue. \u21a9 As you can see, this process is partly why I don\u2019t share my importers code. It requires the storage of way too much personal data in order to keep them working. \u21a9 I don\u2019t really understand why, since opening them up for viewing is almost instant, but nearly all the tools to convert them to other formats are vastly slower. \u21a9 I personally much prefer Mercurial for the clarity of its commands and output and its extensibility, but an advantage of Git\u2019s storage model is that moving files within it comes for free (no extra copy is stored). Moving files in a Mercurial repository costs you a bit in storage space. And if you rename accounts or change how you organize your files you will end up potentially copying many large files. \u21a9","title":"Importing External Data"},{"location":"importing_external_data.html#importing-external-data-in-beancount","text":"Martin Blais , March 2016 http://furius.ca/beancount/doc/ingest This document is about Beancount v2; Beancount v3 is in development and uses a completely different build and installation system. For instructions on importing v3, see this document (Beangulp). Introduction The Importing Process Automating Network Downloads Typical Downloads Extracting Data from PDF Files Tools Invocation Configuration Configuring from an Input File Writing an Importer Regression Testing your Importers Generating Test Input Making Incremental Improvements Running the Tests Caching Data In-Memory Caching On-Disk Caching Organizing your Files Example Importers Cleaning Up Automatic Categorization Cleaning up Payees Future Work Related Discussion Threads Historical Note","title":"Importing External Data in Beancount"},{"location":"importing_external_data.html#introduction","text":"This is the user\u2019s manual for the library and tools in Beancount which can help you automate the importing of external transaction data into your Beancount input file and manage the documents you download from your financial institutions\u2019 websites.","title":"Introduction"},{"location":"importing_external_data.html#the-importing-process","text":"People often wonder how we do this, so let me describe candidly and in more detail what we\u2019re talking about doing here. The essence of the task at hand is to transcribe the transactions that occur in a person\u2019s entire set of accounts to a single text file: the Beancount input file. Having the entire set of transactions ingested in a single system is what we need to do in order to generate comprehensive reports about one\u2019s wealth and expenses. Some people call this \u201creconciling\u201d. We could transcribe all the transactions manually from paper statements by typing them in. However nowadays most financial institutions have a website where you can download a statement of historical transactions in a number of data formats which you can parse to output Beancount syntax for them. Importing transactions from these documents involves: Manually reviewing the transactions for correctness or even fraud; Merging new transactions with previous transactions imported from another account. For example, a payment from a bank account to pay off one\u2019s credit card will typically be imported from both the bank AND the credit card account. You must manually merge the corresponding transactions together 1 . Assigning the right category to an expense transaction Organizing your file by moving the resulting directives to the right place in your file. Verifying balances either visually or inserting a Balance directive which asserts what the final account balance should be after the new transactions are inserted. If my importers work without bugs, this is a process that takes me 30-60 minutes to update the majority of my active accounts. Less active accounts are updated every quarter or when I feel like it. I tend to do this on Saturday morning maybe twice per month, or sometimes weekly. If you maintain a well-organized input file with lots of assertions, mismatches are easily found, it\u2019s a pleasant and easy process, and after you\u2019re done generating an updated balance sheet is rewarding (I typically re-export to a Google Finance portfolio).","title":"The Importing Process"},{"location":"importing_external_data.html#automating-network-downloads","text":"The downloading of files is not something I automate, and Beancount provides no tools to connect to the network and fetch your files. There is simply too great a variety of protocols out there to make a meaningful contribution to this problem 2 . Given the nature of today's secure websites and the castles of JavaScript used to implement them, it would be a nightmare to implement. Web scraping is probably too much to be a worthwhile, viable solution. I manually log into the various websites with my usernames & passwords and click the right buttons to generate the downloaded files I need. These files are recognized automatically by the importers and extracting transactions and filing the documents in a well-organized directory hierarchy is automated using the tools described in this document. While I\u2019m not scripting the fetching, I think it\u2019s possible to do so on some sites. That work is left for you to implement where you think it\u2019s worth the time.","title":"Automating Network Downloads"},{"location":"importing_external_data.html#typical-downloads","text":"Here\u2019s a description of the typical kinds of files involved; this describes my use case and what I\u2019ve managed to do. This should give you a qualitative sense of what\u2019s involved. Credit cards and banks provide fairly good quality historical statement downloads in OFX or CSV file formats but I need to categorize the other side of those transactions manually and merge some of the transactions together. Investment accounts provide me with great quality of processable statements and the extraction of purchase transactions is fully automated, but I need to manually edit sales transactions in order to associate the correct cost basis. Some institutions for specialized products (e.g., P2P lending) provide only PDF files and those are translated manually. Payroll stubs and vesting events are usually provided only as PDFs and I don't bother trying to extract data automatically; I transcribe those manually, keeping the input very regular and with postings in the same order as they appear on the statements. This makes it easier. Cash transactions : I have to enter those by hand. I only book non-food expenses as individual transactions directly, and for food maybe once every six months I'll count my wallet balance and insert a summarizing transaction for each month to debit away the cash account towards food to make it balance. If you do this, you end up with surprisingly little transactions to type manually, maybe just a few each week (it depends on lifestyle choices, for me this works). When I\u2019m on the go, I just note those on my phone in Google Keep and eventually transcribe them after they accumulate.","title":"Typical Downloads"},{"location":"importing_external_data.html#extracting-data-from-pdf-files","text":"I've made some headway toward converting data from PDF files, which is a common need, but it's incomplete; it turns out that fully automating table extraction from PDF isn't easy in the general case. I have some code that is close to working and will release it when the time is right. Otherwise, the best FOSS solution I\u2019ve found for this is a tool called TabulaPDF but you still need to manually identify where the tables of data are located on the page; you may be able to automate some fetching using its sister project tabula-java . Nevertheless, I usually have good success with my importers grepping around PDF statements converted to ugly text in order to identify what institution they are for and extracting the date of issuance of the document. Finally, there are a number of different tools used to extract text from PDF documents, such as PDFMiner , LibreOffice , the xpdf library, the poppler library 3 and more... but none of them works consistently on all input documents; you will likely end up installing many and relying on different ones for different input files. For this reason, I\u2019m not requiring a dependency on PDF conversion tools from within Beancount. You should test what works on your specific documents and invoke those tools from your importer implementations.","title":"Extracting Data from PDF Files"},{"location":"importing_external_data.html#tools","text":"There are three Beancount tools provided to orchestrate the three stages of importing: bean-identify : Given a messy list of downloaded files (e.g. in ~/Downloads), automatically identify which of your configured importers is able to handle them and print them out. This is to be used for debugging and figuring out if your configuration is properly associating a suitable importer for each of the files you downloaded; bean-extract : Extracting transactions and statement date from each file, if at all possible. This produces some Beancount input text to be moved to your input file; bean-file : Filing away the downloaded files to a directory hierarchy which mirrors the chart of accounts, for preservation, e.g. in a personal git repo. The filenames are cleaned, the files are moved and an appropriate statement date is prepended to each of them so that Beancount may produce corresponding Document directives.","title":"Tools"},{"location":"importing_external_data.html#invocation","text":"All tools accept the same input parameters: bean- For example, bean-extract blais.config ~/Downloads The filing tool accepts an extra option that lets the user decide where to move the files, e.g., bean-file -o ~/accounting/documents blais.config ~/Downloads Its default behavior is to move the files to the same directory as that of the configuration file.","title":"Invocation"},{"location":"importing_external_data.html#configuration","text":"The tools introduced previously orchestrate the processes, but they don\u2019t do all that much of the concrete work of groking the individual downloads themselves. They call methods on importer objects. You must provide a list of such importers; this list is the configuration for the importing process (without it, those tools don\u2019t do anything useful). For each file found, each of the importers is called to assert whether it can or cannot handle that file. If it deems that it can, methods can be called to produce a list of transactions, extract a date, or produce a cleaned up filename for the downloaded file. The configuration should be a Python3 module in which you instantiate the importers and assign the list to the module-level \u201c CONFIG \u201d variable, like this: #!/usr/bin/env python3 from myimporters.bank import acmebank from myimporters.bank import chase \u2026 CONFIG = [ acmebank.Importer(), chase.Importer(), \u2026 ] Of course, since you\u2019re crafting a Python script, you can insert whatever other code in there you like. All that matters is that this \u201c CONFIG \u201d variable refers to a list of objects which comply with the importer protocol (described in the next section). Their order does not matter. In particular, it\u2019s a good idea to write your importers as generically as possible and to parameterize them with the particular account names you use in your input file. This helps keep your code independent of the particular accounts and forces you to define logical accounts, and I\u2019ve found that this helps with clarity. Or not\u2026 At the end of the day, these importer codes live in some of your own personal place, not with Beancount. If you so desire, you can keep them as messy and unshareable as you like.","title":"Configuration"},{"location":"importing_external_data.html#configuring-from-an-input-file","text":"An interesting idea that I haven\u2019t tested yet is to use one\u2019s Beancount input file to infer the configuration of importers. If you want to try this out and hack something, you can load your input file from the import configuration Python config, by using the API\u2019s beancount.loader.load_file() function.","title":"Configuring from an Input File"},{"location":"importing_external_data.html#writing-an-importer","text":"Each of the importers must comply with a particular protocol and implement at least some of its methods. The full detail of this protocol is best found in the source code itself: importer.py . The tools above will take care of finding the downloads and invoking the appropriate methods on your importer objects. Here\u2019s a brief summary of the methods you need to, or may want to, implement: name(): This method provides a unique id for each importer instance. It\u2019s convenient to be able to refer to your importers with a unique name; it gets printed out by the identification process, for instance. identify(): This method just returns true if this importer can handle the given file. You must implement this method, and all the tools invoke it to figure out the list of (file, importer) pairs. extract(): This is called to attempt to extract some Beancount directives from the file contents. It must create the directives by instantiating the objects defined in beancount.core.data and return them. file_account(): This method returns the root account associated with this importer. This is where the downloaded file will be moved by the filing script file_date(): If a date can be extracted from the statement\u2019s contents, return it here. This is useful for dated PDF statements\u2026 it\u2019s often possible using regular expressions to grep out the date from a PDF converted to text. This allows the filing script to prepend a relevant date instead of using the date when the file was downloaded (the default). file_name(): It\u2019s most convenient not to bother renaming downloaded files. Oftentimes, the files generated from your bank all have a unique name and they end up getting renamed by your browser when you download multiple ones and the names collide. This function is used for the importer to provide a \u201cnice\u201d name to file the download under. So basically, you create some module somewhere on your PYTHONPATH\u2014anywhere you like, somewhere private\u2014and you implement a class, something like this: from beancount.ingest import importer class Importer(importer.ImporterProtocol): def identify(self, file): \u2026 # Override other methods\u2026 Typically I create my importer module files in directories dedicated to each importer, so that I can place example input files all in that directory for regression testing.","title":"Writing an Importer"},{"location":"importing_external_data.html#regression-testing-your-importers","text":"I've found over time that regression testing is key to maintaining your importer code working. Importers are often written against file formats with no official spec and unexpected surprises routinely occur. For example, I have XML files with some unescaped \"&\" characters, which require a custom fix just for that bank 4 . I\u2019ve also witnessed a discount brokerage switching its dates format between MM/DD/YY and DD/MM/YY; that importer now needs to be able to handle both types. So you make the necessary adjustment, and eventually you find out that something else breaks; this isn\u2019t great. And the timing is particularly annoying: usually things break when you\u2019re trying to update your ledger: you have other things to do. The easiest, laziest and most relevant way to test those importers is to use some real data files and compare what your importer extracts from them to expected outputs. For the importers to be at least somewhat reliable, you really need to be able to reproduce the extractions on a number of real inputs. And since the inputs are so unpredictable and poorly defined, it\u2019s not practical to write exhaustive tests on what they could be. In practice, I have to make at least some fix to some of my importers every couple of months, and with this process, it only sinks about a half-hour of my time: I add the new downloaded file which causes breakage to the importer directory, I fix the code by running it there locally as a test. And I also run the tests over all the previously downloaded test inputs in that directory (old and new) to ensure my importer is still working as intended on the older files. There is some support for automating this process in beancount.ingest.regression . What we want is some routine that will list the importer\u2019s package directory, identify the input files which are to be used for testing, and generate a suite of unit tests which compares the output produced by importer methods to the contents of \u201cexpected files\u201d placed next to the test file. For example, given a package with an implementation of an importer and two sample input files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample2.csv You can place this code in the Python module (the __init__.py file): from beancount.ingest import regression \u2026 def test(): importer = Importer(...) yield from regression.compare_sample_files(importer) If your importer overrides the extract() and file_date() methods, this will generate four unit tests which get run automatically by pytest : A test which calls extract() on sample1.csv , prints the extracted entries to a string, and compares this string with the contents of sample1.csv.extract A test which calls file_date() on sample1.csv and compares the date with the one found in the sample1.csv.file_date file. A test like (1) but on sample2.csv A test like (2) but on sample2.csv","title":"Regression Testing your Importers"},{"location":"importing_external_data.html#generating-test-input","text":"At first, the files containing the expected outputs do not exist. When an expected output file is absent like this, the regression tests automatically generate those files from the extracted output. This would result in the following list of files: /home/joe/importers/acmebank/__init__.py <- code goes here /home/joe/importers/acmebank/sample1.csv /home/joe/importers/acmebank/sample1.csv.extract /home/joe/importers/acmebank/sample1.csv.file_date /home/joe/importers/acmebank/sample2.csv /home/joe/importers/acmebank/sample2.csv.extract /home/joe/importers/acmebank/sample2.csv.file_date You should inspect the contents of the expected output files to visually assert that they represent the contents of the downloaded files. If you run the tests again with those files present, the expected output files will be used as inputs to the tests. If the contents differ in the future, the test will fail and an error will be generated. (You can test this out now if you want, by manually editing and inserting some unexpected data in one of those files.) When you edit your source code, you can always re-run the tests to make sure it still works on those older files. When a newly downloaded file fails, you repeat the process above: You make a copy of it in that directory, fix the importer, run it, check the expected files. That\u2019s it 5 .","title":"Generating Test Input"},{"location":"importing_external_data.html#making-incremental-improvements","text":"Sometimes I make improvements to the importers that result in more or better output being generated even in the older files, so that all the old tests will now fail. A good way to deal with this is to keep all of these files under source control, locally delete all the expected files, run the tests to regenerate new ones, and then diff against the most recent commit to check that the changes are as expected.","title":"Making Incremental Improvements"},{"location":"importing_external_data.html#caching-data","text":"Some of the data conversions for binary files can be costly and slow. This is usually the case for converting PDF files to text 6 . This is particularly painful, since in the process of ingesting our downloaded data we\u2019re typically going to run the tools multiple times\u2014at least twice if everything works without flaw: once to extract, twice to file\u2014and usually many more times if there are problems. For this reason, we want to cache these conversions, so that a painful 40 second PDF-to-text conversion doesn\u2019t have to be run twice, for example. Beancount aims to provide two levels of caching for conversions on downloaded files: An in-memory caching of conversions so that multiple importers requesting the same conversion runs them only once, and An on-disk caching of conversions so that multiple invocations of the tools get reused.","title":"Caching Data"},{"location":"importing_external_data.html#in-memory-caching","text":"In-memory caching works like this: Your methods receive a wrapper object for a given file and invoke the wrapper\u2019s convert() method, providing a converter callable/function. class MyImporter(ImporterProtocol): ... def extract(self, file): text = file.convert(slow_convert_pdf_to_text) match = re.search(..., text) This conversion is automatically memoized: if two importers or two different methods use the same converter on the file, the conversion is only run once. This is a simple way of handling redundant conversions in-memory. Make sure to always call those through the .convert() method and share the converter functions to take advantage of this.","title":"In-Memory Caching"},{"location":"importing_external_data.html#on-disk-caching","text":"At the moment. Beancount only implements (1). On-disk caching will be implemented later. Track this ticket for status updates.","title":"On-Disk Caching"},{"location":"importing_external_data.html#organizing-your-files","text":"The tools described in this document are pretty flexible in terms of letting you specify Import configuration : The Python file which provides the list of importer objects as a configuration; Importers implementation : The Python modules which implement the individual importers and their regression testing files; Downloads directory : Which directory the downloaded files are to be found in; Filing directory : Which directory the downloaded files are intended to be filed to. You can specify these from any location you want. Despite this, some people are often asking how to organize their files, so I provide a template example under beancount/examples/ingest/office , and I describe this here. I recommend that you create a Git or Mercurial 7 source-controlled repository following this structure: office \u251c\u2500\u2500 documents \u2502 \u251c\u2500\u2500 Assets \u2502 \u251c\u2500\u2500 Liabilities \u2502 \u251c\u2500\u2500 Income \u2502 \u2514\u2500\u2500 Expenses \u251c\u2500\u2500 importers \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u2514\u2500\u2500 \u2026 \u2502 \u251c\u2500\u2500 __init__.py \u2502 \u251c\u2500\u2500 sample-download-1.csv \u2502 \u251c\u2500\u2500 sample-download-1.extract \u2502 \u251c\u2500\u2500 sample-download-1.file_date \u2502 \u2514\u2500\u2500 sample-download-1.file_name \u251c\u2500\u2500 personal.beancount \u2514\u2500\u2500 personal.import The root \u201coffice\u201d directory is your repository. It contains your ledger file (\u201c personal.beancount \u201d), your importer configuration (\u201c personal.import \u201d), your custom importers source code (\u201c importers/ \u201d) and your history of documents (\u201c documents/ \u201d), which should be well-organized by bean-file. You always run the commands from this root directory. An advantage of storing your documents in the same repository as your importers source code is that you can just symlink your regression tests to some files under the documents/ directory. You can check your configuration by running identify: bean-identify example.import ~/Downloads If it works, you can extract transactions from your downloaded files at once: bean-extract -e example.beancount example.import ~/Downloads > tmp.beancount You then open tmp.beancount and move its contents to your personal.beancount file. Once you\u2019re finished, you can stash away the downloaded files for posterity like this: bean-file example.import ~/Downloads -o documents If my importers work, I usually don\u2019t even bother opening those files. You can use the --dry-run option to test moving destinations before doing so. To run the regression tests of the custom importers, use the following command: pytest -v importers Personally, I have a Makefile in my root directory with these targets to make my life easier. Note that you will have to install \u201cpytest\u201d, which is a test runner; it is often packaged as \u201cpython3-pytest\u201d or \u201cpytest\u201d.","title":"Organizing your Files"},{"location":"importing_external_data.html#example-importers","text":"Beyond the documentation above, I cooked up an example importer for a made-up CSV file format for a made-up investment account. See this directory . There\u2019s also an example of an importer which uses an external tool (PDFMiner2) to convert a PDF file to text to identify it and to extract the statement date from it. See this directory . Beancount also comes with some very basic generic importers. See this directory . There is a simple OFX importer that has worked for me for a long time. Though it\u2019s pretty simple, I\u2019ve used it for years, it\u2019s good enough to pull info out of most credit card accounts. There are also a couple of mixin classes you can mix into your importer implementation to make it more convenient; these are relics from the LedgerHub project\u2014you don\u2019t really need to use them\u2014which can help in the transition to it. Eventually I plan to build and provide a generic CSV file parser in this framework, as well as a parser for QIF files which should allow one to transition from Quicken to Beancount. (I need example inputs to do this; if you\u2019re comfortable sharing your file I could use it to build this, as I don\u2019t have any real input, I don\u2019t use Quicken.) It would also be nice to build a converter from GnuCash at some point; this would go here as well.","title":"Example Importers"},{"location":"importing_external_data.html#cleaning-up","text":"","title":"Cleaning Up"},{"location":"importing_external_data.html#automatic-categorization","text":"A frequently asked question and common idea from first-time users is \u201cHow do I automatically assign a category to transactions I\u2019ve imported which have only one side?\u201d For example, importing transactions from a credit card account usually provides only one posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD For which you must manually insert an Expenses posting, like this: 2016-03-18 * \"UNION MARKET\" Liabilities:US:CreditCard -12.99 USD Expenses:Food:Grocery People often have the impression that it is time-consuming to do this. My standard answer is that while it would be fun to have, if you have a text editor with account name completion configured properly, it\u2019s a breeze to do this manually and you don\u2019t really need it. You wouldn\u2019t save much time by automating this away. And personally I like to go over each of the transactions to check what they are and sometimes add comments (e.g., who I had dinner with, what that Amazon charge was for, etc.) and that\u2019s when I categorize. It\u2019s something that could eventually be solved by letting the user provide some simple rules, or by using the history of past transactions to feed into a simple learning classifier. Beancount does not currently provide a mechanism to automatically categorize transactions. You can build this into your importer code. I want to provide a hook for the user to register a completion function that could run across all the importers where you could hook that code in.","title":"Automatic Categorization"},{"location":"importing_external_data.html#cleaning-up-payees","text":"The payees that one can find in the downloads are usually ugly names: They are sometimes the legal names of the business, which often does not reflect the street name of the place you went, for various reasons. For example, I recently ate at a restaurant called the \u201cLucky Bee\u201d in New York, and the memo from the OFX file was \u201cKING BEE\u201d. The names are sometimes abbreviated or contain some crud. In the previous example, the actual memo was \u201cKING BEE NEW YO\u201d, where \u201cNEW YO\u201d is a truncated location string. The amount of ugliness is inconsistent between data sources. It would be nice to be able to normalize the payee names by translating them at import time. I think you can do most of it using some simple rules mapping regular expressions to names provided by the user. There\u2019s really no good automated way to obtain the \u201cclean name\u201d of the payee. Beancount does not provide a hook for letting you do this this yet. It will eventually. You could also build a plugin to rename those accounts when loading your ledger. I\u2019ll build that too\u2014it\u2019s easy and would result in much nicer output.","title":"Cleaning up Payees"},{"location":"importing_external_data.html#future-work","text":"A list of things I\u2019d really want to add, beyond fortifying what\u2019s already there: A generic, configurable CSV importer which you can instantiate. I plan to play with this a bit and build a sniffer that could automatically figure out the role of each column. A hook to allow you to register a callback for post-processing transactions that works across all importers.","title":"Future Work"},{"location":"importing_external_data.html#related-discussion-threads","text":"Getting started; assigning accounts to bank .csv data Status of LedgerHub\u2026 how can I get started? Rekon wants your CSV files","title":"Related Discussion Threads"},{"location":"importing_external_data.html#historical-note","text":"There once was a first implementation of the process described in this document. The project was called LedgerHub and has been decommissioned in February 2016, rewritten and the resulting code integrated in Beancount itself, into this beancount.ingest library. The original project was intended to include the implementation of various importers to share them with other people, but this sharing was not very successful, and so the rewrite includes only the scaffolding for building your own importers and invoking them, and only a very limited number of example importer implementations. Documents about LedgerHub are preserved, and can help you understand the origins and design choices for Beancount\u2019s importer support. They can be found here: Original design Original instructions & final status (the old version of this doc) An analysis of the reasons why it the project was terminated (post-mortem) There are essentially three conceptual modes of entering such transactions: (1) a user crafts a single transaction manually, (2) another where a user inputs the two sides as a single transaction to transfer accounts, and (3) the two separate transactions get merged into a single one automatically. These are dual modes of each other. The twist in this story is that the same transaction often posts at different dates in each of its accounts. Beancount currently [March 2016] does not support multiple dates for a single transaction\u2019s postings, but a discussion is ongoing to implement support for these input modes. See this document for more details. \u21a9 The closest to universal downloader you will find in the free software world is ofxclient for OFX files, and in the commercial world, Yodlee provides a service that connects to many financial institutions. \u21a9 The \u2018pdftotext\u2019 utility in poppler provides the useful \u2018-layout\u2019 flag which outputs a text file without mangling tables, which can be helpful in the normal case of \u2018transaction-per-row\u2019 \u21a9 After sending them a few detailed emails about this and getting no response nor seeing any change in the downloaded files, I have given up on them fixing the issue. \u21a9 As you can see, this process is partly why I don\u2019t share my importers code. It requires the storage of way too much personal data in order to keep them working. \u21a9 I don\u2019t really understand why, since opening them up for viewing is almost instant, but nearly all the tools to convert them to other formats are vastly slower. \u21a9 I personally much prefer Mercurial for the clarity of its commands and output and its extensibility, but an advantage of Git\u2019s storage model is that moving files within it comes for free (no extra copy is stored). Moving files in a Mercurial repository costs you a bit in storage space. And if you rename accounts or change how you organize your files you will end up potentially copying many large files. \u21a9","title":"Historical Note"},{"location":"installing_beancount.html","text":"Installing Beancount \uf0c1 Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/install Instructions for downloading and installing Beancount on your computer. Releases \uf0c1 Beancount is a mature project: the first version was written in 2008. The current version of Beancount \u2014 branch \"v3\" \u2014 is stable and under continued maintenance and development. There is a mailing-list and a PyPI page. The \"master\" branch is the development branch. (Note: If you used the \"v2\" branch in the past, many of the tools have been removed from branch \"v3\" and moved to their own dedicated github projects under http://github.com/beancount . Some of the tools, e.g. bean-report, bean-web, have been deprecated.) Where to Get It \uf0c1 This is the official location for the source code: https://github.com/beancount/beancount Download it like this, by using Git to make a clone on your machine: git clone https://github.com/beancount/beancount How to Install \uf0c1 Installing Python \uf0c1 Beancount uses Python 3.8 or above, which is a pretty recent version of Python (as of this writing), and a few common library dependencies. I try to minimize dependencies, but you do have to install a few. This is very easy. First, you should have a working Python install. Install the latest stable version >=3.8 using the download from python.org . Make sure you have the development headers and libraries installed as well (e.g., the \u201cPython.h\u201d header file). For example, on a Debian/Ubuntu system you would install the python3-dev package. Beancount supports setuptools since Feb 2016, and you will need to install dependencies. You will want to have the \u201cpip3\u201d tool installed. It\u2019s installed by default along with Python3 by now\u2014test this out by invoking \u201cpython3 -m pip --help\u201d command. Installing Beancount \uf0c1 Installing Beancount using pip \uf0c1 This is the easiest way to install Beancount. You just install Beancount using sudo -H python3 -m pip install beancount This should automatically download and install all the dependencies. Note, however, that this will install the latest version that was pushed to the PyPI repository and not the very latest version available from source. Releases to PyPI are made sporadically but frequently enough not to be too far behind. Consult the CHANGES file if you\u2019d like to find out what is not included since the release date. Installing Beancount using pip from the Repository \uf0c1 You can also use pip to install Beancount from its source code repository directly: sudo -H python3 -m pip install git+https://github.com/beancount/beancount#egg=beancount Installing Beancount from Source \uf0c1 Installing from source offers the advantage of providing you with the very latest version of the stable branch (\u201cmaster\u201d). The master branch should be as stable as the released version most of the time. Get the source code from the official repository: git clone https://github.com/beancount/beancount You might need to install some non-Python library dependencies, such as bison and flex and perhaps a few more (you'll find out when you try to build). It should be obvious what\u2019s missing. If on Ubuntu, use apt get to install those. If installing on Windows, see the Windows section below. Build and Install Beancount from source using pip3 \uf0c1 You can then install all the dependencies and Beancount itself using pip: cd beancount sudo -H python3 -m pip install . Installing for Development \uf0c1 If you want to execute the source in-place for making changes to it, you can use the setuptools \u201cdevelop\u201d command to point to it: cd beancount sudo python3 setup.py develop Warning: This modifies a .pth file in your Python installation to point to the path to your clone. You may or may not want this. I don't do this myself; the way I work with it is the \"old school\" way; I just build it locally and modify my shell's environment to find its libraries. You build it like this: cd beancount python3 setup.py build_ext -i # or \"make build\" and then both the PATH and PYTHONPATH environment variables need to be updated for it like this: export PATH=$PATH:/path/to/beancount/bin export PYTHONPATH=$PYTHONPATH:/path/to/beancount Dependencies for Development \uf0c1 Beancount needs a few more tools for development. If you\u2019re reading this, you\u2019re a developer, so I\u2019ll assume you can figure out how to install packages, skip the detail, and just list what you might need: pytest: for unit tests ruff: for linting GNU flex : This lexer generator is needed if you intend to modify the lexer. It generated C code that chops up the input files into tokens for the parser to consume. GNU bison : This old-school parser generator is needed if you intend to modify the grammar. It generates C code that parses the tokens generated by flex. (I like using old tools like this because they are pretty low on dependencies, just C code. It should work everywhere.)` python-dateutil : to run the beancount.scripts.example example generator script. Installing from Distribution Packages \uf0c1 Various distributions may package Beancount. Here are links to those known to exist: Arch: https://aur.archlinux.org/packages/beancount/ Windows Installation \uf0c1 Native \uf0c1 Installing this package by pip requires compiling some C++ code during the installation procedure which is only possible if an appropriate compiler is available on the computer, otherwise you will receive an error message about missing vsvarsall.bat or cl.exe . To be able to compile C++ code for Python you will need to install the same major version of the C++ compiler as your Python installation was compiled with. By running python in a console and looking for a text similar to [MSC v.1900 64 bit (AMD64)] you can determine which compiler was used for your particular Python distribution. In this example it is v.1900 . Using this number find the required Visual C++ version here . Since different versions seem to be compatible as long as the first two digits are the same you can in theory use any Visual C++ compiler between 1900 and 1999. According to my experience both Python 3.8 and 3.6 was compiled with MSC v.1900 so you can do either of the following to satisfy this requirement: Install the standalone Build Tools for Visual Studio 2017 or Install the standalone Visual C++ Build Tools 2015 or Modify an existing Visual Studio 2017 installation Start the Visual Studio 2017 installer from Add or remove programs Select Individual components Check VC++ 2017 version 15.9 v14.16 latest v141 tools or newer under Compilers, build tools, and runtimes Install Visual Studio 2019 add C++ build tools: C++ core features, MSVC v142 build tools If cl.exe is not in your path after installation, run Developer Command Prompt for Visual Studio and run the commands there. With Cygwin \uf0c1 Windows installation is, of course, a bit different. It\u2019s a breeze if you use Cygwin. You just have to prep your machine first. Here\u2019s how. Install the latest Cygwin . This may take a while (it downloads a lot of stuff), but it is well worth it in any case. But before you kick off the install, make sure the following packages are all manually enabled in the interface provided by setup.exe (they\u2019re not selected by default): python3 python3-devel python3-setuptools git make gcc-core flex bison Start a new Cygwin bash shell (there should be a new icon on your desktop) and install the pip3 installer tool by running this command: easy_install-3.4 pip Make sure you invoke the version of easy_install which matches your Python version, e.g. easy_install-3.8 if you have Python 3.8 installed, or more. At this point, you should be able to follow the instructions from the previous sections as is, starting from \u201cInstall Beancount using pip\u201d. With WSL \uf0c1 The newly released Windows 10 Anniversary Update brings WSL 'Windows Subsystem for Linux' with bash on Ubuntu on Windows (installation instructions and more at https://msdn.microsoft.com/commandline/wsl/about ). This makes beancount installation easy, from bash: sudo apt-get install python3-pip sudo pip3 install m3-cdecimal sudo pip3 install beancount --pre This is not totally \u201cWindows compatible\u201d, as it is running in a pico-process, but provides a convenient way to get the Linux command-line experience on Windows. (Contrib: willwray) Checking your Install \uf0c1 You should be able to run the binaries from this document . For example, running bean-check should produce something like this: $ bean-check usage: bean-check [-h] [-v] filename bean-check: error: the following arguments are required: filename If this works, you can now go to the tutorial and begin learning how Beancount works. Reporting Problems \uf0c1 If you need to report a problem, either send email on the mailing-list or file a ticket on Github. Running the following command lists the presence and versions of dependencies installed on your computer and it might be useful to include the output of this command in your bug report: python3 -m beancount.scripts.deps Editor Support \uf0c1 There is support for some editors available: Emacs support is provided in a separate repo . See the Getting Started text for installation instruction. Support for editing with Sublime has been contributed by Martin Andreas Andersen . See his github repo . Support for editing with Vim has been contributed by Nathan Grigg . See his GitHub repo . Visual Studio Code currently has two extensions available. Both have been tested on Linux. Beancount , with syntax checking (bean-check) and support for accounts, currencies, etc. It not only allows selecting existing open accounts but also displays their balance and other metadata. Quite helpful! Beancount Formatter , which can format the whole document, aligning the numbers, etc. using bean-format. If You Have Problems \uf0c1 If you run into any installation problems, file a ticket or email the mailing-list . Post-Installation Usage \uf0c1 Normally the scripts located under beancount/bin should be automatically installed somewhere in your executable path. For example, you should be able to just run \u201cbean-check\u201d on the command-line.","title":"Installing Beancount"},{"location":"installing_beancount.html#installing-beancount","text":"Martin Blais - Updated: June 2024 http://furius.ca/beancount/doc/install Instructions for downloading and installing Beancount on your computer.","title":"Installing Beancount"},{"location":"installing_beancount.html#releases","text":"Beancount is a mature project: the first version was written in 2008. The current version of Beancount \u2014 branch \"v3\" \u2014 is stable and under continued maintenance and development. There is a mailing-list and a PyPI page. The \"master\" branch is the development branch. (Note: If you used the \"v2\" branch in the past, many of the tools have been removed from branch \"v3\" and moved to their own dedicated github projects under http://github.com/beancount . Some of the tools, e.g. bean-report, bean-web, have been deprecated.)","title":"Releases"},{"location":"installing_beancount.html#where-to-get-it","text":"This is the official location for the source code: https://github.com/beancount/beancount Download it like this, by using Git to make a clone on your machine: git clone https://github.com/beancount/beancount","title":"Where to Get It"},{"location":"installing_beancount.html#how-to-install","text":"","title":"How to Install"},{"location":"installing_beancount.html#installing-python","text":"Beancount uses Python 3.8 or above, which is a pretty recent version of Python (as of this writing), and a few common library dependencies. I try to minimize dependencies, but you do have to install a few. This is very easy. First, you should have a working Python install. Install the latest stable version >=3.8 using the download from python.org . Make sure you have the development headers and libraries installed as well (e.g., the \u201cPython.h\u201d header file). For example, on a Debian/Ubuntu system you would install the python3-dev package. Beancount supports setuptools since Feb 2016, and you will need to install dependencies. You will want to have the \u201cpip3\u201d tool installed. It\u2019s installed by default along with Python3 by now\u2014test this out by invoking \u201cpython3 -m pip --help\u201d command.","title":"Installing Python"},{"location":"installing_beancount.html#installing-beancount_1","text":"","title":"Installing Beancount"},{"location":"installing_beancount.html#installing-beancount-using-pip","text":"This is the easiest way to install Beancount. You just install Beancount using sudo -H python3 -m pip install beancount This should automatically download and install all the dependencies. Note, however, that this will install the latest version that was pushed to the PyPI repository and not the very latest version available from source. Releases to PyPI are made sporadically but frequently enough not to be too far behind. Consult the CHANGES file if you\u2019d like to find out what is not included since the release date.","title":"Installing Beancount using pip"},{"location":"installing_beancount.html#installing-beancount-using-pip-from-the-repository","text":"You can also use pip to install Beancount from its source code repository directly: sudo -H python3 -m pip install git+https://github.com/beancount/beancount#egg=beancount","title":"Installing Beancount using pip from the Repository"},{"location":"installing_beancount.html#installing-beancount-from-source","text":"Installing from source offers the advantage of providing you with the very latest version of the stable branch (\u201cmaster\u201d). The master branch should be as stable as the released version most of the time. Get the source code from the official repository: git clone https://github.com/beancount/beancount You might need to install some non-Python library dependencies, such as bison and flex and perhaps a few more (you'll find out when you try to build). It should be obvious what\u2019s missing. If on Ubuntu, use apt get to install those. If installing on Windows, see the Windows section below.","title":"Installing Beancount from Source"},{"location":"installing_beancount.html#build-and-install-beancount-from-source-using-pip3","text":"You can then install all the dependencies and Beancount itself using pip: cd beancount sudo -H python3 -m pip install .","title":"Build and Install Beancount from source using pip3"},{"location":"installing_beancount.html#installing-for-development","text":"If you want to execute the source in-place for making changes to it, you can use the setuptools \u201cdevelop\u201d command to point to it: cd beancount sudo python3 setup.py develop Warning: This modifies a .pth file in your Python installation to point to the path to your clone. You may or may not want this. I don't do this myself; the way I work with it is the \"old school\" way; I just build it locally and modify my shell's environment to find its libraries. You build it like this: cd beancount python3 setup.py build_ext -i # or \"make build\" and then both the PATH and PYTHONPATH environment variables need to be updated for it like this: export PATH=$PATH:/path/to/beancount/bin export PYTHONPATH=$PYTHONPATH:/path/to/beancount","title":"Installing for Development"},{"location":"installing_beancount.html#dependencies-for-development","text":"Beancount needs a few more tools for development. If you\u2019re reading this, you\u2019re a developer, so I\u2019ll assume you can figure out how to install packages, skip the detail, and just list what you might need: pytest: for unit tests ruff: for linting GNU flex : This lexer generator is needed if you intend to modify the lexer. It generated C code that chops up the input files into tokens for the parser to consume. GNU bison : This old-school parser generator is needed if you intend to modify the grammar. It generates C code that parses the tokens generated by flex. (I like using old tools like this because they are pretty low on dependencies, just C code. It should work everywhere.)` python-dateutil : to run the beancount.scripts.example example generator script.","title":"Dependencies for Development"},{"location":"installing_beancount.html#installing-from-distribution-packages","text":"Various distributions may package Beancount. Here are links to those known to exist: Arch: https://aur.archlinux.org/packages/beancount/","title":"Installing from Distribution Packages"},{"location":"installing_beancount.html#windows-installation","text":"","title":"Windows Installation"},{"location":"installing_beancount.html#native","text":"Installing this package by pip requires compiling some C++ code during the installation procedure which is only possible if an appropriate compiler is available on the computer, otherwise you will receive an error message about missing vsvarsall.bat or cl.exe . To be able to compile C++ code for Python you will need to install the same major version of the C++ compiler as your Python installation was compiled with. By running python in a console and looking for a text similar to [MSC v.1900 64 bit (AMD64)] you can determine which compiler was used for your particular Python distribution. In this example it is v.1900 . Using this number find the required Visual C++ version here . Since different versions seem to be compatible as long as the first two digits are the same you can in theory use any Visual C++ compiler between 1900 and 1999. According to my experience both Python 3.8 and 3.6 was compiled with MSC v.1900 so you can do either of the following to satisfy this requirement: Install the standalone Build Tools for Visual Studio 2017 or Install the standalone Visual C++ Build Tools 2015 or Modify an existing Visual Studio 2017 installation Start the Visual Studio 2017 installer from Add or remove programs Select Individual components Check VC++ 2017 version 15.9 v14.16 latest v141 tools or newer under Compilers, build tools, and runtimes Install Visual Studio 2019 add C++ build tools: C++ core features, MSVC v142 build tools If cl.exe is not in your path after installation, run Developer Command Prompt for Visual Studio and run the commands there.","title":"Native"},{"location":"installing_beancount.html#with-cygwin","text":"Windows installation is, of course, a bit different. It\u2019s a breeze if you use Cygwin. You just have to prep your machine first. Here\u2019s how. Install the latest Cygwin . This may take a while (it downloads a lot of stuff), but it is well worth it in any case. But before you kick off the install, make sure the following packages are all manually enabled in the interface provided by setup.exe (they\u2019re not selected by default): python3 python3-devel python3-setuptools git make gcc-core flex bison Start a new Cygwin bash shell (there should be a new icon on your desktop) and install the pip3 installer tool by running this command: easy_install-3.4 pip Make sure you invoke the version of easy_install which matches your Python version, e.g. easy_install-3.8 if you have Python 3.8 installed, or more. At this point, you should be able to follow the instructions from the previous sections as is, starting from \u201cInstall Beancount using pip\u201d.","title":"With Cygwin"},{"location":"installing_beancount.html#with-wsl","text":"The newly released Windows 10 Anniversary Update brings WSL 'Windows Subsystem for Linux' with bash on Ubuntu on Windows (installation instructions and more at https://msdn.microsoft.com/commandline/wsl/about ). This makes beancount installation easy, from bash: sudo apt-get install python3-pip sudo pip3 install m3-cdecimal sudo pip3 install beancount --pre This is not totally \u201cWindows compatible\u201d, as it is running in a pico-process, but provides a convenient way to get the Linux command-line experience on Windows. (Contrib: willwray)","title":"With WSL"},{"location":"installing_beancount.html#checking-your-install","text":"You should be able to run the binaries from this document . For example, running bean-check should produce something like this: $ bean-check usage: bean-check [-h] [-v] filename bean-check: error: the following arguments are required: filename If this works, you can now go to the tutorial and begin learning how Beancount works.","title":"Checking your Install"},{"location":"installing_beancount.html#reporting-problems","text":"If you need to report a problem, either send email on the mailing-list or file a ticket on Github. Running the following command lists the presence and versions of dependencies installed on your computer and it might be useful to include the output of this command in your bug report: python3 -m beancount.scripts.deps","title":"Reporting Problems"},{"location":"installing_beancount.html#editor-support","text":"There is support for some editors available: Emacs support is provided in a separate repo . See the Getting Started text for installation instruction. Support for editing with Sublime has been contributed by Martin Andreas Andersen . See his github repo . Support for editing with Vim has been contributed by Nathan Grigg . See his GitHub repo . Visual Studio Code currently has two extensions available. Both have been tested on Linux. Beancount , with syntax checking (bean-check) and support for accounts, currencies, etc. It not only allows selecting existing open accounts but also displays their balance and other metadata. Quite helpful! Beancount Formatter , which can format the whole document, aligning the numbers, etc. using bean-format.","title":"Editor Support"},{"location":"installing_beancount.html#if-you-have-problems","text":"If you run into any installation problems, file a ticket or email the mailing-list .","title":"If You Have Problems"},{"location":"installing_beancount.html#post-installation-usage","text":"Normally the scripts located under beancount/bin should be automatically installed somewhere in your executable path. For example, you should be able to just run \u201cbean-check\u201d on the command-line.","title":"Post-Installation Usage"},{"location":"installing_beancount_v3.html","text":"Installing Beancount (C++ version) \uf0c1 Martin Blais - July 2020 http://furius.ca/beancount/doc/v3-install Instructions for downloading and running Beancount v3 (in development) on your computer. For v2, see this document instead: Beancount - Install (v2) This document is about Beancount v3, an experimental in-development version (as of July 2020); Instructions for building the stable version (Beancount v2) can be found in this other document . Setup Python \uf0c1 Python dependencies are still required to run programs. pip install \u2013r requirements/dev.txt Building with Bazel \uf0c1 Warning: This is an experimental development branch. Do not expect everything to be polished perfectly. Bazel Dependencies \uf0c1 Beancount v3 uses the Bazel build system, which for the most part insulates you from local installs and dependencies from your computer. The dependencies to install are: Bazel itself. Follow instructions on https://bazel.build/ A C++ compiler. Either g++ or clang works. I'm using clang-11. A Python runtime (version 3.8 or above). Install from your distribution. Bazel will download and compile all the libraries it requires itself (even the code generators, e.g., Bison), building them at their precise versions as specified in the build, so you will not have to worry about them. Building & Testing \uf0c1 Simply run the following command: bazel build //bin:all There is currently no installation script, you have to run from source. You can run individual programs (e.g. bean-check) with this command: bazel run //bin:bean_check -- /path/to/ledger.beancount Or if you don't care to automatically rebuild modified code, like this: ./bazel-bin/bin/bean_check /path/to/ledger.beancount Development \uf0c1 You can run all the unit tests like this: bazel test //... Because Bazel has a detailed account of all dependencies, re-running this command after modifying code will result in only the touched targets being re-tested; this makes iterative development with testing a bit more fun. Another advantage is that since all the libraries the build depends on are downloaded and built, while this can be slow on the first build, it allows us to use very recently released versions of the code we depend on. Targets are defined in BUILD files local to their directories. All the build rules can be found under //third_party. Ingestion \uf0c1 The ingestion code involves importing code that lives outside the repository. Bazel binaries are self-contained and will fail to import modules that haven't been declared as dependencies, so running the //bin:bean_extract target, for example, probably won't work. This does not work yet (short of building your import configuration as a py_binary() target that would explicitly link to Beancount). This is doable without writing much Bazel code by defining a suitable WORKSPACE file that fetches the rules from it. I haven't produced an example of this yet (TBD). As a workaround, you can set your PYTHONPATH to import from the source location and create a symlink to the parser .so file beforehand. You can do it like this: make bazel-link TBD \uf0c1 A few build integration tasks remain to be done: pytype is not supported yet. pylint is not integrated in the build either. Installation for development with meson \uf0c1 Note : this section is updated base on the following discussion: https://groups.google.com/g/beancount/c/7ppbyz_5B5w The Bazel build for Beancount v3 builds some experimental C++ code that is at the time of writing (9 March 2024) not yet used for anything else than a technology demonstration. In \u201cproduction\u201d v3 uses meson-python to build the extension modules and pack them up in a Python wheel. This is what is used by pip during installation. This section describes installation processes for the purposes of development beancount v3 python code, rather than experimenting with C++ code. On Linux \uf0c1 Tested with python3.12 on Ubuntu Related links https://mesonbuild.com/meson-python/how-to-guides/editable-installs.html git clone https://github.com/beancount/beancount.git Create and activate virtual environment python3.12 -m venv beancount/venv . beancount/venv/bin/activate cd beancount Install required packages. python -m pip install meson-python meson ninja Install beancount in editable mode with no build isolation python -m pip install --no-build-isolation --editable . Note: There is an opinion that --no-build-isolation option is not needed, but it was also mentioned that the installation wasn\u2019t working without this option. Also the documentation suggests that this option is needed. This may depend on the type of Linux environment Install pytest python -m pip install pytest Run the tests and make sure everything is ok: pytest --import-mode=importlib beancount/ On Windows \uf0c1 Tested on 64 bit Windows 10 Pro prerequisites Install Microsoft Visual Studio (tested on v 2022) Procedure git clone https://github.com/beancount/beancount.git cd beancount If running x64 bit Windows, start the \"x64 Native Tools Command Prompt for VS 20XX\". To do this press and release the Windows keys and type x64 Open this prompt Go to the directory, where beancount is installed C:\\Program Files\\Microsoft Visual Studio\\2022\\Community>cd C:\\_code\\t\\beancount C:\\_code\\t\\beancount> Activate the virtual environment C:\\_code\\t\\beancount>venv\\Scripts\\Activate (venv) C:\\_code\\t\\beancount> Instal required packages (venv) C:\\_code\\t\\beancount>py -m pip install meson-python meson ninja Install beancount in editable mode. Unlike on Linux the --no-build-isolation on Windows from one side throws some errors from the other side does not seem to be needed (venv) C:\\_code\\t\\beancount> py -m pip install --editable . Install pytest (venv) C:\\_code\\t\\beancount>py -m pip install pytest Run tests (venv) C:\\_code\\t\\beancount>pytest --import-mode=importlib beancount Note: some of the tests on Windows fail, but this is due to general portability issue","title":"Installing Beancount"},{"location":"installing_beancount_v3.html#installing-beancount-c-version","text":"Martin Blais - July 2020 http://furius.ca/beancount/doc/v3-install Instructions for downloading and running Beancount v3 (in development) on your computer. For v2, see this document instead: Beancount - Install (v2) This document is about Beancount v3, an experimental in-development version (as of July 2020); Instructions for building the stable version (Beancount v2) can be found in this other document .","title":"Installing Beancount (C++ version)"},{"location":"installing_beancount_v3.html#setup-python","text":"Python dependencies are still required to run programs. pip install \u2013r requirements/dev.txt","title":"Setup Python"},{"location":"installing_beancount_v3.html#building-with-bazel","text":"Warning: This is an experimental development branch. Do not expect everything to be polished perfectly.","title":"Building with Bazel"},{"location":"installing_beancount_v3.html#bazel-dependencies","text":"Beancount v3 uses the Bazel build system, which for the most part insulates you from local installs and dependencies from your computer. The dependencies to install are: Bazel itself. Follow instructions on https://bazel.build/ A C++ compiler. Either g++ or clang works. I'm using clang-11. A Python runtime (version 3.8 or above). Install from your distribution. Bazel will download and compile all the libraries it requires itself (even the code generators, e.g., Bison), building them at their precise versions as specified in the build, so you will not have to worry about them.","title":"Bazel Dependencies"},{"location":"installing_beancount_v3.html#building-testing","text":"Simply run the following command: bazel build //bin:all There is currently no installation script, you have to run from source. You can run individual programs (e.g. bean-check) with this command: bazel run //bin:bean_check -- /path/to/ledger.beancount Or if you don't care to automatically rebuild modified code, like this: ./bazel-bin/bin/bean_check /path/to/ledger.beancount","title":"Building & Testing"},{"location":"installing_beancount_v3.html#development","text":"You can run all the unit tests like this: bazel test //... Because Bazel has a detailed account of all dependencies, re-running this command after modifying code will result in only the touched targets being re-tested; this makes iterative development with testing a bit more fun. Another advantage is that since all the libraries the build depends on are downloaded and built, while this can be slow on the first build, it allows us to use very recently released versions of the code we depend on. Targets are defined in BUILD files local to their directories. All the build rules can be found under //third_party.","title":"Development"},{"location":"installing_beancount_v3.html#ingestion","text":"The ingestion code involves importing code that lives outside the repository. Bazel binaries are self-contained and will fail to import modules that haven't been declared as dependencies, so running the //bin:bean_extract target, for example, probably won't work. This does not work yet (short of building your import configuration as a py_binary() target that would explicitly link to Beancount). This is doable without writing much Bazel code by defining a suitable WORKSPACE file that fetches the rules from it. I haven't produced an example of this yet (TBD). As a workaround, you can set your PYTHONPATH to import from the source location and create a symlink to the parser .so file beforehand. You can do it like this: make bazel-link","title":"Ingestion"},{"location":"installing_beancount_v3.html#tbd","text":"A few build integration tasks remain to be done: pytype is not supported yet. pylint is not integrated in the build either.","title":"TBD"},{"location":"installing_beancount_v3.html#installation-for-development-with-meson","text":"Note : this section is updated base on the following discussion: https://groups.google.com/g/beancount/c/7ppbyz_5B5w The Bazel build for Beancount v3 builds some experimental C++ code that is at the time of writing (9 March 2024) not yet used for anything else than a technology demonstration. In \u201cproduction\u201d v3 uses meson-python to build the extension modules and pack them up in a Python wheel. This is what is used by pip during installation. This section describes installation processes for the purposes of development beancount v3 python code, rather than experimenting with C++ code.","title":"Installation for development with meson"},{"location":"installing_beancount_v3.html#on-linux","text":"Tested with python3.12 on Ubuntu Related links https://mesonbuild.com/meson-python/how-to-guides/editable-installs.html git clone https://github.com/beancount/beancount.git Create and activate virtual environment python3.12 -m venv beancount/venv . beancount/venv/bin/activate cd beancount Install required packages. python -m pip install meson-python meson ninja Install beancount in editable mode with no build isolation python -m pip install --no-build-isolation --editable . Note: There is an opinion that --no-build-isolation option is not needed, but it was also mentioned that the installation wasn\u2019t working without this option. Also the documentation suggests that this option is needed. This may depend on the type of Linux environment Install pytest python -m pip install pytest Run the tests and make sure everything is ok: pytest --import-mode=importlib beancount/","title":"On Linux"},{"location":"installing_beancount_v3.html#on-windows","text":"Tested on 64 bit Windows 10 Pro prerequisites Install Microsoft Visual Studio (tested on v 2022) Procedure git clone https://github.com/beancount/beancount.git cd beancount If running x64 bit Windows, start the \"x64 Native Tools Command Prompt for VS 20XX\". To do this press and release the Windows keys and type x64 Open this prompt Go to the directory, where beancount is installed C:\\Program Files\\Microsoft Visual Studio\\2022\\Community>cd C:\\_code\\t\\beancount C:\\_code\\t\\beancount> Activate the virtual environment C:\\_code\\t\\beancount>venv\\Scripts\\Activate (venv) C:\\_code\\t\\beancount> Instal required packages (venv) C:\\_code\\t\\beancount>py -m pip install meson-python meson ninja Install beancount in editable mode. Unlike on Linux the --no-build-isolation on Windows from one side throws some errors from the other side does not seem to be needed (venv) C:\\_code\\t\\beancount> py -m pip install --editable . Install pytest (venv) C:\\_code\\t\\beancount>py -m pip install pytest Run tests (venv) C:\\_code\\t\\beancount>pytest --import-mode=importlib beancount Note: some of the tests on Windows fail, but this is due to general portability issue","title":"On Windows"},{"location":"ledgerhub_design_doc.html","text":"Design Doc for Ledgerhub \uf0c1 Martin Blais , February 2014 http://furius.ca/beancount/doc/ledgerhub-design-doc Motivation Goals & Stages Details of Stages Fetching Identification Extraction Transform Rendering Filing Implementation Details Importers Interface References Please note that this document is the original design doc for LedgerHub. LedgerHub is being transitioned back to Beancount. See this postmortem document for details [blais, 2015-12]. Motivation \uf0c1 Several open source projects currently exist that provide the capability to create double-entry transactions for bookkeeping from a text file input. These various double-entry bookkeeping projects include Beancount , Ledger , HLedger , Abandon , and they are independent implementations of a similar goal: the creation of an in-memory representation for double-entry accounting transactions from a text file, and the production of various reports from it, such as balance sheets, income statements, journals, and others. Each implementation explores slightly different feature sets, but essentially all work by reading their input from a file whose format is custom declarative language that describe the transactions, a language which is meant to be written by humans and whose syntax is designed with that goal in mind. While the languages do vary somewhat, the underlying data structures that they define are fairly similar. An essential part of the process of regularly updating one\u2019s journal files is the replication of a real-world account\u2019s transaction detail to a single input file in a consistent data format. This is essentially a translation step, meant to bring the transaction details of many institutions\u2019 accounts into a single system. Various banks and credit card companies provide downloadable transaction data in either Quicken or Microsoft Money (OFX) formats, and many institutions provide custom CSV files with transaction detail. Moreover, many of these institutions also make regular statements available for download as PDF files, and these can be associated with one\u2019s ledger accounts. The process of translating these external data formats can be automated to some extent. These various files can be translated to output text that can then be massaged by the user to be integrated into input file formats accepted by a double-entry bookkeeping package. Several projects have begun to make inroads in that domain: Ledger-autosync aims at fetching transactions automatically from OFX servers for and translating them for Ledger and HLedger, and Reckon converts CSV files for Ledger. Beancount includes code that can automate the identification of downloaded files to the accounts from a ledger, extract their transaction detail, and automatically file them to a directory hierarchy that mirrors the ledger\u2019s chart of accounts. This code should probably live outside of Beancount. Ledger also sports a \u201cconvert\u201d command that attempts to do similar things and a CSV2Ledger Perl script is available that can convert CSV files. HLedger also had a convert command which translated CSV files with optional conversion hints defined in a separate file; HLedger now does the same conversion on-the-fly when the input file is CSV (i.e., CSV is considered a first-class input format). The programs that fetch and convert external data files do not have to be tied to a single system. Moreover, this is often cumbersome code that would benefit greatly from having a large number of contributors, which could each benefit each other from having common parsers ready and working for the various institutions that they\u2019re using or likely to use in the future. I - the author of Beancount - have decided to move Beancount\u2019s importing and filing source code outside of its home project and to decouple it from the Beancount source code, so that others can contribute to it, with the intent of providing project-agnostic functionality. This document describes the goals and design of this project. Goals & Stages \uf0c1 This new project should address the following aspects in a project-agnostic manner: Fetching : Automate obtaining the external data files by connecting to the data sources directly. External tools and libraries such as ofxclient for OFX sources can be leveraged for this purpose. Web scraping could be used to fetch downloadable files where possible. The output of this stage is a list of institution-specific files downloaded to a directory. Note that fetching does not just apply to transaction data here; we will also support fetching prices . A list of (date, price) entries may be created from this data. We will likely want to support an intermediate format for expressing a list of positions (and appropriate support in the ledgerhub-Ledger/Beancount/HLedger interface to obtain it). Identification : Given a filename and its contents, automatically guess which institution and account configuration the file is for, and ideally be able to extract the date from the file or statement. This should also work with PDF files. The output of this stage is an association of each input file to a particular extractor and configuration (e.g. a particular account name). Extraction : Parse each file (if possible) and extract a list of information required to generate double-entry transactions data structures from it, in some sort of generic data structure, such as dicts of strings and numbers, independent of the underlying project\u2019s desired output. If possible, a verbatim snippet of the original text that generated the transaction should be attached to the output data structure. The output of this stage is a data structure, e.g., a list of Python dictionaries in some defined format. Transform : Given some information from the past transaction history contained in a journal, using simple learning algorithms, a program should be able to apply transformations on the transactional information extracted from the previous step. The most common use case for this is to automatically add a categorization posting to transactions that have a single posting only. For example, transactions from credit card statements typically include the changes in balance of the credit card account but all transactions are left to be manually associated with a particular expense account. Some of this process can be automated at this stage. Rendering : Convert the internal transactions data structures to the particular syntax of a double-entry bookkeeping project implementation and to the particular desired syntax variants (e.g. currency formatting, comma vs. dot decimal separator, localized input date format). This steps spits out text to be inserted into an input file compatible with the ledger software of choice. Filing : Sanitize the downloaded files\u2019 filenames and move them into a well organized and structured directory hierarchy corresponding to the identified account. This can run from the same associations derived in the identification step. Apart from the Render stage, all the other stages should be implemented without regard for a particular project, this should work across all ledger implementations. The Rendering code, however, should specialize, import source code, and attempt to add as many of the particular features provided by each project to its output text. Where necessary, interfaces to obtain particular data sets from each ledger implementation\u2019s input files should be provided to shield the common code from the particular implementation details of that project. For instance, a categorization Transform step would need to train its algorithm on some of the transaction data (i.e., the narration fields and perhaps some of the amounts, account names, and dates). Each project should provide a way to obtain the necessary data from its input data file, in the same format. Details of Stages \uf0c1 Fetching \uf0c1 By default, a user should be able to click their way to their institution\u2019s website and download documents to their ~/Downloads directory. A directory with some files in it should be the reasonable default input to the identification stage. This directory should be allowed to have other/garbage files in it, the identification step should be able to skip those automatically. A module that can automatically fetch the data needs to be implemented. Ideally this would not require an external tool. The data extracted should also have a copy saved in some Downloads directory. This is the domain of the ledger-autosync project. Perhaps we should coordinate input/outputs or even integrate call some of its library code at this stage. The author notes that fetching data from OFX servers is pretty easy, though the begin/end dates will have to get processed and filtered. Automatic fetching support will vary widely depending on where the institutions are located. Some places have solid support, some less. Use the data from ofxhome.com to configure. Fetching Prices \uf0c1 For fetching prices, there are many libraries out there. Initially we will port Beancount\u2019s bean-prices to ledgerhub. Identification \uf0c1 The identification stage consists in running a driver program that Searches for files in a directory hierarchy (typically your ~/Downloads folder) If necessary, converts the files into some text/ascii format, so that regular expressions can be matched against it (even if the output is messy, e.g., with PDF files converted to ASCII). This works well for PDF files: despite the fact that we cannot typically extract transactional data from them, we can generally pretty reliably identify which account they\u2019re for and almost always extract the statement date as well. Check a list of regular expressions against the ASCII\u2019fied contents. If the regular expressions all match, the configuration is associated to the filename. Note that more than one configuration may be associated to the same file because some files contain many sections, sections for which different importers may be called on to extract their data (e.g., OFX banking + OFX credit card can be mixed in the same file, and some institutions do). The net result of this process is an association of each filename with the a specific importer object instantiated in the configuration file. These importer objects are created with a set of required account names which they use to produce the Ledger-like syntax from the downloaded file that was associated with it. Here is an example configuration for two importers: from ledgerhub.sources.rbc import rbcinvesting, rbcpdf CONFIG = [ ... (('FileType: application/vnd.ms-excel', r'Filename: .*Activity-123456789-', ), rbcinvesting.Importer({ 'FILE' : 'Assets:CA:RBC-Investing:Taxable', 'cash' : 'Assets:CA:RBC-Investing:Taxable:Cash', 'positions' : 'Assets:CA:RBC-Investing:Taxable', 'interest' : 'Income:CA:RBC-Investing:Taxable:Interest', 'dividend' : 'Income:CA:RBC-Investing:Taxable:Dividends', 'fees' : 'Expenses:Financial:Fees', 'commission' : 'Expenses:Financial:Commissions', 'transfer' : 'Assets:CA:RBC:Checking', })), (('FileType: application/pdf', 'Filename:.*/123456789-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d.pdf'), rbcpdf.Importer({ 'FILE': 'Assets:CA:RBC-Investing:RRSP', })), The configuration consists in a list, for each possible importer, of a pair of 1) a list of regular expressions which all should match against a \u201cmatch text\u201d, which is a \u201ctextified\u201d version of the contents of a file to be imported, and 2) an importer object, configured with a specific set of accounts to use for producing transactions. Each importer requires a particular set of output accounts which it uses to create its transactions and postings. The ledger\u2019s filename, and a list of these (regexps, importer) pairs is all that is necessary for the driver to carry out all of its work. The textification consists in a simple and imperfect conversion of downloaded file that are in binary format to something that we can run regular expressions against. For an OFX file or CSV file there is no conversion required for textification, we can just match against the text contents of those files; for an Excel/XLS file, we need to convert that to a CSV file, which can then be searched; for a PDF file, a number of different pdf-to-text converters are attempted until one succeeds (the tools for this are notoriously unreliable, so we have to try various ones). Note that this converted \"match text\" is only created temporarily and only for the purpose of identification; the importer will get the original binary file to do its work. It is not entirely clear whether the regular expressions can be standardized to avoid having the user configure them manually. In practice, I have found it often necessary, or at least very convenient, to place an account id in my import configuration. It is true that configuring each of the possible downloads can be a hassle that requires the user to do a bit of guesswork while looking at the contents of each file, but this has been much more reliable in practice than attempts at normalizing this process, likely because it is a much easier problem to uniquely distinguish between all the files of a particular user than to distinguish between all the types of files. Using an account id in one of the regular expressions is the easy way to do that, and it works well. This also provides a clear place to attach the list of accounts to a particular importer, something that necessarily requires user input anyway. Extraction \uf0c1 Once the association is made, we run the importers on each of the files. Some data structure is produced. The importers each do what they do - this is where the ugly tricks go. Ideally, we should build a library of common utilities to help parsing similar file types. Though each of the importer modules should be pretty much independent, some common functionality can be imagined, for example, how one deals with different stocks held in a single investment account, could be configured outside of each importer (e.g., preferred method could be to create a subaccount of that account, with the symbol of the stock, or otherwise). Note [AMaffei]: This could output a generic and well-defined CSV file format if you want to have the option of running the various steps as separate UNIX-style tools and/or process the intermediate files with regular text processing tools. Transform \uf0c1 Some transformations should be independent of importers. In particular, automatically categorizing incomplete transactions is not dependent on which importer created the transaction. I\u2019d like to keep this step as general as possible so that other embellishment steps can be inserted here in the future. Right now, I can only think of the following uses: Auto-categorization of transactions with only a single leg Detection of duplicate transactions: imported files often contain transactions which are already in the ledger; those should be either ignored or marked as such. In practice, this is not as easy as it sounds, because a straightforward date + narration comparison will fail: if the same transaction comes from two input data files, one side always ends up getting merged to the other, and sometimes even the date differs a bit. Some amount of fuzzy matching is required. Normalization of payee names: the imported names of payees are often cut short or include some irrelevant words, such as \u201cLLC\u201d, city names, and/or number codes. It may be desirable to somehow clean those up automatically. This step involves a bootstrapping phase, where we will extract some data from the actual ledger that the transactions are meant to be imported into. We will implement a generic interface that should allow each ledger language implementation to provide relevant data for training. The output data here should be in the same format as its input, so that we can optionally skip this phase. Rendering \uf0c1 An output renderer should be selected by the driver. This is where we convert the extracted data structures to the particular flavor of ledger implementation you\u2019re using. Each of the renderer implementations should be free to import modules from its particular implementation, and we should be careful to constraint these import dependencies to only these modules, to make sure that only a single ledger implementation is required in order for the code to run. Options for rendering style could be defined here, for each renderer, because each of the languages have particularies. [AMaffei] Also, it should be possible to provide a generic renderer that takes printf-style format strings to output in any desired format. Filing \uf0c1 Importers should be able to look at the textified contents of the files and find the file/statement date. This is useful, because we can rename the file by prepending the date of the statement, and the date at which we download the statement or transaction files is rarely the same date at which it was generated. In the case where we are not able to extract a date from the file, we fall back on the filename\u2019s last modified time. A target directory should be provided and we should move each file to the account with which it is associated. For example, a file like this: ~/Downloads/ofx32755.qbo should be moved to a directory .../Assets/US/RBC/Checking/2013-11-27.ofx32755.qbo if it is associated by the identification step with an importer for the Assets:US:RBC:Checking account. For this purpose, all the importers should have a required \u201cfiling\u201d account associated with them. As far as I know only Beancount implements this at the moment, but I suspect this convenient mechanism of organizing and preserving your imported files will be found useful by others. Given a list of directories, Beancount automatically finds those files and using the date in the filename, is able to render links to the files as line items in the journal web pages, and serve their contents when the user clicks on the links. Even without this capability, it can be used to maintain a cache of your documents (I maintain mine in a repository which I sync to an external drive for backup). Implementation Details \uf0c1 Notes about the initial implementation: The implementation of this project will be carried out in Python3. Why Python? The performance of importing and extracting is largely irrelevant, a dynamic language works well for this type of task Parsing in a dynamic language works great, there are many available libraries Python3 is now widely distributed and all desired parsing libraries are commonly available for it at this point All modules should be tested, including testing with sample input. If you want to add a new module, you should need to provide an anonymized sample file for it. We will have to have an automated test suite, because past experience has shown this type of code to be quite brittle and fragile to new and unexpected inputs. It\u2019s easy to write, but it\u2019s also easy to break. In order to test binary files that cannot be anonymized, we will provide the ability to test from match-text instead of from original binary statement PDF. Those files are generally not extractable anyhow and are only there for identification and filing (e.g. a PDF statement, we can\u2019t extract any meaningful data out of those except perhaps for the statement date). There should be a quick way to test a particular importer with a particular downloaded file with zero configuration, even if the output account names are a little wonky. There needs to be clean and readable tracing for what the importers are doing, including a debugging/verbose option. We provide a single function to call as the driver for your own import script. Your configuration is a script / your script is the configuration. You call a function at the end. We will also provide a script that imports a filename and fetches an attribute from it, for those who want a more traditional invocation. We should keep types simples, but use the standard datetime types for dates, decimal.Decimal for numbers, and strings for currencies/commodities. This is obviously based on my current importers code in Beancount. I\u2019m very open to new ideas and suggestions for this project. Collaborations will be most welcome. The more importers we can support, the better. Importers Interface \uf0c1 Each importer should be implemented as a class that derives from this one: class ImporterBase: \"Base class/interface for all source importers.\" # A dict of required configuration variables to their docstring. # This declares the list of options required for the importer # to be provided with, and their meaning. REQUIRED_CONFIG = {} def __init__(self, config): \"\"\"Create an importer. Most concrete implementations can just use this without overriding. Args: config: A dict of configuration accounts, that must match the REQUIRED_CONFIG values. \"\"\" # a dict of Configuration values. This can be accessed publicly. assert isinstance(config, dict) self.config = config # Check that the config has just the required configuration values. if not verify_config(self, config, self.REQUIRED_CONFIG): raise ValueError(\"Invalid config {}, requires {}\".format( config, self.REQUIRED_CONFIG)) def get_filing_account(self): \"\"\"Return the account for moving the input file to. Returns: The name of the account that corresponds to this importer. \"\"\" return self.config['FILE'] def import_file (self, filename): \"\"\"Attempt to import a file. Args: filename: the name of the file to be imported. Returns: A list of new, imported entries extracted from the file. \"\"\" raise NotImplementedError def import_date (self, filename, text_contents): \"\"\"Attempt to obtain a date that corresponds to the given file. Args: filename: the name of the file to extract the date from text_contents: an ASCII text version of the file contents, whatever format it is originally in. Returns: A date object, if successful, or None. \"\"\" raise NotImplementedError For each importer, a detailed explanation of how the original input file on the institution\u2019s website is to be found and downloaded should be provided, to help those find the correct download when adding this importer (some institutions provide a variety of download formats). In addition, a one-line description of the input file support should be provided, so that we can render at runtime a list of the supported file types. References \uf0c1 Other projects with the same goal as importing account data into Ledger are listed here. Ledger\u2019s \u201cconvert\u201d command HLedger with its built-in readers Reckon OFXmate (GUI for ledger-autosync) CSV2Ledger icsv2ledger csv2ledger (seems to lack active maintainers) Update (Nov 2015): This design doc has been implemented and the project is being transitioned back to Beancount. Read the details here .","title":"Ledgerhub Design Doc"},{"location":"ledgerhub_design_doc.html#design-doc-for-ledgerhub","text":"Martin Blais , February 2014 http://furius.ca/beancount/doc/ledgerhub-design-doc Motivation Goals & Stages Details of Stages Fetching Identification Extraction Transform Rendering Filing Implementation Details Importers Interface References Please note that this document is the original design doc for LedgerHub. LedgerHub is being transitioned back to Beancount. See this postmortem document for details [blais, 2015-12].","title":"Design Doc for Ledgerhub"},{"location":"ledgerhub_design_doc.html#motivation","text":"Several open source projects currently exist that provide the capability to create double-entry transactions for bookkeeping from a text file input. These various double-entry bookkeeping projects include Beancount , Ledger , HLedger , Abandon , and they are independent implementations of a similar goal: the creation of an in-memory representation for double-entry accounting transactions from a text file, and the production of various reports from it, such as balance sheets, income statements, journals, and others. Each implementation explores slightly different feature sets, but essentially all work by reading their input from a file whose format is custom declarative language that describe the transactions, a language which is meant to be written by humans and whose syntax is designed with that goal in mind. While the languages do vary somewhat, the underlying data structures that they define are fairly similar. An essential part of the process of regularly updating one\u2019s journal files is the replication of a real-world account\u2019s transaction detail to a single input file in a consistent data format. This is essentially a translation step, meant to bring the transaction details of many institutions\u2019 accounts into a single system. Various banks and credit card companies provide downloadable transaction data in either Quicken or Microsoft Money (OFX) formats, and many institutions provide custom CSV files with transaction detail. Moreover, many of these institutions also make regular statements available for download as PDF files, and these can be associated with one\u2019s ledger accounts. The process of translating these external data formats can be automated to some extent. These various files can be translated to output text that can then be massaged by the user to be integrated into input file formats accepted by a double-entry bookkeeping package. Several projects have begun to make inroads in that domain: Ledger-autosync aims at fetching transactions automatically from OFX servers for and translating them for Ledger and HLedger, and Reckon converts CSV files for Ledger. Beancount includes code that can automate the identification of downloaded files to the accounts from a ledger, extract their transaction detail, and automatically file them to a directory hierarchy that mirrors the ledger\u2019s chart of accounts. This code should probably live outside of Beancount. Ledger also sports a \u201cconvert\u201d command that attempts to do similar things and a CSV2Ledger Perl script is available that can convert CSV files. HLedger also had a convert command which translated CSV files with optional conversion hints defined in a separate file; HLedger now does the same conversion on-the-fly when the input file is CSV (i.e., CSV is considered a first-class input format). The programs that fetch and convert external data files do not have to be tied to a single system. Moreover, this is often cumbersome code that would benefit greatly from having a large number of contributors, which could each benefit each other from having common parsers ready and working for the various institutions that they\u2019re using or likely to use in the future. I - the author of Beancount - have decided to move Beancount\u2019s importing and filing source code outside of its home project and to decouple it from the Beancount source code, so that others can contribute to it, with the intent of providing project-agnostic functionality. This document describes the goals and design of this project.","title":"Motivation"},{"location":"ledgerhub_design_doc.html#goals-stages","text":"This new project should address the following aspects in a project-agnostic manner: Fetching : Automate obtaining the external data files by connecting to the data sources directly. External tools and libraries such as ofxclient for OFX sources can be leveraged for this purpose. Web scraping could be used to fetch downloadable files where possible. The output of this stage is a list of institution-specific files downloaded to a directory. Note that fetching does not just apply to transaction data here; we will also support fetching prices . A list of (date, price) entries may be created from this data. We will likely want to support an intermediate format for expressing a list of positions (and appropriate support in the ledgerhub-Ledger/Beancount/HLedger interface to obtain it). Identification : Given a filename and its contents, automatically guess which institution and account configuration the file is for, and ideally be able to extract the date from the file or statement. This should also work with PDF files. The output of this stage is an association of each input file to a particular extractor and configuration (e.g. a particular account name). Extraction : Parse each file (if possible) and extract a list of information required to generate double-entry transactions data structures from it, in some sort of generic data structure, such as dicts of strings and numbers, independent of the underlying project\u2019s desired output. If possible, a verbatim snippet of the original text that generated the transaction should be attached to the output data structure. The output of this stage is a data structure, e.g., a list of Python dictionaries in some defined format. Transform : Given some information from the past transaction history contained in a journal, using simple learning algorithms, a program should be able to apply transformations on the transactional information extracted from the previous step. The most common use case for this is to automatically add a categorization posting to transactions that have a single posting only. For example, transactions from credit card statements typically include the changes in balance of the credit card account but all transactions are left to be manually associated with a particular expense account. Some of this process can be automated at this stage. Rendering : Convert the internal transactions data structures to the particular syntax of a double-entry bookkeeping project implementation and to the particular desired syntax variants (e.g. currency formatting, comma vs. dot decimal separator, localized input date format). This steps spits out text to be inserted into an input file compatible with the ledger software of choice. Filing : Sanitize the downloaded files\u2019 filenames and move them into a well organized and structured directory hierarchy corresponding to the identified account. This can run from the same associations derived in the identification step. Apart from the Render stage, all the other stages should be implemented without regard for a particular project, this should work across all ledger implementations. The Rendering code, however, should specialize, import source code, and attempt to add as many of the particular features provided by each project to its output text. Where necessary, interfaces to obtain particular data sets from each ledger implementation\u2019s input files should be provided to shield the common code from the particular implementation details of that project. For instance, a categorization Transform step would need to train its algorithm on some of the transaction data (i.e., the narration fields and perhaps some of the amounts, account names, and dates). Each project should provide a way to obtain the necessary data from its input data file, in the same format.","title":"Goals & Stages"},{"location":"ledgerhub_design_doc.html#details-of-stages","text":"","title":"Details of Stages"},{"location":"ledgerhub_design_doc.html#fetching","text":"By default, a user should be able to click their way to their institution\u2019s website and download documents to their ~/Downloads directory. A directory with some files in it should be the reasonable default input to the identification stage. This directory should be allowed to have other/garbage files in it, the identification step should be able to skip those automatically. A module that can automatically fetch the data needs to be implemented. Ideally this would not require an external tool. The data extracted should also have a copy saved in some Downloads directory. This is the domain of the ledger-autosync project. Perhaps we should coordinate input/outputs or even integrate call some of its library code at this stage. The author notes that fetching data from OFX servers is pretty easy, though the begin/end dates will have to get processed and filtered. Automatic fetching support will vary widely depending on where the institutions are located. Some places have solid support, some less. Use the data from ofxhome.com to configure.","title":"Fetching"},{"location":"ledgerhub_design_doc.html#fetching-prices","text":"For fetching prices, there are many libraries out there. Initially we will port Beancount\u2019s bean-prices to ledgerhub.","title":"Fetching Prices"},{"location":"ledgerhub_design_doc.html#identification","text":"The identification stage consists in running a driver program that Searches for files in a directory hierarchy (typically your ~/Downloads folder) If necessary, converts the files into some text/ascii format, so that regular expressions can be matched against it (even if the output is messy, e.g., with PDF files converted to ASCII). This works well for PDF files: despite the fact that we cannot typically extract transactional data from them, we can generally pretty reliably identify which account they\u2019re for and almost always extract the statement date as well. Check a list of regular expressions against the ASCII\u2019fied contents. If the regular expressions all match, the configuration is associated to the filename. Note that more than one configuration may be associated to the same file because some files contain many sections, sections for which different importers may be called on to extract their data (e.g., OFX banking + OFX credit card can be mixed in the same file, and some institutions do). The net result of this process is an association of each filename with the a specific importer object instantiated in the configuration file. These importer objects are created with a set of required account names which they use to produce the Ledger-like syntax from the downloaded file that was associated with it. Here is an example configuration for two importers: from ledgerhub.sources.rbc import rbcinvesting, rbcpdf CONFIG = [ ... (('FileType: application/vnd.ms-excel', r'Filename: .*Activity-123456789-', ), rbcinvesting.Importer({ 'FILE' : 'Assets:CA:RBC-Investing:Taxable', 'cash' : 'Assets:CA:RBC-Investing:Taxable:Cash', 'positions' : 'Assets:CA:RBC-Investing:Taxable', 'interest' : 'Income:CA:RBC-Investing:Taxable:Interest', 'dividend' : 'Income:CA:RBC-Investing:Taxable:Dividends', 'fees' : 'Expenses:Financial:Fees', 'commission' : 'Expenses:Financial:Commissions', 'transfer' : 'Assets:CA:RBC:Checking', })), (('FileType: application/pdf', 'Filename:.*/123456789-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d-\\d\\d\\d\\d[A-Z][a-z][a-z]\\d\\d.pdf'), rbcpdf.Importer({ 'FILE': 'Assets:CA:RBC-Investing:RRSP', })), The configuration consists in a list, for each possible importer, of a pair of 1) a list of regular expressions which all should match against a \u201cmatch text\u201d, which is a \u201ctextified\u201d version of the contents of a file to be imported, and 2) an importer object, configured with a specific set of accounts to use for producing transactions. Each importer requires a particular set of output accounts which it uses to create its transactions and postings. The ledger\u2019s filename, and a list of these (regexps, importer) pairs is all that is necessary for the driver to carry out all of its work. The textification consists in a simple and imperfect conversion of downloaded file that are in binary format to something that we can run regular expressions against. For an OFX file or CSV file there is no conversion required for textification, we can just match against the text contents of those files; for an Excel/XLS file, we need to convert that to a CSV file, which can then be searched; for a PDF file, a number of different pdf-to-text converters are attempted until one succeeds (the tools for this are notoriously unreliable, so we have to try various ones). Note that this converted \"match text\" is only created temporarily and only for the purpose of identification; the importer will get the original binary file to do its work. It is not entirely clear whether the regular expressions can be standardized to avoid having the user configure them manually. In practice, I have found it often necessary, or at least very convenient, to place an account id in my import configuration. It is true that configuring each of the possible downloads can be a hassle that requires the user to do a bit of guesswork while looking at the contents of each file, but this has been much more reliable in practice than attempts at normalizing this process, likely because it is a much easier problem to uniquely distinguish between all the files of a particular user than to distinguish between all the types of files. Using an account id in one of the regular expressions is the easy way to do that, and it works well. This also provides a clear place to attach the list of accounts to a particular importer, something that necessarily requires user input anyway.","title":"Identification"},{"location":"ledgerhub_design_doc.html#extraction","text":"Once the association is made, we run the importers on each of the files. Some data structure is produced. The importers each do what they do - this is where the ugly tricks go. Ideally, we should build a library of common utilities to help parsing similar file types. Though each of the importer modules should be pretty much independent, some common functionality can be imagined, for example, how one deals with different stocks held in a single investment account, could be configured outside of each importer (e.g., preferred method could be to create a subaccount of that account, with the symbol of the stock, or otherwise). Note [AMaffei]: This could output a generic and well-defined CSV file format if you want to have the option of running the various steps as separate UNIX-style tools and/or process the intermediate files with regular text processing tools.","title":"Extraction"},{"location":"ledgerhub_design_doc.html#transform","text":"Some transformations should be independent of importers. In particular, automatically categorizing incomplete transactions is not dependent on which importer created the transaction. I\u2019d like to keep this step as general as possible so that other embellishment steps can be inserted here in the future. Right now, I can only think of the following uses: Auto-categorization of transactions with only a single leg Detection of duplicate transactions: imported files often contain transactions which are already in the ledger; those should be either ignored or marked as such. In practice, this is not as easy as it sounds, because a straightforward date + narration comparison will fail: if the same transaction comes from two input data files, one side always ends up getting merged to the other, and sometimes even the date differs a bit. Some amount of fuzzy matching is required. Normalization of payee names: the imported names of payees are often cut short or include some irrelevant words, such as \u201cLLC\u201d, city names, and/or number codes. It may be desirable to somehow clean those up automatically. This step involves a bootstrapping phase, where we will extract some data from the actual ledger that the transactions are meant to be imported into. We will implement a generic interface that should allow each ledger language implementation to provide relevant data for training. The output data here should be in the same format as its input, so that we can optionally skip this phase.","title":"Transform"},{"location":"ledgerhub_design_doc.html#rendering","text":"An output renderer should be selected by the driver. This is where we convert the extracted data structures to the particular flavor of ledger implementation you\u2019re using. Each of the renderer implementations should be free to import modules from its particular implementation, and we should be careful to constraint these import dependencies to only these modules, to make sure that only a single ledger implementation is required in order for the code to run. Options for rendering style could be defined here, for each renderer, because each of the languages have particularies. [AMaffei] Also, it should be possible to provide a generic renderer that takes printf-style format strings to output in any desired format.","title":"Rendering"},{"location":"ledgerhub_design_doc.html#filing","text":"Importers should be able to look at the textified contents of the files and find the file/statement date. This is useful, because we can rename the file by prepending the date of the statement, and the date at which we download the statement or transaction files is rarely the same date at which it was generated. In the case where we are not able to extract a date from the file, we fall back on the filename\u2019s last modified time. A target directory should be provided and we should move each file to the account with which it is associated. For example, a file like this: ~/Downloads/ofx32755.qbo should be moved to a directory .../Assets/US/RBC/Checking/2013-11-27.ofx32755.qbo if it is associated by the identification step with an importer for the Assets:US:RBC:Checking account. For this purpose, all the importers should have a required \u201cfiling\u201d account associated with them. As far as I know only Beancount implements this at the moment, but I suspect this convenient mechanism of organizing and preserving your imported files will be found useful by others. Given a list of directories, Beancount automatically finds those files and using the date in the filename, is able to render links to the files as line items in the journal web pages, and serve their contents when the user clicks on the links. Even without this capability, it can be used to maintain a cache of your documents (I maintain mine in a repository which I sync to an external drive for backup).","title":"Filing"},{"location":"ledgerhub_design_doc.html#implementation-details","text":"Notes about the initial implementation: The implementation of this project will be carried out in Python3. Why Python? The performance of importing and extracting is largely irrelevant, a dynamic language works well for this type of task Parsing in a dynamic language works great, there are many available libraries Python3 is now widely distributed and all desired parsing libraries are commonly available for it at this point All modules should be tested, including testing with sample input. If you want to add a new module, you should need to provide an anonymized sample file for it. We will have to have an automated test suite, because past experience has shown this type of code to be quite brittle and fragile to new and unexpected inputs. It\u2019s easy to write, but it\u2019s also easy to break. In order to test binary files that cannot be anonymized, we will provide the ability to test from match-text instead of from original binary statement PDF. Those files are generally not extractable anyhow and are only there for identification and filing (e.g. a PDF statement, we can\u2019t extract any meaningful data out of those except perhaps for the statement date). There should be a quick way to test a particular importer with a particular downloaded file with zero configuration, even if the output account names are a little wonky. There needs to be clean and readable tracing for what the importers are doing, including a debugging/verbose option. We provide a single function to call as the driver for your own import script. Your configuration is a script / your script is the configuration. You call a function at the end. We will also provide a script that imports a filename and fetches an attribute from it, for those who want a more traditional invocation. We should keep types simples, but use the standard datetime types for dates, decimal.Decimal for numbers, and strings for currencies/commodities. This is obviously based on my current importers code in Beancount. I\u2019m very open to new ideas and suggestions for this project. Collaborations will be most welcome. The more importers we can support, the better.","title":"Implementation Details"},{"location":"ledgerhub_design_doc.html#importers-interface","text":"Each importer should be implemented as a class that derives from this one: class ImporterBase: \"Base class/interface for all source importers.\" # A dict of required configuration variables to their docstring. # This declares the list of options required for the importer # to be provided with, and their meaning. REQUIRED_CONFIG = {} def __init__(self, config): \"\"\"Create an importer. Most concrete implementations can just use this without overriding. Args: config: A dict of configuration accounts, that must match the REQUIRED_CONFIG values. \"\"\" # a dict of Configuration values. This can be accessed publicly. assert isinstance(config, dict) self.config = config # Check that the config has just the required configuration values. if not verify_config(self, config, self.REQUIRED_CONFIG): raise ValueError(\"Invalid config {}, requires {}\".format( config, self.REQUIRED_CONFIG)) def get_filing_account(self): \"\"\"Return the account for moving the input file to. Returns: The name of the account that corresponds to this importer. \"\"\" return self.config['FILE'] def import_file (self, filename): \"\"\"Attempt to import a file. Args: filename: the name of the file to be imported. Returns: A list of new, imported entries extracted from the file. \"\"\" raise NotImplementedError def import_date (self, filename, text_contents): \"\"\"Attempt to obtain a date that corresponds to the given file. Args: filename: the name of the file to extract the date from text_contents: an ASCII text version of the file contents, whatever format it is originally in. Returns: A date object, if successful, or None. \"\"\" raise NotImplementedError For each importer, a detailed explanation of how the original input file on the institution\u2019s website is to be found and downloaded should be provided, to help those find the correct download when adding this importer (some institutions provide a variety of download formats). In addition, a one-line description of the input file support should be provided, so that we can render at runtime a list of the supported file types.","title":"Importers Interface"},{"location":"ledgerhub_design_doc.html#references","text":"Other projects with the same goal as importing account data into Ledger are listed here. Ledger\u2019s \u201cconvert\u201d command HLedger with its built-in readers Reckon OFXmate (GUI for ledger-autosync) CSV2Ledger icsv2ledger csv2ledger (seems to lack active maintainers) Update (Nov 2015): This design doc has been implemented and the project is being transitioned back to Beancount. Read the details here .","title":"References"},{"location":"precision_tolerances.html","text":"Beancount Precision & Tolerances \uf0c1 Martin Blais , May 2015 http://furius.ca/beancount/doc/tolerances This document describes how Beancount handles the limited precision of numbers in transaction balance checks and balance assertions. It also documents rounding that may occur in inferring numbers automatically. Motivation \uf0c1 Beancount automatically enforces that the amounts on the Postings of Transactions entered in an input file sum up to zero. In order for Beancount to verify this in a realistic way, it must tolerate a small amount of imprecision. This is because Beancount lets you replicate what happens in real world account transactions , and in the real world, institutions round amounts up or down for practical reasons. Here\u2019s an example: Consider the following transaction which consists in a transfer between two accounts denominated in different currencies (US dollars and Euros): 2015-05-01 * \"Transfer from secret Swiss bank account\" Assets:CH:SBS:Checking -9000.00 CHF Assets:US:BofA:Checking 9643.82 USD @ 0.93324 CHF In this example, the exchange rate used was 0.93324 USD/CHF, that is, 0.93324 Swiss Francs per US dollar. This rate was quoted to 5 digits of precision by the bank. A full-precision conversion of 9000.00 CHF / 0.93324 CHF yields 9643.82152501... USD. Similarly, converting the US dollars to Francs using the given rate yields an imprecise result as well: 9643.82 x 0.93324 = 8999.9985768\u2026 . Here is another example where this type of rounding may occur: A transaction for a fractional number of shares of a mutual fund: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD Once again, rounding occurs in this transaction: not only the Net Asset Value of the fund is rounded to its nearest penny value ($37.61), but the number of units is also rounded and accounted for by Vanguard with a fixed number of digits (10.22626 units of VPMBX). And the balance of the entire transaction needs to tolerate some imprecision, whether you compute the value of the shares (10.22626 x $37.61 = $384.6096386 ) or whether you compute the number of shares from the desired dollar amount of the contribution ($384.61 / $37.61 = 10.2262696091 ). From Beancount\u2019s point-of-view, both of the examples above are balancing transactions. Clearly, if we are to try to represent and reproduce the transactions of external accounts to our input file, there needs to be some tolerance in the balance verification algorithm. How Precision is Determined \uf0c1 Beancount attempts to derive the precision from each transaction automatically , from the input, for each Transaction in isolation 1 . Let us inspect our last example again: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD In this transaction, Beancount will infer the tolerance of RGAGX at 5 fractional digits, that is, 0.000005 RGAGX , and USD at 2 fractional digits, that is, 0.005 USD . Note that the tolerance used is half of the last digit of precision provided by the user. This is entirely inferred from the input, without having to fetch any global tolerance declaration. Also note how the precision is calculated separately for each currency . Observe that although we are inferring a tolerance for units of RGAGX, it is actually not used in the balancing of this transaction, because the \u201cweight\u201d of the first posting is in USD (10.22626 x 37.61 = 384.6096386 USD). So what happens here? The weights of each postings are calculated: 384.6096386 USD for the first posting -384.61 USD for the second These are summed together, by currency (there is only USD in the weights of this transaction) which results in a residual value of -0.0003614 USD. This value is compared to the tolerance for units of USD: |-0.0003614| < 0.005, and this transaction balances. Prices and Costs \uf0c1 For the purpose of inferring the tolerance to be used, the price and cost amounts declared on a transaction\u2019s Postings are ignored . This makes sense if you consider that these are usually specified at a higher precision than the base amounts of the postings\u2014and sometimes this extra precision is necessary to make the transaction balance. These should not be used in setting the precision of the whole transaction. For example, in the following transaction: 1999-09-30 * \"Vest ESPP - Bought at discount: 18.5980 USD\" Assets:US:Schwab:ESPP 54 HOOL {21.8800 USD} Income:CA:ESPP:PayContrib -1467.84 CAD @ 0.6842 USD Income:CA:ESPP:Discount -259.03 CAD @ 0.6842 USD The only tolerance inferred here is 0.005 for CAD. (54 HOOL does not yield anything in this case because it is integral; the next section explains this). There is no tolerance inferred for USD, neither from the cost from the first posting (21.8800 USD), nor from the prices of the remaining postings (0.6842 USD). Integer Amounts \uf0c1 For integer amounts in the input, the precision is not inferred to 0.5, that is, this should fail to balance: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD In other words, integer amounts do not contribute a number of digits to the determination of the tolerance for their currency. By default, the tolerance used on amounts without an inferred precision is zero . So in this example, because we cannot infer the precision of USD (recall that the cost is ignored), this transaction will fail to balance, because its residual is non-zero (|-0.0003614| > 0). You can customize what the default tolerance should be for each currency separately and for any currency as well (see section below on how to do this). This treatment of integer amounts implies that the maximum amount of precision that one can specify just by inputting numbers is 0.05 units of the currency, for example, by providing a number such as 10.7 as input 2 . On the other hand, the settings for the default tolerance to use allows specifying arbitrary numbers. Resolving Ambiguities \uf0c1 A case that presents itself rarely is one where multiple different precisions are being input for the same currency. In this case, the largest (coarsest) of the inferred input tolerances is used. For example, if we wanted to track income to more than pennies, we might write this: 1999-08-20 * \"Sell\" Assets:US:BRS:ESPP -81 HOOL {26.3125 USD} Assets:US:BRS:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:CA:ESPP:PnL -10.125 USD The amounts we have for USD in this case are 2141.36, 0.08 and -10.125, which infer tolerances of either 0.005 or 0.0005. We select the coarsest amount: this transaction tolerates an imprecision of 0.005 USD. Default Tolerances \uf0c1 When a transaction\u2019s numbers do not provide enough information to infer a tolerance locally , we fall back to some default tolerance value. As seen in previous examples, this may occur either because (a) the numbers associated with the currency we need it for are integral, or (b) sufficient numbers are simply absent from the input. By default, this default tolerance is zero for all currencies. This can be specified with an option, like this: option \"inferred_tolerance_default\" \"*:0.001\" The default tolerance can be further refined for each currency involved, by providing the currency to the option, like this: option \"inferred_tolerance_default\" \"USD:0.003\" If provided, the currency-specific tolerance will be used over the global value. The general form for this option is: option \"inferred_tolerance_default\" \":\" Just to be clear: this option is only used when the tolerance cannot be inferred. If you have overly large rounding errors and the numbers in your transactions do infer some tolerance value, this value will be ignored (e.g., setting it to a larger number to try to address that fix will not work). If you need to loosen up the tolerance, see the \u201c inferred_tolerance_multiplier \u201d in the next section. (Note: I\u2019ve been considering dedicating a special meta-data field to the Commodity directive for this, but this would break from the invariant that meta-data is only there to be used by users and plugins, so I\u2019ve refrained so far.) Tolerance Multiplier \uf0c1 We\u2019re shown previously that when the tolerance value isn\u2019t provided explicitly, that it is inferred from the numbers on the postings. By default, the smallest digit found on those numbers is divided by half to obtain the tolerance because we assume that the institutions which we\u2019re reproducing the transactions apply rounding and so the error should never be more than half. But in reality, you may find that the rounding errors sometime exceed this value. For this reason, we provide an option to set the multiplier for the inferred tolerance: option \"inferred_tolerance_multiplier\" \"1.2\" This value overrides the default multiplier. In this example, for a transaction with postings only with values such as 24.45 CHF, the inferred tolerance for CHF would be +/- 0.012 CHF. Inferring Tolerances from Cost \uf0c1 There is also a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 / 2 = 0.045 USD and the sum of all such possible rounding errors is calculate for all postings held at cost or converted from a price, and the resulting tolerance is added to the list of candidates used to figure out the tolerance we should use for the given commodity (we use the maximum value of all the inferred tolerances). You turn on the feature like this: option \"infer_tolerance_from_cost\" \"TRUE\" Enabling this flag only makes the tolerances potentially wider, never smaller. Balance Assertions & Padding \uf0c1 There are a few other places where approximate comparisons are needed. Balance assertions also compare two numbers: 2015-05-08 balance Assets:Investments:RGAGX 4.271 RGAGX This asserts that the accumulated balance for this account has 4.271 units of RGAGX, plus or minus 0.001 RGAGX. So accumulated values of 4.270 RGAGX up to 4.272 RGAGX will check as asserted. The tolerance is inferred automatically to be 1 unit of the least significant digit of the number on the balance assertion. If you wanted a looser assertion, you could have declared: 2015-05-08 balance Assets:Investments:RGAGX 4.27 RGAGX This assertion would accept values from 4.26 RGAGX to 4.28 RGAGX. Note that the inferred tolerances are also expanded by the inferred tolerance multiplier discussed above. Tolerances that Trigger Padding \uf0c1 Pad directives automatically insert transactions to bring account balances in-line with a subsequent balance assertion. The insertion only triggers if the balance differs from the expected value, and the tolerance for this to occur behaves exactly the same as for balance assertions. Explicit Tolerances on Balance Assertions \uf0c1 Beancount supports the specification of an explicit tolerance amount, like this: 2015-05-08 balance Assets:Investments:RGAGX 4.271 ~ 0.01 RGAGX This feature was added because of some observed peculiarities in Vanguard investment accounts whereby rounding appears to follow odd rules and balances don\u2019t match. Saving Rounding Error \uf0c1 As we saw previously, transactions don\u2019t have to balance exactly, they allow for a small amount of imprecision. This bothers some people. If you would like to track and measure the residual amounts allowed by the tolerances, Beancount offers an option to automatically insert postings that will make each transaction balance exactly. You enable the feature like this: option \"account_rounding\" \"Equity:RoundingError\" This tells Beancount to insert postings to compensate for the rounding error to an \u201c Equity:RoundingError \u201d account. For example, with the feature enabled, the following transaction: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD will be automatically transformed into this: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD Equity:RoundingError -0.00135 USD You can verify that this transaction balances exactly. If the transaction already balances exactly (this is the case for most transactions) no posting is inserted. Finally, if you require that all accounts be opened explicitly, you should remember to declare the rounding account in your file at an appropriate date, like this: 2000-01-01 open Equity:RoundingError Precision of Inferred Numbers \uf0c1 Beancount is able to infer some missing numbers in the input. For example, the second posting in this transaction is \u201cinterpolated\u201d automatically by Beancount: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash The calculated amount to be inserted from the first posting is -227.2067 USD. Now, you might ask, to which precision is it inserted at? Does it insert 227.2067 USD at the full precision or does the number get rounded to a penny, e.g. 227.21 USD? It depends on the tolerance inferred for that currency. In this example, no tolerance is able to get inferred (there is no USD amount provided other than the cost amount, which is ignored for the purpose of inferring the tolerance), so we have to defer to the default tolerance. If the default tolerance is not overridden in the input file\u2014and therefore is zero\u2014the full precision will be used; no rounding occurs. This will result in the following transaction: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.2067 USD Note that if a tolerance could be inferred from other numbers on that transaction, it would be used for rounding, such as in this example where the Cash posting is rounded to two digits because of the 9.95 USD number on the Commissions posting: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Expenses:Commissions 9.95 USD Assets:Investments:Cash -237.16 USD However, if no inference is possible, and the default tolerance for USD is set to 0.001, the number will be quantized to 0.001 before insertion, that is, 227.207 USD will be stored: option \"default_tolerance\" \"USD:0.001\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Finally, if you enabled the accumulation of rounding error, the posting\u2019s amount will reflect the correct residual, taking into account the rounded amount that was automatically inserted: option \"default_tolerance\" \"USD:0.01\" option \"account_rounding\" \"Equity:RoundingError\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Equity:RoundingError 0.0003 USD Porting Existing Input \uf0c1 The inference of tolerance values from the transaction\u2019s numbers is generally good enough to keep existing files working without changes. There may be new errors appearing in older files once we process them with the method described in this document, but they should either point to previously undetected errors in the input, or be fixable with simple addition of a suitable number of digits. As a testimony, porting the author\u2019s very large input file has been a relatively painless process that took less than 1 hour. In order to ease the transition, you will probably want to change the default tolerance for all currencies to match the previous value that Beancount had been using, like this: option \"inferred_tolerance_default\" \"*:0.005\" I would recommend you start with this and fix all errors in your file, then proceed to removing this and fix the rest of errors. This should make it easier to adapt your file to this new behavior. As an example of how to fix a new error\u2026 converting this newly failing transaction from the Integer Amounts section: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD by inserting zero\u2019s to provide a locally inferred value like this: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.00 USD is sufficient to silence the balance check. Representational Issues \uf0c1 Internally, Beancount uses a decimal number representation (not a binary/float representation, neither rational numbers). Calculations that result in a large number of fractional digits are carried out to 28 decimal places (the default precision from the context of Python\u2019s IEEE decimal implementation). This is plenty sufficient, because the method we propose above rarely trickles these types of numbers throughout the system: the tolerances allows us to post the precise amounts declared by users, and only automatically derived prices and costs will possibly result in precisions calculated to an unrealistic number of digits that could creep into aggregations in the rest of the system. References \uf0c1 The original proposal that led to this implementation can be found here . In particular, the proposal highlights on the other systems have attempted to deal with this issue. There are also some discussions on the mailing-list dedicated to this topic. Note that for the longest time, Beancount used a fixed precision of 0.005 across all currencies. This was eliminated once the method described in this document was implemented. Also, for Balance and Pad directives, there used to be a \u201ctolerance\u201d option that was set by default to 0.015 of any units. This option has been deprecated with the merging of the changes described in this document. Historical Notes \uf0c1 Here\u2019s an overview of the status of numbers rendering in Beancount as of March 2016, from the mailing-list : First, it's important to realize how these numbers are represented in memory. They are using the Decimal representation which beyond being able to accurately representing decimal numbers (as opposed to the approximation that binary floats provides) also contains a specific precision. That is, the number 2.00 is represented differently than the numbers 2.0 and 2.000. The numbers \"remember\" which precision they are represented up to. This is important. When I say rendering the numbers to their \"natural precision\" I mean the precision with which they are represented, i.e., 2.0 renders as \"2.0\", 2.000 renders as \"2.000\". Then, there are two DISTINCT topics: (1) tolerances, and (2) precision. \"Tolerances\" are values used to determine how much imprecision is acceptable in balancing transactions. This is used in the verification stage, to determine how much looseness to allow. It should not affect how numbers are rendered. \"Precision\" is perhaps a bit of a misnomer: By that I'm referring to is how many digits the numbers are to be rendered with. Once upon a time - after the shell was already written - these concepts weren't well defined in Beancount and I wasn't dealing with these things consistently. At some point it became clear what I needed to do and I created a class called \"DisplayContext\" which could contain appropriate settings for rendering the precision of numbers for each currency (each currency tends to have its own most common rendering precision, e.g. two digits for USD, one digit for MXN, no digits for JPY and in reports we're typically fine rounding the actual numbers to that precision). So an instance of this DisplayContext is automatically instantiated in the parser and in order to avoid the user having to set these values manually - for Beancount to \"do the right thing\" by default - it is able to accumulate the numbers seen and to deduce the most common and maximum number of digits used from the input, and to use that as the default number of digits for rendering numbers. The most common format/number of digits is used to render the number of units, and the maximum number of digits seen is used to render costs and prices. In addition, this class also has capabilities for aligning to the decimal dot and to insert commas on thousands as well. It separates the control of the formatting from the numbers themselves. MOST of the code that renders numbers uses the DisplayContext (via the to_string() methods) to convert the numbers into strings, such as the web interface and explicit text reports. But NOT ALL... there's a bit of HISTORY here... the SQL shell uses some old special-purpose code to render numbers that I never bothered to convert to the DisplayContext class. There's a TODO item for it. It needs to get converted at some point, but I've neglected doing this so far because I have much bigger plans for the SQL query engine that involve a full rewrite of it with many improvements and I figured I'd do that then. If you recall, the SQL query engine was a prototype, and actually it works, but it is not well covered by unit tests. My purpose with it was to discover through usage what would be useful and to then write a v2 of it that would be much better. Now, about that PRINT command... this is not intended as a reporting tool. The printer's purpose is to print input that accurately represents the content of the transactions. In order to do this, it needs to render the numbers at their \"natural\" precision, so that when they get read back in, they parse into the very same number, that is, with the same number of digits (even if zeros). For this reason, the PRINT command does not attempt to render using the DisplayContext instance derived from the input file - this is on purpose. I could change that, but then round-trip would break: the rounding resulting from formatting using the display context may output transactions which don't balance anymore. As you can see, it's not an obvious topic... Hopefully this should allow you to understand what comes out of Beancount in terms of the precision of the numbers it renders. Note: \"default_tolerances\" has been renamed to \"inferred_tolerance_default\" recently because the name was too general and confusing. Old name will work but generate a warning. I just noticed from your comments and some grepping around that the \"render_commas\" option is not used anymore. I'm not sure how that happened, but I'll go ad fix that right away and set the default value of the DisplayContext derived from the input. I should probably also convert the SQL shell rendering to use the display context regardless of future plans, so that it renders consistently with all the rest. Not sure I can do that this weekend, but I'll log a ticket, here . I hope this helps. You're welcome to ask questions if the above isn't clear. I'm sorry if this isn't entirely obvious... there's been a fair bit of history there and there's a lot of code. I should review the naming of options, I think the tolerance options all have \"tolerance\" in their name, but there aren't options to override the rendering and when I add them they should all have a common name as well. Further Reading \uf0c1 What Every Computer Scientist Should Know About Floating-Point Arithmetic This stands in contrast to Ledger which attempts to infer the precision based on other transactions recently parsed in the file, in file order. This has the unfortunate effect of creating \u201ccross-talk\u201d between the transactions in terms of what precision can be used. \u21a9 Note that due to the way Beancount represents numbers internally, it is also not able to distinguish between \u201c230\u201d and \u201c230.\u201d; these parse into the same representation for Beancount. Therefore, we are not able to use that distinction in the input to support a precision of 0.5. \u21a9","title":"Precision Tolerances"},{"location":"precision_tolerances.html#beancount-precision-tolerances","text":"Martin Blais , May 2015 http://furius.ca/beancount/doc/tolerances This document describes how Beancount handles the limited precision of numbers in transaction balance checks and balance assertions. It also documents rounding that may occur in inferring numbers automatically.","title":"Beancount Precision & Tolerances"},{"location":"precision_tolerances.html#motivation","text":"Beancount automatically enforces that the amounts on the Postings of Transactions entered in an input file sum up to zero. In order for Beancount to verify this in a realistic way, it must tolerate a small amount of imprecision. This is because Beancount lets you replicate what happens in real world account transactions , and in the real world, institutions round amounts up or down for practical reasons. Here\u2019s an example: Consider the following transaction which consists in a transfer between two accounts denominated in different currencies (US dollars and Euros): 2015-05-01 * \"Transfer from secret Swiss bank account\" Assets:CH:SBS:Checking -9000.00 CHF Assets:US:BofA:Checking 9643.82 USD @ 0.93324 CHF In this example, the exchange rate used was 0.93324 USD/CHF, that is, 0.93324 Swiss Francs per US dollar. This rate was quoted to 5 digits of precision by the bank. A full-precision conversion of 9000.00 CHF / 0.93324 CHF yields 9643.82152501... USD. Similarly, converting the US dollars to Francs using the given rate yields an imprecise result as well: 9643.82 x 0.93324 = 8999.9985768\u2026 . Here is another example where this type of rounding may occur: A transaction for a fractional number of shares of a mutual fund: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD Once again, rounding occurs in this transaction: not only the Net Asset Value of the fund is rounded to its nearest penny value ($37.61), but the number of units is also rounded and accounted for by Vanguard with a fixed number of digits (10.22626 units of VPMBX). And the balance of the entire transaction needs to tolerate some imprecision, whether you compute the value of the shares (10.22626 x $37.61 = $384.6096386 ) or whether you compute the number of shares from the desired dollar amount of the contribution ($384.61 / $37.61 = 10.2262696091 ). From Beancount\u2019s point-of-view, both of the examples above are balancing transactions. Clearly, if we are to try to represent and reproduce the transactions of external accounts to our input file, there needs to be some tolerance in the balance verification algorithm.","title":"Motivation"},{"location":"precision_tolerances.html#how-precision-is-determined","text":"Beancount attempts to derive the precision from each transaction automatically , from the input, for each Transaction in isolation 1 . Let us inspect our last example again: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.22626 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.61 USD In this transaction, Beancount will infer the tolerance of RGAGX at 5 fractional digits, that is, 0.000005 RGAGX , and USD at 2 fractional digits, that is, 0.005 USD . Note that the tolerance used is half of the last digit of precision provided by the user. This is entirely inferred from the input, without having to fetch any global tolerance declaration. Also note how the precision is calculated separately for each currency . Observe that although we are inferring a tolerance for units of RGAGX, it is actually not used in the balancing of this transaction, because the \u201cweight\u201d of the first posting is in USD (10.22626 x 37.61 = 384.6096386 USD). So what happens here? The weights of each postings are calculated: 384.6096386 USD for the first posting -384.61 USD for the second These are summed together, by currency (there is only USD in the weights of this transaction) which results in a residual value of -0.0003614 USD. This value is compared to the tolerance for units of USD: |-0.0003614| < 0.005, and this transaction balances.","title":"How Precision is Determined"},{"location":"precision_tolerances.html#prices-and-costs","text":"For the purpose of inferring the tolerance to be used, the price and cost amounts declared on a transaction\u2019s Postings are ignored . This makes sense if you consider that these are usually specified at a higher precision than the base amounts of the postings\u2014and sometimes this extra precision is necessary to make the transaction balance. These should not be used in setting the precision of the whole transaction. For example, in the following transaction: 1999-09-30 * \"Vest ESPP - Bought at discount: 18.5980 USD\" Assets:US:Schwab:ESPP 54 HOOL {21.8800 USD} Income:CA:ESPP:PayContrib -1467.84 CAD @ 0.6842 USD Income:CA:ESPP:Discount -259.03 CAD @ 0.6842 USD The only tolerance inferred here is 0.005 for CAD. (54 HOOL does not yield anything in this case because it is integral; the next section explains this). There is no tolerance inferred for USD, neither from the cost from the first posting (21.8800 USD), nor from the prices of the remaining postings (0.6842 USD).","title":"Prices and Costs"},{"location":"precision_tolerances.html#integer-amounts","text":"For integer amounts in the input, the precision is not inferred to 0.5, that is, this should fail to balance: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD In other words, integer amounts do not contribute a number of digits to the determination of the tolerance for their currency. By default, the tolerance used on amounts without an inferred precision is zero . So in this example, because we cannot infer the precision of USD (recall that the cost is ignored), this transaction will fail to balance, because its residual is non-zero (|-0.0003614| > 0). You can customize what the default tolerance should be for each currency separately and for any currency as well (see section below on how to do this). This treatment of integer amounts implies that the maximum amount of precision that one can specify just by inputting numbers is 0.05 units of the currency, for example, by providing a number such as 10.7 as input 2 . On the other hand, the settings for the default tolerance to use allows specifying arbitrary numbers.","title":"Integer Amounts"},{"location":"precision_tolerances.html#resolving-ambiguities","text":"A case that presents itself rarely is one where multiple different precisions are being input for the same currency. In this case, the largest (coarsest) of the inferred input tolerances is used. For example, if we wanted to track income to more than pennies, we might write this: 1999-08-20 * \"Sell\" Assets:US:BRS:ESPP -81 HOOL {26.3125 USD} Assets:US:BRS:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:CA:ESPP:PnL -10.125 USD The amounts we have for USD in this case are 2141.36, 0.08 and -10.125, which infer tolerances of either 0.005 or 0.0005. We select the coarsest amount: this transaction tolerates an imprecision of 0.005 USD.","title":"Resolving Ambiguities"},{"location":"precision_tolerances.html#default-tolerances","text":"When a transaction\u2019s numbers do not provide enough information to infer a tolerance locally , we fall back to some default tolerance value. As seen in previous examples, this may occur either because (a) the numbers associated with the currency we need it for are integral, or (b) sufficient numbers are simply absent from the input. By default, this default tolerance is zero for all currencies. This can be specified with an option, like this: option \"inferred_tolerance_default\" \"*:0.001\" The default tolerance can be further refined for each currency involved, by providing the currency to the option, like this: option \"inferred_tolerance_default\" \"USD:0.003\" If provided, the currency-specific tolerance will be used over the global value. The general form for this option is: option \"inferred_tolerance_default\" \":\" Just to be clear: this option is only used when the tolerance cannot be inferred. If you have overly large rounding errors and the numbers in your transactions do infer some tolerance value, this value will be ignored (e.g., setting it to a larger number to try to address that fix will not work). If you need to loosen up the tolerance, see the \u201c inferred_tolerance_multiplier \u201d in the next section. (Note: I\u2019ve been considering dedicating a special meta-data field to the Commodity directive for this, but this would break from the invariant that meta-data is only there to be used by users and plugins, so I\u2019ve refrained so far.)","title":"Default Tolerances"},{"location":"precision_tolerances.html#tolerance-multiplier","text":"We\u2019re shown previously that when the tolerance value isn\u2019t provided explicitly, that it is inferred from the numbers on the postings. By default, the smallest digit found on those numbers is divided by half to obtain the tolerance because we assume that the institutions which we\u2019re reproducing the transactions apply rounding and so the error should never be more than half. But in reality, you may find that the rounding errors sometime exceed this value. For this reason, we provide an option to set the multiplier for the inferred tolerance: option \"inferred_tolerance_multiplier\" \"1.2\" This value overrides the default multiplier. In this example, for a transaction with postings only with values such as 24.45 CHF, the inferred tolerance for CHF would be +/- 0.012 CHF.","title":"Tolerance Multiplier"},{"location":"precision_tolerances.html#inferring-tolerances-from-cost","text":"There is also a feature that expands the maximum tolerance inferred on transactions to include values on cost currencies inferred by postings held at-cost or converted at price. Those postings can imply a tolerance value by multiplying the smallest digit of the unit by the cost or price value and taking half of that value. For example, if a posting has an amount of \"2.345 RGAGX {45.00 USD}\" attached to it, it implies a tolerance of 0.001 x 45.00 / 2 = 0.045 USD and the sum of all such possible rounding errors is calculate for all postings held at cost or converted from a price, and the resulting tolerance is added to the list of candidates used to figure out the tolerance we should use for the given commodity (we use the maximum value of all the inferred tolerances). You turn on the feature like this: option \"infer_tolerance_from_cost\" \"TRUE\" Enabling this flag only makes the tolerances potentially wider, never smaller.","title":"Inferring Tolerances from Cost"},{"location":"precision_tolerances.html#balance-assertions-padding","text":"There are a few other places where approximate comparisons are needed. Balance assertions also compare two numbers: 2015-05-08 balance Assets:Investments:RGAGX 4.271 RGAGX This asserts that the accumulated balance for this account has 4.271 units of RGAGX, plus or minus 0.001 RGAGX. So accumulated values of 4.270 RGAGX up to 4.272 RGAGX will check as asserted. The tolerance is inferred automatically to be 1 unit of the least significant digit of the number on the balance assertion. If you wanted a looser assertion, you could have declared: 2015-05-08 balance Assets:Investments:RGAGX 4.27 RGAGX This assertion would accept values from 4.26 RGAGX to 4.28 RGAGX. Note that the inferred tolerances are also expanded by the inferred tolerance multiplier discussed above.","title":"Balance Assertions & Padding"},{"location":"precision_tolerances.html#tolerances-that-trigger-padding","text":"Pad directives automatically insert transactions to bring account balances in-line with a subsequent balance assertion. The insertion only triggers if the balance differs from the expected value, and the tolerance for this to occur behaves exactly the same as for balance assertions.","title":"Tolerances that Trigger Padding"},{"location":"precision_tolerances.html#explicit-tolerances-on-balance-assertions","text":"Beancount supports the specification of an explicit tolerance amount, like this: 2015-05-08 balance Assets:Investments:RGAGX 4.271 ~ 0.01 RGAGX This feature was added because of some observed peculiarities in Vanguard investment accounts whereby rounding appears to follow odd rules and balances don\u2019t match.","title":"Explicit Tolerances on Balance Assertions"},{"location":"precision_tolerances.html#saving-rounding-error","text":"As we saw previously, transactions don\u2019t have to balance exactly, they allow for a small amount of imprecision. This bothers some people. If you would like to track and measure the residual amounts allowed by the tolerances, Beancount offers an option to automatically insert postings that will make each transaction balance exactly. You enable the feature like this: option \"account_rounding\" \"Equity:RoundingError\" This tells Beancount to insert postings to compensate for the rounding error to an \u201c Equity:RoundingError \u201d account. For example, with the feature enabled, the following transaction: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD will be automatically transformed into this: 2013-02-23 * \"Buying something\" Assets:Invest 1.245 RGAGX {43.23 USD} Assets:Cash -53.82 USD Equity:RoundingError -0.00135 USD You can verify that this transaction balances exactly. If the transaction already balances exactly (this is the case for most transactions) no posting is inserted. Finally, if you require that all accounts be opened explicitly, you should remember to declare the rounding account in your file at an appropriate date, like this: 2000-01-01 open Equity:RoundingError","title":"Saving Rounding Error"},{"location":"precision_tolerances.html#precision-of-inferred-numbers","text":"Beancount is able to infer some missing numbers in the input. For example, the second posting in this transaction is \u201cinterpolated\u201d automatically by Beancount: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash The calculated amount to be inserted from the first posting is -227.2067 USD. Now, you might ask, to which precision is it inserted at? Does it insert 227.2067 USD at the full precision or does the number get rounded to a penny, e.g. 227.21 USD? It depends on the tolerance inferred for that currency. In this example, no tolerance is able to get inferred (there is no USD amount provided other than the cost amount, which is ignored for the purpose of inferring the tolerance), so we have to defer to the default tolerance. If the default tolerance is not overridden in the input file\u2014and therefore is zero\u2014the full precision will be used; no rounding occurs. This will result in the following transaction: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.2067 USD Note that if a tolerance could be inferred from other numbers on that transaction, it would be used for rounding, such as in this example where the Cash posting is rounded to two digits because of the 9.95 USD number on the Commissions posting: 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Expenses:Commissions 9.95 USD Assets:Investments:Cash -237.16 USD However, if no inference is possible, and the default tolerance for USD is set to 0.001, the number will be quantized to 0.001 before insertion, that is, 227.207 USD will be stored: option \"default_tolerance\" \"USD:0.001\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Finally, if you enabled the accumulation of rounding error, the posting\u2019s amount will reflect the correct residual, taking into account the rounded amount that was automatically inserted: option \"default_tolerance\" \"USD:0.01\" option \"account_rounding\" \"Equity:RoundingError\" 2014-05-06 * \"Buy mutual fund\" Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.207 USD Equity:RoundingError 0.0003 USD","title":"Precision of Inferred Numbers"},{"location":"precision_tolerances.html#porting-existing-input","text":"The inference of tolerance values from the transaction\u2019s numbers is generally good enough to keep existing files working without changes. There may be new errors appearing in older files once we process them with the method described in this document, but they should either point to previously undetected errors in the input, or be fixable with simple addition of a suitable number of digits. As a testimony, porting the author\u2019s very large input file has been a relatively painless process that took less than 1 hour. In order to ease the transition, you will probably want to change the default tolerance for all currencies to match the previous value that Beancount had been using, like this: option \"inferred_tolerance_default\" \"*:0.005\" I would recommend you start with this and fix all errors in your file, then proceed to removing this and fix the rest of errors. This should make it easier to adapt your file to this new behavior. As an example of how to fix a new error\u2026 converting this newly failing transaction from the Integer Amounts section: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384 USD by inserting zero\u2019s to provide a locally inferred value like this: 2013-04-03 * \"Buy Mutual Fund - Price as of date based on closing price\" Assets:US:Vanguard:RGAGX 10.21005 RGAGX {37.61 USD} Assets:US:Vanguard:Cash -384.00 USD is sufficient to silence the balance check.","title":"Porting Existing Input"},{"location":"precision_tolerances.html#representational-issues","text":"Internally, Beancount uses a decimal number representation (not a binary/float representation, neither rational numbers). Calculations that result in a large number of fractional digits are carried out to 28 decimal places (the default precision from the context of Python\u2019s IEEE decimal implementation). This is plenty sufficient, because the method we propose above rarely trickles these types of numbers throughout the system: the tolerances allows us to post the precise amounts declared by users, and only automatically derived prices and costs will possibly result in precisions calculated to an unrealistic number of digits that could creep into aggregations in the rest of the system.","title":"Representational Issues"},{"location":"precision_tolerances.html#references","text":"The original proposal that led to this implementation can be found here . In particular, the proposal highlights on the other systems have attempted to deal with this issue. There are also some discussions on the mailing-list dedicated to this topic. Note that for the longest time, Beancount used a fixed precision of 0.005 across all currencies. This was eliminated once the method described in this document was implemented. Also, for Balance and Pad directives, there used to be a \u201ctolerance\u201d option that was set by default to 0.015 of any units. This option has been deprecated with the merging of the changes described in this document.","title":"References"},{"location":"precision_tolerances.html#historical-notes","text":"Here\u2019s an overview of the status of numbers rendering in Beancount as of March 2016, from the mailing-list : First, it's important to realize how these numbers are represented in memory. They are using the Decimal representation which beyond being able to accurately representing decimal numbers (as opposed to the approximation that binary floats provides) also contains a specific precision. That is, the number 2.00 is represented differently than the numbers 2.0 and 2.000. The numbers \"remember\" which precision they are represented up to. This is important. When I say rendering the numbers to their \"natural precision\" I mean the precision with which they are represented, i.e., 2.0 renders as \"2.0\", 2.000 renders as \"2.000\". Then, there are two DISTINCT topics: (1) tolerances, and (2) precision. \"Tolerances\" are values used to determine how much imprecision is acceptable in balancing transactions. This is used in the verification stage, to determine how much looseness to allow. It should not affect how numbers are rendered. \"Precision\" is perhaps a bit of a misnomer: By that I'm referring to is how many digits the numbers are to be rendered with. Once upon a time - after the shell was already written - these concepts weren't well defined in Beancount and I wasn't dealing with these things consistently. At some point it became clear what I needed to do and I created a class called \"DisplayContext\" which could contain appropriate settings for rendering the precision of numbers for each currency (each currency tends to have its own most common rendering precision, e.g. two digits for USD, one digit for MXN, no digits for JPY and in reports we're typically fine rounding the actual numbers to that precision). So an instance of this DisplayContext is automatically instantiated in the parser and in order to avoid the user having to set these values manually - for Beancount to \"do the right thing\" by default - it is able to accumulate the numbers seen and to deduce the most common and maximum number of digits used from the input, and to use that as the default number of digits for rendering numbers. The most common format/number of digits is used to render the number of units, and the maximum number of digits seen is used to render costs and prices. In addition, this class also has capabilities for aligning to the decimal dot and to insert commas on thousands as well. It separates the control of the formatting from the numbers themselves. MOST of the code that renders numbers uses the DisplayContext (via the to_string() methods) to convert the numbers into strings, such as the web interface and explicit text reports. But NOT ALL... there's a bit of HISTORY here... the SQL shell uses some old special-purpose code to render numbers that I never bothered to convert to the DisplayContext class. There's a TODO item for it. It needs to get converted at some point, but I've neglected doing this so far because I have much bigger plans for the SQL query engine that involve a full rewrite of it with many improvements and I figured I'd do that then. If you recall, the SQL query engine was a prototype, and actually it works, but it is not well covered by unit tests. My purpose with it was to discover through usage what would be useful and to then write a v2 of it that would be much better. Now, about that PRINT command... this is not intended as a reporting tool. The printer's purpose is to print input that accurately represents the content of the transactions. In order to do this, it needs to render the numbers at their \"natural\" precision, so that when they get read back in, they parse into the very same number, that is, with the same number of digits (even if zeros). For this reason, the PRINT command does not attempt to render using the DisplayContext instance derived from the input file - this is on purpose. I could change that, but then round-trip would break: the rounding resulting from formatting using the display context may output transactions which don't balance anymore. As you can see, it's not an obvious topic... Hopefully this should allow you to understand what comes out of Beancount in terms of the precision of the numbers it renders. Note: \"default_tolerances\" has been renamed to \"inferred_tolerance_default\" recently because the name was too general and confusing. Old name will work but generate a warning. I just noticed from your comments and some grepping around that the \"render_commas\" option is not used anymore. I'm not sure how that happened, but I'll go ad fix that right away and set the default value of the DisplayContext derived from the input. I should probably also convert the SQL shell rendering to use the display context regardless of future plans, so that it renders consistently with all the rest. Not sure I can do that this weekend, but I'll log a ticket, here . I hope this helps. You're welcome to ask questions if the above isn't clear. I'm sorry if this isn't entirely obvious... there's been a fair bit of history there and there's a lot of code. I should review the naming of options, I think the tolerance options all have \"tolerance\" in their name, but there aren't options to override the rendering and when I add them they should all have a common name as well.","title":"Historical Notes"},{"location":"precision_tolerances.html#further-reading","text":"What Every Computer Scientist Should Know About Floating-Point Arithmetic This stands in contrast to Ledger which attempts to infer the precision based on other transactions recently parsed in the file, in file order. This has the unfortunate effect of creating \u201ccross-talk\u201d between the transactions in terms of what precision can be used. \u21a9 Note that due to the way Beancount represents numbers internally, it is also not able to distinguish between \u201c230\u201d and \u201c230.\u201d; these parse into the same representation for Beancount. Therefore, we are not able to use that distinction in the input to support a precision of 0.5. \u21a9","title":"Further Reading"},{"location":"rounding_precision_in_beancount.html","text":"Proposal: Rounding & Precision in Beancount \uf0c1 Martin Blais , October 2014 This document describes the problem of rounding errors on Beancount transactions and how they are handled. It also includes a proposal for better handling precision issues in Beancount. Motivation \uf0c1 Balancing Precision \uf0c1 Balancing transactions cannot be done precisely. This has been discussed on the Ledger mailing-list before . It is necessary to allow for some tolerance on the amounts used to balance a transaction. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits, something like this example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.21 USD If you calculate it, the first posting\u2019s precise balance amount is 227.2067 USD, not 227.21 USD. However, the broker company managing the investment account will apply rounding to the closest cent for the cash withdrawal, and the rounded amount is the correct one to be used. This transaction has to balance; we need to allow for some looseness somehow. The great majority of the cases where mathematical operations occur involve the conversion from a number of units and a price or a cost to a corresponding cash value (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters. Automatic Rounding \uf0c1 Another related issue is that of automatically rounding amounts for interpolated numbers. Let\u2019s take our original problematic example again: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash ;; Interpolated posting = -227.2067 USD Here the amount from the second posting is interpolated from the balance amount for the first posting. Ideally, we should find a way to specify how it should round to 2 fractional digits of precision. Note that this affects interpolated prices and costs too: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {USD} Assets:Investments:Cash -227.21 USD Here, the cost is intended to be automatically calculated from the cash legs: 227.21 / 4.27 = 53.2107728337\u2026 USD. The correct cost to be inferred is also the rounded amount of 53.21 USD. We would like a mechanism to allow us to infer the desired precision. This mechanism cannot unfortunately be solely based on commodity: different accounts may track currencies with different precisions. As a real-world example, I have a retail FOREX trading account that really uses 4 digits of precision for its prices and deposits. Precision of Balance Assertions \uf0c1 The precision of a balance assertions is also subject to this problem, assertions like this one: 2014-04-01 balance Assets:Investments:Cash 4526.77 USD The user does not intend for this balance check to precisely sum up to 4526.77000000\u2026 USD. However, it this cash account previously received a deposit with a greater precision as in the previous section\u2019s example, then we have a problem. Now the cash amount contains some of the crumbs deposited from the interpolation (0.0067 USD). If we were able to find a good solution for the automatic rounding of postings in the previous section, this would not be a problem. But in the meantime, we must find a solution. Beancount\u2019s current approach is a kludge: it uses a user-configurable tolerance of 0.0150 (in any unit). We\u2019d like to change this so that the tolerance used is able to depend on the commodity, the account, or even the particular directive in use. Other Systems \uf0c1 Other command-line accounting systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used. Proposal \uf0c1 Automatically Inferring Tolerance \uf0c1 Beancount should derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults. That is, for each transaction, it will inspect postings with simple amounts (no cost, no price) and infer the precision to be used for tolerance as half of that of the most precise amount entered by the user on this transaction. For example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.278 RGAGX {53.21 USD} Assets:Investments:Cash -227.6324 USD Expenses:Commissions 9.95 USD The number of digits for the precision to be used here is the maximum of the 2nd and 3rd postings, that is, max(4, 2) = 4. The first postings is ignored because its amount is the result of a mathematical operation. The tolerance value should be half of the most precise digit, that is 0.00005 USD. This should allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision\u2026 an integer should imply exact matching. The user could specify a single trailing period to imply a sub-dollar precision. For example, the following transaction should fail to balance because the calculated amount is 999.999455 USD: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000 USD Instead, the user should explicitly allow for some tolerance to be used: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000. USD Or better, use 1000.00 USD. This has the disadvantage that is prevents the user from specifying the simpler integer amount. I\u2019m not sure if this is a big deal. Finally, no global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context. Inference on Amounts Held at Cost \uf0c1 An idea from by Matthew Harris ( here ) is that we could also use the value of the to the smallest decimal of the number of units times the cost as a number to use in establishing the tolerance for balancing transactions. For example, in the following transaction: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} ... The tolerance value that could be derived is 0.01 RGAGX x 42.6439 USD = 0.426439 USD The original use case presented by Matthew was of a transaction that did not contain a simple amount, just a conversion with both legs held at cost: 2011-01-25 * \"Transfer of Assets, 3467.90 USD\" * Assets:RothIRA:Vanguard:VTIVX 250.752 VTIVX {18.35 USD} @ 13.83 USD * Assets:RothIRA:DodgeCox:DODGX -30.892 DODGX {148.93 USD} @ 112.26 USD I\u2019ve actually tried to implement this and the resulting tolerances are either unacceptably wide or unacceptably small. It does not work well in practice so I\u2019ve abandoned the idea. Automated Rounding \uf0c1 For values that are automatically calculated, for example, on auto-postings where the remaining value is derived automatically, we should consider rounding the values. No work has been done on this yet; these values are currently not rounded. Fixing Balance Assertions \uf0c1 To fix balance assertions, we will derive the required precision by the number of digits used in the balance amount itself, by looking at the most precision fractional digit and using half of that digit\u2019s value to compute the tolerance: 2014-04-01 balance Assets:Investments:Cash 4526.7702 USD This balance check implies a precision of 0.00005 USD. If you use an integer number of units, no tolerance is allowed. The precise number should match: 2014-04-01 balance Assets:Investments:Cash 4526 USD If you want to allow for sub-dollar variance, use a single comma: 2014-04-01 balance Assets:Investments:Cash 4526. USD This balance check implies a precision of 0.50 USD. Approximate Assertions \uf0c1 Another idea, proposed in this ticket on Ledger , proposes an explicitly approximate assertion. We could implement it this way (just an idea): 2014-04-01 balance Assets:Investments:Cash 4526.00 +/- 0.05 USD Accumulating & Reporting Residuals \uf0c1 In order to explicitly render and monitor the amount of rounding errors that occur in a Ledger, we should accumulate it to an Equity account , such as \u201cEquity:Rounding\u201d. This should be turned on optionally. It should be possible for the user to specify an account to be used to accumulate the error. Whenever a transaction does not balance exactly, the residual, or rounding error, will be inserted as a posting of the transaction to the equity account. By default, this accumulation should be turned off. It\u2019s not clear whether the extra postings will be disruptive yet (if they\u2019re not, maybe this should be turned on by default; practice will inform us). Implementation \uf0c1 The implementation of this proposal is documented here .","title":"Rounding Precision in Beancount"},{"location":"rounding_precision_in_beancount.html#proposal-rounding-precision-in-beancount","text":"Martin Blais , October 2014 This document describes the problem of rounding errors on Beancount transactions and how they are handled. It also includes a proposal for better handling precision issues in Beancount.","title":"Proposal: Rounding & Precision in Beancount"},{"location":"rounding_precision_in_beancount.html#motivation","text":"","title":"Motivation"},{"location":"rounding_precision_in_beancount.html#balancing-precision","text":"Balancing transactions cannot be done precisely. This has been discussed on the Ledger mailing-list before . It is necessary to allow for some tolerance on the amounts used to balance a transaction. This need is clear when you consider that inputting numbers in a text file implies a limited decimal representation. For example, if you\u2019re going to multiply a number of units and a cost, say both written down with 2 fractional digits, you might end up with a number that has 4 fractional digits, and then you need to compare that result with a cash amount that would typically be entered with only 2 fractional digits, something like this example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash -227.21 USD If you calculate it, the first posting\u2019s precise balance amount is 227.2067 USD, not 227.21 USD. However, the broker company managing the investment account will apply rounding to the closest cent for the cash withdrawal, and the rounded amount is the correct one to be used. This transaction has to balance; we need to allow for some looseness somehow. The great majority of the cases where mathematical operations occur involve the conversion from a number of units and a price or a cost to a corresponding cash value (e.g., units x cost = total cost). Our task in representing transactional information is the replication of operations that take place mostly in institutions. These operations always involve the rounding of numbers for units and currencies (banks do apply stochastic rounding), and the correct numbers to be used from the perspective of these institutions, and from the perspective of the government, are indeed the rounded numbers themselves. It is a not a question of mathematical purity, but one of practicality, and our system should do the same that banks do. Therefore, I think that we should always post the rounded numbers to accounts. Using rational numbers is not a limitation in that sense, but we must be careful to store rounded numbers where it matters.","title":"Balancing Precision"},{"location":"rounding_precision_in_beancount.html#automatic-rounding","text":"Another related issue is that of automatically rounding amounts for interpolated numbers. Let\u2019s take our original problematic example again: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {53.21 USD} Assets:Investments:Cash ;; Interpolated posting = -227.2067 USD Here the amount from the second posting is interpolated from the balance amount for the first posting. Ideally, we should find a way to specify how it should round to 2 fractional digits of precision. Note that this affects interpolated prices and costs too: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.27 RGAGX {USD} Assets:Investments:Cash -227.21 USD Here, the cost is intended to be automatically calculated from the cash legs: 227.21 / 4.27 = 53.2107728337\u2026 USD. The correct cost to be inferred is also the rounded amount of 53.21 USD. We would like a mechanism to allow us to infer the desired precision. This mechanism cannot unfortunately be solely based on commodity: different accounts may track currencies with different precisions. As a real-world example, I have a retail FOREX trading account that really uses 4 digits of precision for its prices and deposits.","title":"Automatic Rounding"},{"location":"rounding_precision_in_beancount.html#precision-of-balance-assertions","text":"The precision of a balance assertions is also subject to this problem, assertions like this one: 2014-04-01 balance Assets:Investments:Cash 4526.77 USD The user does not intend for this balance check to precisely sum up to 4526.77000000\u2026 USD. However, it this cash account previously received a deposit with a greater precision as in the previous section\u2019s example, then we have a problem. Now the cash amount contains some of the crumbs deposited from the interpolation (0.0067 USD). If we were able to find a good solution for the automatic rounding of postings in the previous section, this would not be a problem. But in the meantime, we must find a solution. Beancount\u2019s current approach is a kludge: it uses a user-configurable tolerance of 0.0150 (in any unit). We\u2019d like to change this so that the tolerance used is able to depend on the commodity, the account, or even the particular directive in use.","title":"Precision of Balance Assertions"},{"location":"rounding_precision_in_beancount.html#other-systems","text":"Other command-line accounting systems differ in how they choose that tolerance: Ledger attempts to automatically derive the precision to use for its balance checks by using recently parsed context (in file order). The precision to be used is that of the last value parsed for the particular commodity under consideration. This can be problematic: it can lead to unnecessary side-effects between transactions which can be difficult to debug . HLedger, on the other hand, uses global precision settings. The whole file is processed first, then the precisions are derived from the most precise numbers seen in the entire input file. At the moment, Beancount uses a constant value for the tolerance used in its balance checking algorithm (0.005 of any unit). This is weak and should, at the very least, be commodity-dependent, if not also dependent on the particular account in which the commodity is used.","title":"Other Systems"},{"location":"rounding_precision_in_beancount.html#proposal","text":"","title":"Proposal"},{"location":"rounding_precision_in_beancount.html#automatically-inferring-tolerance","text":"Beancount should derive its precision using a method entirely local to each transaction, perhaps with a global value for defaults. That is, for each transaction, it will inspect postings with simple amounts (no cost, no price) and infer the precision to be used for tolerance as half of that of the most precise amount entered by the user on this transaction. For example: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 4.278 RGAGX {53.21 USD} Assets:Investments:Cash -227.6324 USD Expenses:Commissions 9.95 USD The number of digits for the precision to be used here is the maximum of the 2nd and 3rd postings, that is, max(4, 2) = 4. The first postings is ignored because its amount is the result of a mathematical operation. The tolerance value should be half of the most precise digit, that is 0.00005 USD. This should allow the user to use an arbitrary precision, simply by inserting more digits in the input. Only fractional digits will be used to derive precision\u2026 an integer should imply exact matching. The user could specify a single trailing period to imply a sub-dollar precision. For example, the following transaction should fail to balance because the calculated amount is 999.999455 USD: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000 USD Instead, the user should explicitly allow for some tolerance to be used: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} Assets:Investments:Cash -1000. USD Or better, use 1000.00 USD. This has the disadvantage that is prevents the user from specifying the simpler integer amount. I\u2019m not sure if this is a big deal. Finally, no global effect implied by transactions will be applied. No transaction should ever affect any other transaction\u2019s balancing context.","title":"Automatically Inferring Tolerance"},{"location":"rounding_precision_in_beancount.html#inference-on-amounts-held-at-cost","text":"An idea from by Matthew Harris ( here ) is that we could also use the value of the to the smallest decimal of the number of units times the cost as a number to use in establishing the tolerance for balancing transactions. For example, in the following transaction: 2014-05-06 * \u201cBuy mutual fund\u201d Assets:Investments:RGXGX 23.45 RGAGX {42.6439 USD} ... The tolerance value that could be derived is 0.01 RGAGX x 42.6439 USD = 0.426439 USD The original use case presented by Matthew was of a transaction that did not contain a simple amount, just a conversion with both legs held at cost: 2011-01-25 * \"Transfer of Assets, 3467.90 USD\" * Assets:RothIRA:Vanguard:VTIVX 250.752 VTIVX {18.35 USD} @ 13.83 USD * Assets:RothIRA:DodgeCox:DODGX -30.892 DODGX {148.93 USD} @ 112.26 USD I\u2019ve actually tried to implement this and the resulting tolerances are either unacceptably wide or unacceptably small. It does not work well in practice so I\u2019ve abandoned the idea.","title":"Inference on Amounts Held at Cost"},{"location":"rounding_precision_in_beancount.html#automated-rounding","text":"For values that are automatically calculated, for example, on auto-postings where the remaining value is derived automatically, we should consider rounding the values. No work has been done on this yet; these values are currently not rounded.","title":"Automated Rounding"},{"location":"rounding_precision_in_beancount.html#fixing-balance-assertions","text":"To fix balance assertions, we will derive the required precision by the number of digits used in the balance amount itself, by looking at the most precision fractional digit and using half of that digit\u2019s value to compute the tolerance: 2014-04-01 balance Assets:Investments:Cash 4526.7702 USD This balance check implies a precision of 0.00005 USD. If you use an integer number of units, no tolerance is allowed. The precise number should match: 2014-04-01 balance Assets:Investments:Cash 4526 USD If you want to allow for sub-dollar variance, use a single comma: 2014-04-01 balance Assets:Investments:Cash 4526. USD This balance check implies a precision of 0.50 USD.","title":"Fixing Balance Assertions"},{"location":"rounding_precision_in_beancount.html#approximate-assertions","text":"Another idea, proposed in this ticket on Ledger , proposes an explicitly approximate assertion. We could implement it this way (just an idea): 2014-04-01 balance Assets:Investments:Cash 4526.00 +/- 0.05 USD","title":"Approximate Assertions"},{"location":"rounding_precision_in_beancount.html#accumulating-reporting-residuals","text":"In order to explicitly render and monitor the amount of rounding errors that occur in a Ledger, we should accumulate it to an Equity account , such as \u201cEquity:Rounding\u201d. This should be turned on optionally. It should be possible for the user to specify an account to be used to accumulate the error. Whenever a transaction does not balance exactly, the residual, or rounding error, will be inserted as a posting of the transaction to the equity account. By default, this accumulation should be turned off. It\u2019s not clear whether the extra postings will be disruptive yet (if they\u2019re not, maybe this should be turned on by default; practice will inform us).","title":"Accumulating & Reporting Residuals"},{"location":"rounding_precision_in_beancount.html#implementation","text":"The implementation of this proposal is documented here .","title":"Implementation"},{"location":"running_beancount_and_generating_reports.html","text":"Running Beancount & Generating Reports \uf0c1 Martin Blais , July-Sep 2014 http://furius.ca/beancount/doc/tools Introduction Tools bean-check bean-report bean-query bean-web Global Pages View Reports Pages bean-bake bean-doctor Context bean-format bean-example Filtering Transactions Reports Balance Reports Trial Balance (balances) Balance Sheet (balsheet) Opening Balances (openbal) Income Statement (income) Journal Reports Journal (journal) Rendering at Cost Adding a Balance Column Character Width Precision Compact, Normal or Verbose Summary Equivalent SQL Query Conversions (conversions) Documents (documents) Holdings Reports Holdings & Aggregations (holdings*) Net Worth (networth) Other Report Types Cash Prices (prices) Statistics (stats) Update Activity (activity) Introduction \uf0c1 This document describes the tools you use to process Beancount input files, and many of the reports available from it. The syntax of the language is described in the Beancount Language Syntax document. This manual only covers the technical details for using Beancount from the command-line. Tools \uf0c1 bean-check \uf0c1 bean-check is the program you use to verify that your input syntax and transactions work correctly. All it does is load your input file and run the various plugins you configured in it, plus some extra validation checks. It report errors (if any), and then exits. You run it on your input file, like this: bean-check /path/to/my/file.beancount If there are no errors, there should be no output, it should exit quietly. If there were errors, they will be printed to stderr with the filename, line number and error description (in a format that is understood by Emacs, so you can just use next-error and previous-error to navigate to the file if you want): /home/user/myledger.beancount:44381: Transaction does not balance: 34.46 USD 2014-07-12 * \"Black Iron Burger\" \"\" Expenses:Food:Restaurant 17.23 USD Assets:Cash 17.23 USD You should always fix all the errors before producing reports. bean-report \uf0c1 This is the main tool used to extract specialized reports to the console in text or one of the various other formats. You invoke it like this: bean-report /path/to/my/file.beancount For example: bean-report /path/to/my/file.beancount balances There are many reports available. See the section on reports for a description of the main ones. If you want to produce the full list of reports, ask it for help: bean-report --help-reports Report names can sometimes accept arguments. At the moment the arguments are specified as part of the report name itself, often separated by a colon (:), like this: bean-report /path/to/my/file.beancount balances:Vanguard There are a few special reports you should know about: check, or validate: This is the same as running the bean-check command. print: This simply prints out the entries that Beancount has parsed, in Beancount syntax. This can be used to confirm that Beancount has read and interpreted your input data correctly (if you\u2019re debugging something difficult). The other reports are what you\u2019d expect: they print out various tables of aggregations of amounts. The reports you can generate are described in a dedicated section below. PLEASE NOTE! At the moment of release, the list of reports available from the web page will differ from the list available from the console. In a future release, I will consolidate those two lists and all the reports that are available from the web pages will also be available from the console, and in many different formats. Stay tuned. bean-query \uf0c1 Beancount\u2019s parsed list of transactions and postings is like an in-memory database. bean-query is a command-line tool that acts like a client to that in-memory database in which you can type queries in a variant of SQL. You invoke it like this: bean-query /path/to/my/file.beancount Input file: \"/path/to/my/file.beancount\" Ready with 14212 directives (21284 postings in 8879 transactions). beancount> _ More details are available in its own document . bean-web \uf0c1 bean-web serves all the reports on a web server that runs on your computer. You run it like this: bean-web /path/to/my/file.beancount It will serve all pages on port 8080 on your machine. Navigate to http://localhost:8080 with a web browser. You should be able to click your way through all the reports easily. The web interface provides a set of global pages and a set of report pages for each \u201cview.\u201d Global Pages \uf0c1 The top-level table of contents page provides links to all the global pages at the top: The table of contents (the page you\u2019re looking at) A list of the errors that occurred in your ledger file A view of the source code of your Ledger file (this is used by various other links when referring to a location in your input file.) The table of contents provides a convenient list of links to all the common views, such as \u201cview by year\u201d, \u201cview by tag\u201d, and of course, \u201cview all transactions.\u201d There are a few more. View Reports Pages \uf0c1 When you click on a view report page, you enter a set of pages for the subset of transactions for that view. Various reports about those transactions are available from here: Opening balances (a balance sheet at the beginning of the view) Balance sheet (a balance sheet at the end of the view) Income statement (for the period of the view) Various journals for each account (just click on an account name) Various reports of holdings at the end of the view Lists of documents and prices included in the view\u2019s entries Some statistics about the view data \u2026 and much more. There should be an index of all the available view reports. bean-bake \uf0c1 bean-bake runs a bean-web instance and bakes all the pages to a directory: bean-bake /path/to/my/file.beancount myfinances It also support baking directly to an archive file: bean-bake /path/to/my/file.beancount myfinances.zip Various compression methods are supported, e.g. .tar.gz. This is useful to share the web interface with your accountant or other people, who usually don\u2019t have the ability to run Beancount. The page links have all been converted to relative links, and they should be able to extract the archive to a directory and browse all the reports the same way you do with bean-web . bean-doctor \uf0c1 This is a debugging tool used to perform various diagnostics and run debugging commands, and to help provide information for reporting bugs. For example, it can do the following: List the Beancount dependencies that are installed on your machine, and the ones that are missing. It can tell you what you\u2019re missing that you should be installing. Print a dump of the parser\u2019s lexer tokens for your input file. If you report a parsing bug, it can be useful to look at the lexer output (if you know what you\u2019re doing). It can run your input file through a parsing round-trip, that is, print out the file and re-read it again and compare it. This is a useful parser and printer test. It can check that a directory hierarchy corresponds to a Beancount input file\u2019s chart-of-accounts, and reporting directories that do not comply. This is useful in case you decide to change some account names and are maintaining a corresponding archive of documents which needs to be adjusted accordingly. Context \uf0c1 It can list the context upon which a transaction is applied, i.e., the inventory balances of each account the transaction postings modify, before and after it is applied. Use it like this: $ bean-doctor context /home/blais/accounting/blais.beancount 28514 /home/blais/accounting/blais.beancount:28513: ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; Assets:US:ETrade:Cash 8143.97 USD 2014-07-25 * \"(TRD) BOT +50 BND @82.10\" ^273755872 Assets:US:ETrade:BND 50.00 BND {82.10 USD} Assets:US:ETrade:Cash -4105.00 USD ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; ! Assets:US:ETrade:BND 50.00 BND {82.10 USD} ; ! Assets:US:ETrade:Cash 4038.97 USD There is a corresponding Emacs binding (C-c x) to invoke this around the cursor. bean-format \uf0c1 This pure text processing tool will reformat Beancount input to right-align all the numbers at the same, minimal column. It left-aligns all the currencies. It only modifies whitespace. This tool accepts a filename as arguments and outputs the aligned file on stdout (similar to UNIX cat ). bean-example \uf0c1 This program generates an example Beancount input file. See the Tutorial for more details about the contents of this example file. Filtering Transactions \uf0c1 In order to produce different views of your financial transactions, we select a subset of full list of parsed transactions, for example, \u201call the transactions that occurred in year 2013\u201d, and then use that to produce the various available reports that Beancount provides. At the moment, only a preset list of filters are available as \u201cviews\u201d from the web interface. These views include: All transactions Transactions that occur in a particular year Transactions with a particular tag Transactions with a particular payee Transactions that involve at least one account with a particular name component At the moment, in order to access reports from these subsets of transactions, you need to use the bean-web web interface, and click on the related keyword in the root global page, which enters you into a set of reports for that view. Reports \uf0c1 The whole point of entering your transactions in a single input file in the first place is that it allows you to sum, filter, aggregate and arrange various subsets of your data into well-known reports. There are three distinct ways to produce reports from Beancount: by using bean-web and browsing to a view and then to a specific report (this is the easy way), by using bean-report and providing the name of a desired report (and possibly some report-specific arguments), and by using bean-query and requesting data by specifying an SQL statement. Reports can sometimes be rendered in different file formats. Each report type will support being rendered in a list of common ones, such as console text, HTML and CSV. Some reports render in Beancount syntax itself, and we simply call this format name \u201cbeancount.\u201d There are many types of reports available, and there will be many more in the future, as many of the features on the roadmap involve new types of output. This section provides an overview of the most common ones. Use bean-report to inquire about the full list supported by your installed version: bean-report --help-reports PLEASE NOTE! At the moment, the sets of reports that are available from the web interface and from the console are different, though there is some overlap. In a subsequent version, the list of reports will be reconciled and all reports will be made available via both the web interface and the console, in a variety of data formats (text, CSV, HTML and maybe others.) For now, we will document these in the sections below. Balance Reports \uf0c1 All the balance reports are similar in that they produce tables of some set of accounts and their associated balances: [output] |-- Assets | `-- US | |-- BofA | | `-- Checking 596.05 USD | |-- ETrade | | |-- Cash 5,120.50 USD | | |-- GLD 70.00 GLD | | |-- ITOT 17.00 ITOT | | |-- VEA 36.00 VEA | | `-- VHT 294.00 VHT | |-- Federal | | `-- PreTax401k | |-- Hoogle | | `-- Vacation 337.26 VACHR | `-- Vanguard ... Balances for commodities held \u201cat cost\u201d are rendered at their book value. (Unrealized gains, if any, are inserted as separate transactions by an optional plugin and the result amounts get mixed in with the cost basis if rendered in the same account.) If an account\u2019s balance contains many different types of currencies (commodities not held \u201cat cost\u201d, such as dollars, euros, yen), each gets printed on its own line. This can render the balance column a bit too busy and messes with the vertical regularity of account names. This is to some extent an unavoidable compromise, but in practice, there are only a small number of commodities that form the large majority of a user\u2019s ledger: the home country\u2019s currency units. To this extent, amounts in common currencies can be broken out into their own column using the \u201c operating_currency \u201d option: option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" You may use this option multiple times if you have many of them (this is my case, for instance, because I am an expat and hold assets in both my host and home countries). Declaring operating currencies also hold the advantage that the name of the currency need not be rendered, and it can thus more easily be imported into a spreadsheet. Finally, some accounts are deemed \u201cactive\u201d if they have not been closed. A closed account with no transactions in the filtered set of transactions will not be rendered. Trial Balance ( balances ) \uf0c1 A trial balance report simply produces a table of final balances of all the active accounts, with all the accounts rendered vertically. The sum total of all balances is reported at the bottom. Unlike a balance sheet, it may not always balance to zero because of currency conversions. (This is the equivalent of Ledger\u2019s bal report.) The equivalent bean-query command is: SELECT account, sum(position) GROUP BY account ORDER BY account; Balance Sheet ( balsheet ) \uf0c1 A balance sheet is a snapshot of the balances of the Assets, Liabilities and Equity accounts at a particular point in time. In order to build such a report, we have to move balances from the other accounts to it: compute the balances of the Income and Expenses account at that point in time, insert transactions that will zero out balances from these accounts by transferring them to an equity account ( Equity:Earnings:Current ), render a tree of the balances of the Assets accounts on the left side, render a tree of the Liabilities accounts on the right side, render a tree of the Equity accounts below the Liabilities accounts. See the introduction document for an example. Note that the Equity accounts include the amounts reported from the Income and Expenses accounts, also often called \u201cNet Income.\u201d Also, in practice, we make two transfers because we\u2019re typically looking at a reporting period, and we want to differentiate between Net Income amounts transferred before the beginning of the period ( Equity:Earnings:Previous ) and during the period itself ( Equity:Earnings:Current ). And similar pair of transfers is carried out in order to handle currency conversions (this is a bit of a hairy topic, but one with a great solution; refer to the dedicated document if you want all the details). The equivalent bean-query command is: SELECT account, sum(position) FROM CLOSE ON 2016-01-01 GROUP BY account ORDER BY account; Opening Balances ( openbal ) \uf0c1 The opening balances report is simply a balance sheet drawn at the beginning of the reporting period. This report only makes sense for a list of filtered entries that represents a period of time, such as \u201cyear 2014.\u201d The balance sheet is generated using only the summarization entries that were synthesized when the transactions were filtered (see the double-entry method document ). Income Statement ( income ) \uf0c1 An income statement lists the final balances of the Income and Expenses accounts. It represents a summary of the transient activity within these accounts. If the balance sheet is the snapshot at a particular point in time, this is the difference between the beginning and the end of a period (in our case: of a filtered set of transactions). The balances of the active Income accounts are rendered on the left, and those of the active Expenses accounts on the right. See the introduction document for an example. The difference between the total of Income and Expenses balances is the Net Income. Note that the initial balances of the Income and Expenses accounts should have been zero\u2019ed out by summarization transactions that occur at the beginning of the period, because we\u2019re only interested in the changes in these accounts. Journal Reports \uf0c1 The reports in this section render lists of transactions and other directives in a linear fashion. Journal ( journal ) \uf0c1 This report is the equivalent of an \u201caccount statement\u201d from an institution, a list of transactions with at least one posting in that account. This is the equivalent of Ledger\u2019s register report ( reg ). You generate a journal report like this: bean-report myfile.beancount journal \u2026 By default, this renders a journal of all the transactions, which is unlikely to be what you want. Select a particular account to render like this: bean-report myfile.beancount journal -a Expenses:Restaurant At the moment, the \u201c -a \u201d option accepts only a complete account name, or the name of one of the parent accounts. Eventually we will extend it to handle expressions. Rendering at Cost \uf0c1 The numbers column on the right displays the changes from the postings of the selected account. Notice that only the balances for the postings affecting the given account are rendered. The change column renders the changes in the units affected. For example, if this posting is selected: Assets:Investments:Apple 2 AAPL {402.00 USD} The value reported for the change will be \u201c 2 AAPL \u201d. If you would like to render the values at cost, use the \u201c --at-cost \u201d or \u201c -c \u201d option, which will in this case render \u201c 804.00 USD \u201d instead. There is no \u201cmarket value\u201d option. Unrealized gains are automatically inserted at the end of the history by the \u201c beancount.plugins.unrealized \u201d plugin. See options for that plugin to insert its unrealized gains. Note that if the sum of the selected postings is zero, no amount is rendered in the change column. Adding a Balance Column \uf0c1 If you want to add a column that sums up the running balance for the reported changes, use the \u201c --render-balance \u201d or \u201c -b \u201d option. This does not always make sense to report, so it is up to you to decide whether you want a running balance. Character Width \uf0c1 By default, the report will be as wide as your terminal allows. Restrict the width to a set number of characters with the \u201c-w\u201d option. Precision \uf0c1 The number of fractional digits for the number rendering can be specified via \u201c --precision \u201d or \u201c -k \u201d. Compact, Normal or Verbose \uf0c1 In its normal operation, Beancount renders an empty line between transactions. This helps delineate transactions where there are multiple currencies affected, as they render on separate lines. If you want a more compact rendering, use the \u201c --compact \u201d or \u201c -x \u201d option. On the other hand, if you want to render the affected postings under the transaction line, use the \u201c --verbose \u201d or \u201c -X \u201d option. Summary \uf0c1 Here is a summary of its arguments: optional arguments: -h, --help show this help message and exit -a ACCOUNT, --account ACCOUNT Account to render -w WIDTH, --width WIDTH The number of characters wide to render the report to -k PRECISION, --precision PRECISION The number of digits to render after the period -b, --render-balance, --balance If true, render a running balance -c, --at-cost, --cost If true, render values at cost -x, --compact Rendering compactly -X, --verbose Rendering verbosely Equivalent SQL Query \uf0c1 The equivalent bean-query command is: SELECT date, flag, description, account, cost(position), cost(balance); Conversions ( conversions ) \uf0c1 This report lists the total of currency conversions that result from the selected transactions. (Most people won\u2019t need this.) Documents ( documents ) \uf0c1 This report produces an HTML list of all the external documents found in the ledger, either from explicit directives or from plugins that automatically find the documents and add them to the stream of transactions. Holdings Reports \uf0c1 These reports produces aggregations for assets held at cost. Holdings & Aggregations ( holdings* ) \uf0c1 This report produces a detailed list of all holdings found in the ledger. You can produce aggregates by commodity and accounts using the \u201c-g\u201d option. Net Worth ( networth ) \uf0c1 This report produces a short summary of the net worth (equity) of the ledger, in each of the operating currencies. Other Report Types \uf0c1 Cash \uf0c1 This report renders balances in commodities not held at cost, in other words, cash: bean-report example.beancount cash -c USD Account Units Currency Cost Currency Average Cost Price Book Value Market Value -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ Assets:US:BofA:Checking 596.05 USD USD 596.05 596.05 Assets:US:ETrade:Cash 5,120.50 USD USD 5,120.50 5,120.50 Assets:US:Hoogle:Vacation 337.26 VACHR Assets:US:Vanguard:Cash -0.02 USD USD -0.02 -0.02 Liabilities:US:Chase:Slate -2,891.85 USD USD -2,891.85 -2,891.85 -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ The report allows you to convert all currencies to a common currency (in the example above, \"convert everything to USD\"). There's also an option to report only on the operating currencies. I use this to get an overview of all uninvested cash. Prices ( prices ) \uf0c1 This report renders a list of price points for a base currency in terms of a quote currency. The list is sorted by date. You can output this table in beancount format as well. This is convenient to save a price database to a file, that can then be combined and loaded into another input file. Statistics ( stats ) \uf0c1 This report simply provides various statistics on the parsed entries. Update Activity ( activity ) \uf0c1 This table renders for each account the date of the last entry.","title":"Running Beancount and Generating Reports"},{"location":"running_beancount_and_generating_reports.html#running-beancount-generating-reports","text":"Martin Blais , July-Sep 2014 http://furius.ca/beancount/doc/tools Introduction Tools bean-check bean-report bean-query bean-web Global Pages View Reports Pages bean-bake bean-doctor Context bean-format bean-example Filtering Transactions Reports Balance Reports Trial Balance (balances) Balance Sheet (balsheet) Opening Balances (openbal) Income Statement (income) Journal Reports Journal (journal) Rendering at Cost Adding a Balance Column Character Width Precision Compact, Normal or Verbose Summary Equivalent SQL Query Conversions (conversions) Documents (documents) Holdings Reports Holdings & Aggregations (holdings*) Net Worth (networth) Other Report Types Cash Prices (prices) Statistics (stats) Update Activity (activity)","title":"Running Beancount & Generating Reports"},{"location":"running_beancount_and_generating_reports.html#introduction","text":"This document describes the tools you use to process Beancount input files, and many of the reports available from it. The syntax of the language is described in the Beancount Language Syntax document. This manual only covers the technical details for using Beancount from the command-line.","title":"Introduction"},{"location":"running_beancount_and_generating_reports.html#tools","text":"","title":"Tools"},{"location":"running_beancount_and_generating_reports.html#bean-check","text":"bean-check is the program you use to verify that your input syntax and transactions work correctly. All it does is load your input file and run the various plugins you configured in it, plus some extra validation checks. It report errors (if any), and then exits. You run it on your input file, like this: bean-check /path/to/my/file.beancount If there are no errors, there should be no output, it should exit quietly. If there were errors, they will be printed to stderr with the filename, line number and error description (in a format that is understood by Emacs, so you can just use next-error and previous-error to navigate to the file if you want): /home/user/myledger.beancount:44381: Transaction does not balance: 34.46 USD 2014-07-12 * \"Black Iron Burger\" \"\" Expenses:Food:Restaurant 17.23 USD Assets:Cash 17.23 USD You should always fix all the errors before producing reports.","title":"bean-check"},{"location":"running_beancount_and_generating_reports.html#bean-report","text":"This is the main tool used to extract specialized reports to the console in text or one of the various other formats. You invoke it like this: bean-report /path/to/my/file.beancount For example: bean-report /path/to/my/file.beancount balances There are many reports available. See the section on reports for a description of the main ones. If you want to produce the full list of reports, ask it for help: bean-report --help-reports Report names can sometimes accept arguments. At the moment the arguments are specified as part of the report name itself, often separated by a colon (:), like this: bean-report /path/to/my/file.beancount balances:Vanguard There are a few special reports you should know about: check, or validate: This is the same as running the bean-check command. print: This simply prints out the entries that Beancount has parsed, in Beancount syntax. This can be used to confirm that Beancount has read and interpreted your input data correctly (if you\u2019re debugging something difficult). The other reports are what you\u2019d expect: they print out various tables of aggregations of amounts. The reports you can generate are described in a dedicated section below. PLEASE NOTE! At the moment of release, the list of reports available from the web page will differ from the list available from the console. In a future release, I will consolidate those two lists and all the reports that are available from the web pages will also be available from the console, and in many different formats. Stay tuned.","title":"bean-report"},{"location":"running_beancount_and_generating_reports.html#bean-query","text":"Beancount\u2019s parsed list of transactions and postings is like an in-memory database. bean-query is a command-line tool that acts like a client to that in-memory database in which you can type queries in a variant of SQL. You invoke it like this: bean-query /path/to/my/file.beancount Input file: \"/path/to/my/file.beancount\" Ready with 14212 directives (21284 postings in 8879 transactions). beancount> _ More details are available in its own document .","title":"bean-query"},{"location":"running_beancount_and_generating_reports.html#bean-web","text":"bean-web serves all the reports on a web server that runs on your computer. You run it like this: bean-web /path/to/my/file.beancount It will serve all pages on port 8080 on your machine. Navigate to http://localhost:8080 with a web browser. You should be able to click your way through all the reports easily. The web interface provides a set of global pages and a set of report pages for each \u201cview.\u201d","title":"bean-web"},{"location":"running_beancount_and_generating_reports.html#global-pages","text":"The top-level table of contents page provides links to all the global pages at the top: The table of contents (the page you\u2019re looking at) A list of the errors that occurred in your ledger file A view of the source code of your Ledger file (this is used by various other links when referring to a location in your input file.) The table of contents provides a convenient list of links to all the common views, such as \u201cview by year\u201d, \u201cview by tag\u201d, and of course, \u201cview all transactions.\u201d There are a few more.","title":"Global Pages"},{"location":"running_beancount_and_generating_reports.html#view-reports-pages","text":"When you click on a view report page, you enter a set of pages for the subset of transactions for that view. Various reports about those transactions are available from here: Opening balances (a balance sheet at the beginning of the view) Balance sheet (a balance sheet at the end of the view) Income statement (for the period of the view) Various journals for each account (just click on an account name) Various reports of holdings at the end of the view Lists of documents and prices included in the view\u2019s entries Some statistics about the view data \u2026 and much more. There should be an index of all the available view reports.","title":"View Reports Pages"},{"location":"running_beancount_and_generating_reports.html#bean-bake","text":"bean-bake runs a bean-web instance and bakes all the pages to a directory: bean-bake /path/to/my/file.beancount myfinances It also support baking directly to an archive file: bean-bake /path/to/my/file.beancount myfinances.zip Various compression methods are supported, e.g. .tar.gz. This is useful to share the web interface with your accountant or other people, who usually don\u2019t have the ability to run Beancount. The page links have all been converted to relative links, and they should be able to extract the archive to a directory and browse all the reports the same way you do with bean-web .","title":"bean-bake"},{"location":"running_beancount_and_generating_reports.html#bean-doctor","text":"This is a debugging tool used to perform various diagnostics and run debugging commands, and to help provide information for reporting bugs. For example, it can do the following: List the Beancount dependencies that are installed on your machine, and the ones that are missing. It can tell you what you\u2019re missing that you should be installing. Print a dump of the parser\u2019s lexer tokens for your input file. If you report a parsing bug, it can be useful to look at the lexer output (if you know what you\u2019re doing). It can run your input file through a parsing round-trip, that is, print out the file and re-read it again and compare it. This is a useful parser and printer test. It can check that a directory hierarchy corresponds to a Beancount input file\u2019s chart-of-accounts, and reporting directories that do not comply. This is useful in case you decide to change some account names and are maintaining a corresponding archive of documents which needs to be adjusted accordingly.","title":"bean-doctor"},{"location":"running_beancount_and_generating_reports.html#context","text":"It can list the context upon which a transaction is applied, i.e., the inventory balances of each account the transaction postings modify, before and after it is applied. Use it like this: $ bean-doctor context /home/blais/accounting/blais.beancount 28514 /home/blais/accounting/blais.beancount:28513: ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; Assets:US:ETrade:Cash 8143.97 USD 2014-07-25 * \"(TRD) BOT +50 BND @82.10\" ^273755872 Assets:US:ETrade:BND 50.00 BND {82.10 USD} Assets:US:ETrade:Cash -4105.00 USD ; Assets:US:ETrade:BND 100.00 BND {81.968730000000 USD} ; ! Assets:US:ETrade:BND 50.00 BND {82.10 USD} ; ! Assets:US:ETrade:Cash 4038.97 USD There is a corresponding Emacs binding (C-c x) to invoke this around the cursor.","title":"Context"},{"location":"running_beancount_and_generating_reports.html#bean-format","text":"This pure text processing tool will reformat Beancount input to right-align all the numbers at the same, minimal column. It left-aligns all the currencies. It only modifies whitespace. This tool accepts a filename as arguments and outputs the aligned file on stdout (similar to UNIX cat ).","title":"bean-format"},{"location":"running_beancount_and_generating_reports.html#bean-example","text":"This program generates an example Beancount input file. See the Tutorial for more details about the contents of this example file.","title":"bean-example"},{"location":"running_beancount_and_generating_reports.html#filtering-transactions","text":"In order to produce different views of your financial transactions, we select a subset of full list of parsed transactions, for example, \u201call the transactions that occurred in year 2013\u201d, and then use that to produce the various available reports that Beancount provides. At the moment, only a preset list of filters are available as \u201cviews\u201d from the web interface. These views include: All transactions Transactions that occur in a particular year Transactions with a particular tag Transactions with a particular payee Transactions that involve at least one account with a particular name component At the moment, in order to access reports from these subsets of transactions, you need to use the bean-web web interface, and click on the related keyword in the root global page, which enters you into a set of reports for that view.","title":"Filtering Transactions"},{"location":"running_beancount_and_generating_reports.html#reports","text":"The whole point of entering your transactions in a single input file in the first place is that it allows you to sum, filter, aggregate and arrange various subsets of your data into well-known reports. There are three distinct ways to produce reports from Beancount: by using bean-web and browsing to a view and then to a specific report (this is the easy way), by using bean-report and providing the name of a desired report (and possibly some report-specific arguments), and by using bean-query and requesting data by specifying an SQL statement. Reports can sometimes be rendered in different file formats. Each report type will support being rendered in a list of common ones, such as console text, HTML and CSV. Some reports render in Beancount syntax itself, and we simply call this format name \u201cbeancount.\u201d There are many types of reports available, and there will be many more in the future, as many of the features on the roadmap involve new types of output. This section provides an overview of the most common ones. Use bean-report to inquire about the full list supported by your installed version: bean-report --help-reports PLEASE NOTE! At the moment, the sets of reports that are available from the web interface and from the console are different, though there is some overlap. In a subsequent version, the list of reports will be reconciled and all reports will be made available via both the web interface and the console, in a variety of data formats (text, CSV, HTML and maybe others.) For now, we will document these in the sections below.","title":"Reports"},{"location":"running_beancount_and_generating_reports.html#balance-reports","text":"All the balance reports are similar in that they produce tables of some set of accounts and their associated balances: [output] |-- Assets | `-- US | |-- BofA | | `-- Checking 596.05 USD | |-- ETrade | | |-- Cash 5,120.50 USD | | |-- GLD 70.00 GLD | | |-- ITOT 17.00 ITOT | | |-- VEA 36.00 VEA | | `-- VHT 294.00 VHT | |-- Federal | | `-- PreTax401k | |-- Hoogle | | `-- Vacation 337.26 VACHR | `-- Vanguard ... Balances for commodities held \u201cat cost\u201d are rendered at their book value. (Unrealized gains, if any, are inserted as separate transactions by an optional plugin and the result amounts get mixed in with the cost basis if rendered in the same account.) If an account\u2019s balance contains many different types of currencies (commodities not held \u201cat cost\u201d, such as dollars, euros, yen), each gets printed on its own line. This can render the balance column a bit too busy and messes with the vertical regularity of account names. This is to some extent an unavoidable compromise, but in practice, there are only a small number of commodities that form the large majority of a user\u2019s ledger: the home country\u2019s currency units. To this extent, amounts in common currencies can be broken out into their own column using the \u201c operating_currency \u201d option: option \"operating_currency\" \"USD\" option \"operating_currency\" \"CAD\" You may use this option multiple times if you have many of them (this is my case, for instance, because I am an expat and hold assets in both my host and home countries). Declaring operating currencies also hold the advantage that the name of the currency need not be rendered, and it can thus more easily be imported into a spreadsheet. Finally, some accounts are deemed \u201cactive\u201d if they have not been closed. A closed account with no transactions in the filtered set of transactions will not be rendered.","title":"Balance Reports"},{"location":"running_beancount_and_generating_reports.html#trial-balance-balances","text":"A trial balance report simply produces a table of final balances of all the active accounts, with all the accounts rendered vertically. The sum total of all balances is reported at the bottom. Unlike a balance sheet, it may not always balance to zero because of currency conversions. (This is the equivalent of Ledger\u2019s bal report.) The equivalent bean-query command is: SELECT account, sum(position) GROUP BY account ORDER BY account;","title":"Trial Balance (balances)"},{"location":"running_beancount_and_generating_reports.html#balance-sheet-balsheet","text":"A balance sheet is a snapshot of the balances of the Assets, Liabilities and Equity accounts at a particular point in time. In order to build such a report, we have to move balances from the other accounts to it: compute the balances of the Income and Expenses account at that point in time, insert transactions that will zero out balances from these accounts by transferring them to an equity account ( Equity:Earnings:Current ), render a tree of the balances of the Assets accounts on the left side, render a tree of the Liabilities accounts on the right side, render a tree of the Equity accounts below the Liabilities accounts. See the introduction document for an example. Note that the Equity accounts include the amounts reported from the Income and Expenses accounts, also often called \u201cNet Income.\u201d Also, in practice, we make two transfers because we\u2019re typically looking at a reporting period, and we want to differentiate between Net Income amounts transferred before the beginning of the period ( Equity:Earnings:Previous ) and during the period itself ( Equity:Earnings:Current ). And similar pair of transfers is carried out in order to handle currency conversions (this is a bit of a hairy topic, but one with a great solution; refer to the dedicated document if you want all the details). The equivalent bean-query command is: SELECT account, sum(position) FROM CLOSE ON 2016-01-01 GROUP BY account ORDER BY account;","title":"Balance Sheet (balsheet)"},{"location":"running_beancount_and_generating_reports.html#opening-balances-openbal","text":"The opening balances report is simply a balance sheet drawn at the beginning of the reporting period. This report only makes sense for a list of filtered entries that represents a period of time, such as \u201cyear 2014.\u201d The balance sheet is generated using only the summarization entries that were synthesized when the transactions were filtered (see the double-entry method document ).","title":"Opening Balances (openbal)"},{"location":"running_beancount_and_generating_reports.html#income-statement-income","text":"An income statement lists the final balances of the Income and Expenses accounts. It represents a summary of the transient activity within these accounts. If the balance sheet is the snapshot at a particular point in time, this is the difference between the beginning and the end of a period (in our case: of a filtered set of transactions). The balances of the active Income accounts are rendered on the left, and those of the active Expenses accounts on the right. See the introduction document for an example. The difference between the total of Income and Expenses balances is the Net Income. Note that the initial balances of the Income and Expenses accounts should have been zero\u2019ed out by summarization transactions that occur at the beginning of the period, because we\u2019re only interested in the changes in these accounts.","title":"Income Statement (income)"},{"location":"running_beancount_and_generating_reports.html#journal-reports","text":"The reports in this section render lists of transactions and other directives in a linear fashion.","title":"Journal Reports"},{"location":"running_beancount_and_generating_reports.html#journal-journal","text":"This report is the equivalent of an \u201caccount statement\u201d from an institution, a list of transactions with at least one posting in that account. This is the equivalent of Ledger\u2019s register report ( reg ). You generate a journal report like this: bean-report myfile.beancount journal \u2026 By default, this renders a journal of all the transactions, which is unlikely to be what you want. Select a particular account to render like this: bean-report myfile.beancount journal -a Expenses:Restaurant At the moment, the \u201c -a \u201d option accepts only a complete account name, or the name of one of the parent accounts. Eventually we will extend it to handle expressions.","title":"Journal (journal)"},{"location":"running_beancount_and_generating_reports.html#rendering-at-cost","text":"The numbers column on the right displays the changes from the postings of the selected account. Notice that only the balances for the postings affecting the given account are rendered. The change column renders the changes in the units affected. For example, if this posting is selected: Assets:Investments:Apple 2 AAPL {402.00 USD} The value reported for the change will be \u201c 2 AAPL \u201d. If you would like to render the values at cost, use the \u201c --at-cost \u201d or \u201c -c \u201d option, which will in this case render \u201c 804.00 USD \u201d instead. There is no \u201cmarket value\u201d option. Unrealized gains are automatically inserted at the end of the history by the \u201c beancount.plugins.unrealized \u201d plugin. See options for that plugin to insert its unrealized gains. Note that if the sum of the selected postings is zero, no amount is rendered in the change column.","title":"Rendering at Cost"},{"location":"running_beancount_and_generating_reports.html#adding-a-balance-column","text":"If you want to add a column that sums up the running balance for the reported changes, use the \u201c --render-balance \u201d or \u201c -b \u201d option. This does not always make sense to report, so it is up to you to decide whether you want a running balance.","title":"Adding a Balance Column"},{"location":"running_beancount_and_generating_reports.html#character-width","text":"By default, the report will be as wide as your terminal allows. Restrict the width to a set number of characters with the \u201c-w\u201d option.","title":"Character Width"},{"location":"running_beancount_and_generating_reports.html#precision","text":"The number of fractional digits for the number rendering can be specified via \u201c --precision \u201d or \u201c -k \u201d.","title":"Precision"},{"location":"running_beancount_and_generating_reports.html#compact-normal-or-verbose","text":"In its normal operation, Beancount renders an empty line between transactions. This helps delineate transactions where there are multiple currencies affected, as they render on separate lines. If you want a more compact rendering, use the \u201c --compact \u201d or \u201c -x \u201d option. On the other hand, if you want to render the affected postings under the transaction line, use the \u201c --verbose \u201d or \u201c -X \u201d option.","title":"Compact, Normal or Verbose"},{"location":"running_beancount_and_generating_reports.html#summary","text":"Here is a summary of its arguments: optional arguments: -h, --help show this help message and exit -a ACCOUNT, --account ACCOUNT Account to render -w WIDTH, --width WIDTH The number of characters wide to render the report to -k PRECISION, --precision PRECISION The number of digits to render after the period -b, --render-balance, --balance If true, render a running balance -c, --at-cost, --cost If true, render values at cost -x, --compact Rendering compactly -X, --verbose Rendering verbosely","title":"Summary"},{"location":"running_beancount_and_generating_reports.html#equivalent-sql-query","text":"The equivalent bean-query command is: SELECT date, flag, description, account, cost(position), cost(balance);","title":"Equivalent SQL Query"},{"location":"running_beancount_and_generating_reports.html#conversions-conversions","text":"This report lists the total of currency conversions that result from the selected transactions. (Most people won\u2019t need this.)","title":"Conversions (conversions)"},{"location":"running_beancount_and_generating_reports.html#documents-documents","text":"This report produces an HTML list of all the external documents found in the ledger, either from explicit directives or from plugins that automatically find the documents and add them to the stream of transactions.","title":"Documents (documents)"},{"location":"running_beancount_and_generating_reports.html#holdings-reports","text":"These reports produces aggregations for assets held at cost.","title":"Holdings Reports"},{"location":"running_beancount_and_generating_reports.html#holdings-aggregations-holdings","text":"This report produces a detailed list of all holdings found in the ledger. You can produce aggregates by commodity and accounts using the \u201c-g\u201d option.","title":"Holdings & Aggregations (holdings*)"},{"location":"running_beancount_and_generating_reports.html#net-worth-networth","text":"This report produces a short summary of the net worth (equity) of the ledger, in each of the operating currencies.","title":"Net Worth (networth)"},{"location":"running_beancount_and_generating_reports.html#other-report-types","text":"","title":"Other Report Types"},{"location":"running_beancount_and_generating_reports.html#cash","text":"This report renders balances in commodities not held at cost, in other words, cash: bean-report example.beancount cash -c USD Account Units Currency Cost Currency Average Cost Price Book Value Market Value -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ Assets:US:BofA:Checking 596.05 USD USD 596.05 596.05 Assets:US:ETrade:Cash 5,120.50 USD USD 5,120.50 5,120.50 Assets:US:Hoogle:Vacation 337.26 VACHR Assets:US:Vanguard:Cash -0.02 USD USD -0.02 -0.02 Liabilities:US:Chase:Slate -2,891.85 USD USD -2,891.85 -2,891.85 -------------------------- --------- -------- ------------- ------------ ----- ---------- ------------ The report allows you to convert all currencies to a common currency (in the example above, \"convert everything to USD\"). There's also an option to report only on the operating currencies. I use this to get an overview of all uninvested cash.","title":"Cash"},{"location":"running_beancount_and_generating_reports.html#prices-prices","text":"This report renders a list of price points for a base currency in terms of a quote currency. The list is sorted by date. You can output this table in beancount format as well. This is convenient to save a price database to a file, that can then be combined and loaded into another input file.","title":"Prices (prices)"},{"location":"running_beancount_and_generating_reports.html#statistics-stats","text":"This report simply provides various statistics on the parsed entries.","title":"Statistics (stats)"},{"location":"running_beancount_and_generating_reports.html#update-activity-activity","text":"This table renders for each account the date of the last entry.","title":"Update Activity (activity)"},{"location":"settlement_dates_in_beancount.html","text":"Settlement Dates & Transfer Accounts in Beancount \uf0c1 Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-dates Motivation Proposal Description Remaining Questions Previous Work Ledger Effective and Auxiliary Dates References Motivation \uf0c1 When a trade executes in an investment account, there is most often a delay between the date that the transaction is carried out (the \u201ctransaction date\u201d) and the date that the funds are deposited in an associated cash account (the \u201csettlement date\u201d). This makes imported balance assertions sometimes requiring the fudging of their dates, and sometimes they can even be impossible. This document proposes the addition of an optional \u201csettlement date\u201d to be attached to a transaction or a posting, and associated semantics for how to deal with the problem. Proposal Description \uf0c1 Settlement Dates \uf0c1 In the first implementation of Beancount I used to have two dates attached to a transaction, but I never did anything with them. The alternate date would get attached but was ignored thereafter. The meaning of it is that it should have split the transaction into two, with some sort of transfer account, that might have been useful semantics, I never developed it. Something like this as input: 2014-06-23=2014-06-28 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} S Assets:ScottTrade:Cash where the \u201cS\u201d posting flag marks the leg \u201cto be postponed to the settlement date.\u201d Alternatively, you could attach the date to a posting: 2014-06-23 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} 2014-06-28 Assets:ScottTrade:Cash Both of the above syntax proposals allow you to specify which postings are meant to be postponed to settlement. The second one is more flexible, as each posting could potentially have a different date, but the more constrained syntax of the first would create less complications. Either of these could get translated to multiple transactions with a transfer account to absorb the pending amount: 2014-06-23 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} Assets:ScottTrade:Transfer 2014-06-28 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:Transfer Assets:ScottTrade:Cash 5890.00 USD So far, I\u2019ve been getting away with fudging the dates on balance assertions where necessary. I have relatively few sales, so this hasn\u2019t been a big problem so far. I\u2019m not convinced it needs a solution yet, maybe the best thing to do is just to document how to deal with the issue when it occurs. Maybe someone can convince me otherwise. Transfer Accounts \uf0c1 In the previous section, we discuss a style whereby a single entry moving money between two accounts contains two dates and results in two separate entries. An auxiliary problem, which is related in its solution, is how to carry out the reverse operation, that is, how to merge two separate entries posting to a common transfer account (sometimes called a \u201c suspense account \u201d). For example, a user may want to input the two sides of a transaction separately, e.g. by running import scripts on separate input files, and instead of having to reconcile and merge those by hand, we would want to explicitly support this by identifying matching transactions to these transfer accounts and creating a common link between them. Most importantly, we want to be able to easily identify which of the transactions is not matched on the other side, which indicates missing data. There is a prototype of this under beancount.plugins.tag_pending . Also see redstreet0\u2019s \u201c zerosum \u201d plugin from this thread . Remaining Questions \uf0c1 How do we determine a proper transfer account name? Is a subaccount a reasonable approach? What if a user would like to have a single global limbo account? TODO Does this property solve the problem of making balance assertions between trade and settlement? TODO [Write out a detailed example] Any drawbacks? TODO How does this affect the balance sheet and income statement, if any? Is it going to be obvious to users what the amounts in these limbo/transfer accounts are? TODO Unrooting Transactions \uf0c1 A wilder idea would be to add an extra level in the transaction-posting hierarchy, adding the capability to group multiple partial transactions, and move the balancing rule to that level. Basically, two transactions input separately and then grouped - by some rule, or trivially by themselves - could form a new unit of balance rule. That would be a much more demanding change on the schema and on the Beancount design but would allow to natively support partial transactions, keeping their individual dates, descriptions, etc. Maybe that's a better model? Consider the advantages. Previous Work \uf0c1 Ledger Effective and Auxiliary Dates \uf0c1 Ledger has the concept of \u201c auxiliary dates \u201d. The way these work is straightforward: any transaction may have a second date, and the user can select at runtime (with --aux-date ) whether the main date or the auxiliary dates are meant to be used. It is unclear to me how this is meant to be used in practice, in the presence of balance assertions. Without balance assertions, I can see how it would just work: you\u2019d render everything with settlement dates only. This would probably only make sense for specific reports. I would much rather keep a single semantic for the set of transactions that gets parsed in; the idea that the meaning of the transactions varies depending on the invocation conditions would set a precedent in Beancount, I\u2019d prefer not to break this nice property, so by default I\u2019d prefer to avoid implementing this solution. Auxiliary dates are also known as \u201c effective dates \u201d and can be associated with each individual posting. Auxiliary dates are secondary to the the \u201cprimary date\u201d or the \u201cactual date\u201d, being the posting date of the record): 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] Expenses:Food:Groceries $ 37.50 ; [=2008/12/01] Expenses:Food:Groceries $ 37.50 ; [=2009/01/01] Expenses:Food:Groceries $ 37.50 ; [=2009/02/01] Expenses:Food:Groceries $ 37.50 ; [=2009/03/01] Assets:Checking The original motivation for this was for budgeting, allow one to move accounting of expenses to neighboring budget periods in order to carry over actual paid amounts to those periods. Bank amounts in one month could be set against a budget from the immediately preceding or following month, as needed. (Note: John Wiegley) This is similar to one of the syntaxes I\u2019m suggesting above\u2014letting the user specify a date for each posting\u2014but the other postings are not split as independent transactions. The usage of those dates is similarly triggered by a command-line option ( --effective ). I\u2019m assuming that the posting on the checking account above occurs at once at 2008/10/16, regardless of reporting date. Let\u2019s verify this: $ ledger -f settlement1.lgr reg checking --effective 08-Oct-16 Bountiful Blessings.. Assets:Checking $ -225.00 $ -225.00 That\u2019s what I thought. This works, but a problem with this approach is that any balance sheet drawn between 2008/10/01 (the earliest effective date) and 2009/03/01 (the latest effective date) would not balance. Between those dates, some amounts are \u201cin limbo\u201d and drawing up a balance sheet at one of those dates would not balance. This would break an invariant in Beancount: we require that you should always be able to draw a balance sheet at any point in time, and any subset of transactions should balance. I would rather implement this by splitting this example transaction into many other ones, as in the proposal above, moving those temporary amounts living in limbo in an explicit \u201climbo\u201d or \u201ctransfer\u201d account, where each transaction balances. Moreover, this step can be implemented as a transformation stage, replacing the transaction with effective dates by one transaction for each posting where the effective date differs from the transaction\u2019s date (this could be enabled on demand via a plugin). GnuCash \uf0c1 TODO(blais) - How is this handled in GnuCash and other GUI systems? Is there a standard account method? References \uf0c1 The IRS requires you to use the trade date and NOT the settlement date for tax reporting; from the IRS Publication 17: Securities traded on established market. For securities traded on an established securities market, your holding period begins the day after the trade date you bought the securities, and ends on the trade date you sold them. Do not confuse the trade date with the settlement date, which is the date by which the stock must be delivered and payment must be made. Example. You are a cash method, calendar year taxpayer. You sold stock at a gain on December 30, 2013. According to the rules of the stock exchange, the sale was closed by delivery of the stock 4 trading days after the sale, on January 6, 2014. You received payment of the sales price on that same day. Report your gain on your 2013 return, even though you received the payment in 2014. The gain is long term or short term depending on whether you held the stock more than 1 year. Your holding period ended on December 30. If you had sold the stock at a loss, you would also report it on your 2013 return. Threads \uf0c1 An interesting \"feature by coincidence\" First Opinions, Coming from Ledger","title":"Settlement Dates in Beancount"},{"location":"settlement_dates_in_beancount.html#settlement-dates-transfer-accounts-in-beancount","text":"Martin Blais, July 2014 http://furius.ca/beancount/doc/proposal-dates Motivation Proposal Description Remaining Questions Previous Work Ledger Effective and Auxiliary Dates References","title":"Settlement Dates & Transfer Accounts in Beancount"},{"location":"settlement_dates_in_beancount.html#motivation","text":"When a trade executes in an investment account, there is most often a delay between the date that the transaction is carried out (the \u201ctransaction date\u201d) and the date that the funds are deposited in an associated cash account (the \u201csettlement date\u201d). This makes imported balance assertions sometimes requiring the fudging of their dates, and sometimes they can even be impossible. This document proposes the addition of an optional \u201csettlement date\u201d to be attached to a transaction or a posting, and associated semantics for how to deal with the problem.","title":"Motivation"},{"location":"settlement_dates_in_beancount.html#proposal-description","text":"","title":"Proposal Description"},{"location":"settlement_dates_in_beancount.html#settlement-dates","text":"In the first implementation of Beancount I used to have two dates attached to a transaction, but I never did anything with them. The alternate date would get attached but was ignored thereafter. The meaning of it is that it should have split the transaction into two, with some sort of transfer account, that might have been useful semantics, I never developed it. Something like this as input: 2014-06-23=2014-06-28 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} S Assets:ScottTrade:Cash where the \u201cS\u201d posting flag marks the leg \u201cto be postponed to the settlement date.\u201d Alternatively, you could attach the date to a posting: 2014-06-23 * \"Sale\" Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} 2014-06-28 Assets:ScottTrade:Cash Both of the above syntax proposals allow you to specify which postings are meant to be postponed to settlement. The second one is more flexible, as each posting could potentially have a different date, but the more constrained syntax of the first would create less complications. Either of these could get translated to multiple transactions with a transfer account to absorb the pending amount: 2014-06-23 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:GOOG -10 GOOG {589.00 USD} Assets:ScottTrade:Transfer 2014-06-28 * \"Sale\" ^settlement-3463873948 Assets:ScottTrade:Transfer Assets:ScottTrade:Cash 5890.00 USD So far, I\u2019ve been getting away with fudging the dates on balance assertions where necessary. I have relatively few sales, so this hasn\u2019t been a big problem so far. I\u2019m not convinced it needs a solution yet, maybe the best thing to do is just to document how to deal with the issue when it occurs. Maybe someone can convince me otherwise.","title":"Settlement Dates"},{"location":"settlement_dates_in_beancount.html#transfer-accounts","text":"In the previous section, we discuss a style whereby a single entry moving money between two accounts contains two dates and results in two separate entries. An auxiliary problem, which is related in its solution, is how to carry out the reverse operation, that is, how to merge two separate entries posting to a common transfer account (sometimes called a \u201c suspense account \u201d). For example, a user may want to input the two sides of a transaction separately, e.g. by running import scripts on separate input files, and instead of having to reconcile and merge those by hand, we would want to explicitly support this by identifying matching transactions to these transfer accounts and creating a common link between them. Most importantly, we want to be able to easily identify which of the transactions is not matched on the other side, which indicates missing data. There is a prototype of this under beancount.plugins.tag_pending . Also see redstreet0\u2019s \u201c zerosum \u201d plugin from this thread .","title":"Transfer Accounts"},{"location":"settlement_dates_in_beancount.html#remaining-questions","text":"How do we determine a proper transfer account name? Is a subaccount a reasonable approach? What if a user would like to have a single global limbo account? TODO Does this property solve the problem of making balance assertions between trade and settlement? TODO [Write out a detailed example] Any drawbacks? TODO How does this affect the balance sheet and income statement, if any? Is it going to be obvious to users what the amounts in these limbo/transfer accounts are? TODO","title":"Remaining Questions"},{"location":"settlement_dates_in_beancount.html#unrooting-transactions","text":"A wilder idea would be to add an extra level in the transaction-posting hierarchy, adding the capability to group multiple partial transactions, and move the balancing rule to that level. Basically, two transactions input separately and then grouped - by some rule, or trivially by themselves - could form a new unit of balance rule. That would be a much more demanding change on the schema and on the Beancount design but would allow to natively support partial transactions, keeping their individual dates, descriptions, etc. Maybe that's a better model? Consider the advantages.","title":"Unrooting Transactions"},{"location":"settlement_dates_in_beancount.html#previous-work","text":"","title":"Previous Work"},{"location":"settlement_dates_in_beancount.html#ledger-effective-and-auxiliary-dates","text":"Ledger has the concept of \u201c auxiliary dates \u201d. The way these work is straightforward: any transaction may have a second date, and the user can select at runtime (with --aux-date ) whether the main date or the auxiliary dates are meant to be used. It is unclear to me how this is meant to be used in practice, in the presence of balance assertions. Without balance assertions, I can see how it would just work: you\u2019d render everything with settlement dates only. This would probably only make sense for specific reports. I would much rather keep a single semantic for the set of transactions that gets parsed in; the idea that the meaning of the transactions varies depending on the invocation conditions would set a precedent in Beancount, I\u2019d prefer not to break this nice property, so by default I\u2019d prefer to avoid implementing this solution. Auxiliary dates are also known as \u201c effective dates \u201d and can be associated with each individual posting. Auxiliary dates are secondary to the the \u201cprimary date\u201d or the \u201cactual date\u201d, being the posting date of the record): 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] Expenses:Food:Groceries $ 37.50 ; [=2008/12/01] Expenses:Food:Groceries $ 37.50 ; [=2009/01/01] Expenses:Food:Groceries $ 37.50 ; [=2009/02/01] Expenses:Food:Groceries $ 37.50 ; [=2009/03/01] Assets:Checking The original motivation for this was for budgeting, allow one to move accounting of expenses to neighboring budget periods in order to carry over actual paid amounts to those periods. Bank amounts in one month could be set against a budget from the immediately preceding or following month, as needed. (Note: John Wiegley) This is similar to one of the syntaxes I\u2019m suggesting above\u2014letting the user specify a date for each posting\u2014but the other postings are not split as independent transactions. The usage of those dates is similarly triggered by a command-line option ( --effective ). I\u2019m assuming that the posting on the checking account above occurs at once at 2008/10/16, regardless of reporting date. Let\u2019s verify this: $ ledger -f settlement1.lgr reg checking --effective 08-Oct-16 Bountiful Blessings.. Assets:Checking $ -225.00 $ -225.00 That\u2019s what I thought. This works, but a problem with this approach is that any balance sheet drawn between 2008/10/01 (the earliest effective date) and 2009/03/01 (the latest effective date) would not balance. Between those dates, some amounts are \u201cin limbo\u201d and drawing up a balance sheet at one of those dates would not balance. This would break an invariant in Beancount: we require that you should always be able to draw a balance sheet at any point in time, and any subset of transactions should balance. I would rather implement this by splitting this example transaction into many other ones, as in the proposal above, moving those temporary amounts living in limbo in an explicit \u201climbo\u201d or \u201ctransfer\u201d account, where each transaction balances. Moreover, this step can be implemented as a transformation stage, replacing the transaction with effective dates by one transaction for each posting where the effective date differs from the transaction\u2019s date (this could be enabled on demand via a plugin).","title":"Ledger Effective and Auxiliary Dates"},{"location":"settlement_dates_in_beancount.html#gnucash","text":"TODO(blais) - How is this handled in GnuCash and other GUI systems? Is there a standard account method?","title":"GnuCash"},{"location":"settlement_dates_in_beancount.html#references","text":"The IRS requires you to use the trade date and NOT the settlement date for tax reporting; from the IRS Publication 17: Securities traded on established market. For securities traded on an established securities market, your holding period begins the day after the trade date you bought the securities, and ends on the trade date you sold them. Do not confuse the trade date with the settlement date, which is the date by which the stock must be delivered and payment must be made. Example. You are a cash method, calendar year taxpayer. You sold stock at a gain on December 30, 2013. According to the rules of the stock exchange, the sale was closed by delivery of the stock 4 trading days after the sale, on January 6, 2014. You received payment of the sales price on that same day. Report your gain on your 2013 return, even though you received the payment in 2014. The gain is long term or short term depending on whether you held the stock more than 1 year. Your holding period ended on December 30. If you had sold the stock at a loss, you would also report it on your 2013 return.","title":"References"},{"location":"settlement_dates_in_beancount.html#threads","text":"An interesting \"feature by coincidence\" First Opinions, Coming from Ledger","title":"Threads"},{"location":"sharing_expenses_with_beancount.html","text":"Sharing Expenses in Beancount \uf0c1 Martin Blais , May 2015 http://furius.ca/beancount/doc/shared Introduction \uf0c1 This document presents a method for precisely and easily accounting for shared expenses in complicated group situations. For example, traveling with a group of friends where people pay for different things. We show it is possible to deal with expenses to be split evenly among the group as well as group expenses that should be allocated to specific persons. The method uses the double-entry accounting system. The essence of the method consists in separating the accounting of expenses and their associated payments. This makes it much easier to deal with shared costs, because using this method it doesn\u2019t matter who pays for what: we reconcile the precise amounts owed for each individual at the end and automatically compute final correcting transfers for each. This article is structured around a simple trip with expenses shared equally by two people. However, the same method generalizes to any type of project with incomes and expenses to be shared among multiple participants, and splitting expenses evenly or not. A Travel Example \uf0c1 Martin (the author) and Caroline (his girlfriend) visited Mexico in March 2015. We visited the island of Cozumel for a SCUBA diving trip for three days and then headed to Tulum for two days to relax and dive in the cenotes . Our assumptions are: Chaotic payments . We lead very busy lives\u2026 both of us are going to make payments ahead of time and during the trip, without any forethought regarding who will pay for what, though we will carefully record every payment. Each of us just pays for whatever preparation costs as needed to arrange this trip. For example, Caroline selected and booked flights early while I paid for the resort and booked the rental and activities at the local dive shop one week before departure. Use of shared and individual assets. Both of us are bringing cash, and are going to make payments during the trip from our respective wallets as well as from a shared pool of cash converted to local currency (Mexican pesos, for which I will use the \u201cMXN\u201d symbol), and we will use credit cards as necessary before and during the trip. Multiple currencies. Some of our expenses will be denominated in US dollars and some in Mexican pesos. For example, flights were paid in US dollars, local meals and the accommodation were paid in pesos, but the local dive shop charged us dollars. Converted amounts will come from both cash and credit cards sources. Individual expenses in a shared pool. While most of the expenses are to be shared equally, some of the expenses will apply to only one of us, and we want to account for those explicitly. For example, Caroline took a SCUBA certification course ( PADI Open Water ) and will pay for that on her own; similarly, she should not be paying for Martin\u2019s expensive boat diving costs. To complicate matters, the dive shop issued us a single joint bill for everything at the end of our stay. A Note About Sharing \uf0c1 I feel that something should be said about the \u201csharing\u201d aspect of our expenses, as this topic has come up on previous discussions on the mailing-lists involving sharing examples. We are nitpicking on purpose. For the purpose of this exercise, we are accounting for every little penny spent in an incredibly detailed manner. The point of this document is specifically to show how a complex set of transactions well accounted for can be efficiently and simply disentangled to a precise accounting of expenses for each participant, regardless of who actually makes the payments. We are not cheapskates. We will assume that we\u2019ve decided to split expenses evenly. Our \u201cgenerosity\u201d to each other is not a topic relevant to this document. We\u2019re both well compensated working professionals and you can assume that we\u2019ve agreed to split the common costs for this trip evenly (50/50). One of the attributes of the method we show here is that the decision of how we choose to split expenses can be made separately from the actual payment of costs. For example, we may decide in the end that I pay for \u2154rd of the trip, but that can be decided precisely rather than in an ad-hoc \u201coh, I think I remember I paid for this\u201d manner. This is especially useful in a larger group of people because when expenses aren\u2019t tracked accurately, usually everyone is left with a feeling that they paid more than the others. The sum of the perceived fractions of expenses paid in a group is always greater than 100%... Overview of the Method \uf0c1 In this section we present a brief illustrated overview of the method. A set of common Assets accounts that belong to the project, and book all our individual expenses and transfer for the trip as coming from external Income accounts: During the trip, we use the common Assets to make expenses. Most of the expenses are attributed to both of us (and to be shared eventually), but some of the expenses are intended to be attributed to each of us individually: After the trip, remaining Assets (like cash we walked home with) gets distributed back to ourselves to zero out the balances of the Assets accounts and we record this through contra postings to Income accounts: Finally, the list of shared Expenses are split between each other\u2014using a plugin that forks every posting that is intended to be a shared expense\u2014and the final amount is used to make a final transfer between each other so that we\u2019ve each paid for our respective expenses and we\u2019re square: Note that the final balance of expenses for each participant may differ, and these are due to particular expenses that were attributed separately, or if we decide to split the total unevenly. How to Track Expenses \uf0c1 In order to write down all expenses for this trip we will open a brand new Beancount input file. Despite the fact that the expenses will come from each person\u2019s personal accounts, it is useful to think of the trip as a special project, as if it were a separate entity, e.g., a company that exists only for the duration of the trip. The example file we wrote for our trip can be found here and should help you follow along this text. Accounts \uf0c1 The set of accounts in the input file should not have to match your personal Beancount file\u2019s account names. The accounts we will use include accounts that correspond to Martin and Caroline\u2019s personal accounts but with generic names (e.g., Income:Martin:CreditCard instead of Liabilities:US:Chase ), and expense accounts may not match any regular expenses in my personal Beancount file\u2014it\u2019s not important. As a convention, any account that pertains specifically to one of the travelers will include that person\u2019s name in the account name. For example, Caroline\u2019s credit card will be named \u201c Income:Caroline:CreditCard \u201d. This is important, as we will use this later on to split contributions and expenses. Let\u2019s examine the different types of accounts we will need to carry this out. External Income Accounts \uf0c1 The \u201cproject\u201d will receive income in the form of transfers from personal accounts of either traveler. These are accounts we will consider external to the project and so will define them as Income accounts: ;; External accounts for Martin. 2015-02-01 open Income:Martin:Cash 2015-02-01 open Income:Martin:Cash:Foreign 2015-02-01 open Income:Martin:Wallet 2015-02-01 open Income:Martin:CreditCard ;; External accounts for Caroline. 2015-02-01 open Income:Caroline:Cash 2015-02-01 open Income:Caroline:Wallet 2015-02-01 open Income:Caroline:MetroCard 2015-02-01 open Income:Caroline:CreditCard Transactions carried out from these accounts must be copied from your personal Beancount file. Obviously, you must be careful to include all the transactions pertaining to the trip. I used a tag to do this in my personal file. Assets & Liabilities Accounts \uf0c1 There will be a few Asset accounts that will be active and exist for the duration of the trip. These temporary accounts will be zero\u2019ed out at the end of it. One example is a pool of petty cash in local currency: 2015-02-01 open Assets:Cash:Pesos description: \"A shared account to contain our pocket of pesos\" We also carried cash in each of our pockets while traveling, so I created two separate accounts for that: 2015-02-01 open Assets:Cash:Martin description: \"Cash for the trip held by Martin\" 2015-02-01 open Assets:Cash:Caroline description: \"Cash for the trip held by Caroline\" Note however that despite their individual names, those accounts are considered as part of the project. It was just convenient to separately track balances for the cash we each held during the trip. Expenses Accounts \uf0c1 We will define various accounts to book our Expenses to. For example, \u201c Expenses:Flights \u201d will contain our costs associated with flight travel. For convenience, and because there are many types of expenses in this file, we chose to leverage the \u201cauto-accounts\u201d plugin and let Beancount automatically open these accounts: plugin \"beancount.ops.auto_accounts\" The great majority of these accounts are for shared expenses to be split between us. For example, shared SCUBA diving expenses will be booked to \u201c Expenses:Scuba \u201d. However, for expenses that are intended to be covered by one of us only, we simply include the name of the traveler in the account name. For example, Martin\u2019s extra costs for boat diving will be booked to the \u201c Expenses:Scuba:Martin \u201d account. Example Transactions \uf0c1 Let\u2019s turn our attention to the different types of transactions present in the file. In this section I will walk you through some representative transactions (the names I give to these are arbitrary). Contributing Transactions \uf0c1 Contributions to the project\u2019s costs are usually done in the form of expenses paid from external accounts. For example, Caroline paying for flights with her credit card looks like this: 2015-02-01 * \"Flights to Cancun\" Income:Caroline:CreditCard -976.00 USD Expenses:Flights Martin paying for the taxi to the airport looks like this: 2015-02-25 * \"Taxi to airport\" ^433f66ea0e4e Expenses:Transport:Taxi 62.80 USD Income:Martin:CreditCard Bringing Cash \uf0c1 We both brought some cash on us, as we were warned it might be difficult to use credit cards in Mexico. In my personal Beancount file, the cash account is \u201c Assets:Cash \u201d but here it must get booked as an external contribution with my name on it: ;; Initial cash on us. 2015-02-24 * \"Getting cash for travel\" Income:Martin:Cash -1200 USD Assets:Cash:Martin 1200 USD Caroline\u2019s cash is similar: 2015-02-24 * \"Getting cash for travel\" Income:Caroline:Cash -300 USD Assets:Cash:Caroline 300 USD Once again, note that the Assets:Cash:Martin and Assets:Cash:Caroline accounts are considered a part of the project, in this case it just refers to who is carrying it. (These accounts are cleared at the end, so it does not matter that our names are in them.) Transfers \uf0c1 Before the trip, I was really busy. It looked like Caroline was going to make most of arrangements and upfront payments, so I made a transfer to her Google Wallet to help her cover for some expenses ahead of time: 2015-02-01 * \"Transfer Martin -> Caroline on Google Wallet\" Income:Martin:Wallet -1000 USD Income:Caroline:Wallet 1000 USD Cash Conversions \uf0c1 Obtaining local currency was done by changing a small amount of cash at the airport (at a very bad rate): 2015-02-25 * \"Exchanged cash at XIC at CUN airport\" Assets:Cash:Caroline -100.00 USD @ 12.00 MXN Assets:Cash:Pesos 1200 MXN The \u201c Assets:Cash:Pesos \u201d account tracks our common pool of local currency that we use for various small expenses. Cash Expenses in US Dollars \uf0c1 Some local expenses will call for US money, which in this example I paid from my cash pocket: 2015-03-01 * \"Motmot Diving\" | \"Deposit for cenote diving\" Expenses:Scuba 50.00 USD Assets:Cash:Martin Cash Expenses in Local Currency \uf0c1 Paying cash using pesos from our shared stash of pesos looks like this: 2015-02-25 * \"UltraMar Ferry across to Cozumel\" Expenses:Transport:Bus 326 MXN Assets:Cash:Pesos Sometimes we even had to pay with a mix of US dollars and pesos. In this example, we ran out of pesos, so we have to give them dollars and pesos (all the restaurants and hotels in the beach part of Tulum accept US currency): 2015-03-01 * \"Hartwood\" | \"Dinner - ran out of pesos\" Expenses:Restaurant 1880 MXN Assets:Cash:Pesos -1400 MXN Assets:Cash:Martin -40.00 USD @ 12.00 MXN I used the unfavorable rate the restaurant was offering to accept dollars at (the market rate was 14.5 at the time). Individual Expenses \uf0c1 Here is an example of booking individual expenses using shared money. In order for us to have access to the reef for diving, we had to pay a \u201cmarine park\u201d fee of $2.50 per day to the island. This was a short trip where I dove only three days there and Caroline\u2019s fee was included in her course except for one day: 2015-02-25 * \"Marine Park (3 days Martin, 1 day Caroline)\" Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Scuba:ParkFees:Caroline 2.50 USD Assets:Cash:Martin All that needs to be done is to book these to expense accounts with our names in them, which will get separated out at the end. Here is a more complicated example: the dive shop at Scuba Club Cozumel charged us a single bill at the end of our stay for all our rental gear and extra dives. All I did here was translate the itemized bill into a single transaction, booking to each other: 2015-03-01 * \"Scuba Club Cozumel\" | \"Dive shop bill\" ^69b409189b37 Income:Martin:CreditCard -381.64 USD Expenses:Scuba:Martin 27 USD ;; Regulator w/ Gauge Expenses:Scuba:Caroline 9 USD ;; Regulator w/ Gauge Expenses:Scuba:Martin 27 USD ;; BCD Expenses:Scuba:Caroline 9 USD ;; BCD Expenses:Scuba:Martin 6 USD ;; Fins Expenses:Scuba:Martin 24 USD ;; Wetsuit Expenses:Scuba:Caroline 8 USD ;; Wetsuit Expenses:Scuba:Caroline 9 USD ;; Dive computer Expenses:Scuba:Martin 5 USD ;; U/W Light Expenses:Scuba:Caroline 70 USD ;; Dive trip (2 tank) Expenses:Scuba:Martin 45 USD ;; Wreck Dive w/ Lite Expenses:Scuba:Martin 45 USD ;; Afternoon dive Expenses:Scuba:Caroline 45 USD ;; Afternoon dive Expenses:Scuba:Martin 28.64 USD ;; Taxes Expenses:Scuba:Caroline 24.00 USD ;; Taxes Final Balances \uf0c1 Of course you can use balance assertions at any time during the trip. For example, just before leaving at the Cancun airport we knew we wouldn\u2019t spend any more Mexican pesos for a while, so I counted what we had left after Caroline decided to splurge the remainder of them into overpriced chocolate sold at the airport shop: 2015-03-04 balance Assets:Cash:Pesos 65 MXN Ideally the bookkeeper should want to do this at a quiet moment at the end of every day or couple of days, which makes it easier to triangulate an expense you might have forgotten to enter (we are on vacation after all, in our relaxed state of mind we forget to write down stuff here and there). Clearing Asset Accounts \uf0c1 At the end of the trip, you should clear the final balances of all Assets and Liabilities accounts by transferring the remaining funds out to the participants, i.e., to the Income accounts. This should leave all the balances of the trip at zero and ensures all the cash put forward for trip expenses has been moved out to travelers. By the way, it doesn\u2019t matter much who keeps this money, because at the end we will end up making a single correcting transfer that should account for the balance required to create an even split. You can transfer it to anyone; the end result will be the same. Our clearing transaction looked like this: 2015-03-06 * \"Final transfer to clear internal balances to external ones\" Assets:Cash:Pesos -65 MXN Income:Martin:Cash:Foreign 60 MXN Income:Caroline:Cash 5 MXN Assets:Cash:Martin -330 USD Income:Martin:Cash 330 USD Assets:Cash:Caroline -140 USD Income:Caroline:Cash 140 USD 2015-03-07 balance Assets:Cash:Pesos 0 MXN 2015-03-07 balance Assets:Cash:Pesos 0 USD 2015-03-07 balance Assets:Cash:Martin 0 USD 2015-03-07 balance Assets:Cash:Caroline 0 USD We had three 20 peso bills, and I kept the bills for future travels. Caroline kept the 5 peso coin (forgot to hand it over as a tip). We transferred out the respective cash amounts we had been carrying together during the trip. How to Take Notes \uf0c1 During this trip I did not carry a laptop\u2014this was vacation after all. I like to disconnect. I did not carry a notebook either. Instead, I just took notes at the end of the day on a few sheets of paper at the hotel. This process took about 5-10 minutes each night, just recalling from memory and writing things down. These notes look like this: I made a paper spreadsheet where each line had The narration for the transaction (a description that would allow me to select an Expenses account later on) Who paid (which Assets or Income account the money came from) The amount (either in USD or MXN) After our trip, I sat down at the computer and typed the corresponding Beancount file . If I had a computer during my vacation I probably would have typed it as we went along. Of course, I had to do a few adjustments here and there because of mistakes. The bottom line is: if you\u2019re organized well, the overhead of doing this is minimal. Reconciling Expenses \uf0c1 Running bean-web on the trip\u2019s file: bean-web beancount/examples/sharing/cozumel2015.beancount You can view the balances in the \u201cAll Transactions\u201d view (click on \u201cAll Transactions\u201d). The Balance Sheet should show empty balances for Assets accounts: The balances of the equity accounts should reflect the total amount of currency conversions made during the trip. You can verify this by calculating the amount-weight average rate like this: 7539.00 / 559.88 ~= 13.465 USD/MXN (which is about right). Reviewing Contributions \uf0c1 The Income Statement should show a summary of all expenses and contributions to the project: The Income account balances show the total amounts of contributions for each person. Note that in creating the Income accounts, I went through the extra step of creating some specific accounts for each source of payment, like \u201cCaroline\u2019s Credit Card\u201d, etc. From this view, we can see that we contributed a total of 4254.28 USD (and were left with 65 MXN in hand) for this trip. The expenses side should match, considering the currency exchanges: 3694.40 + 7474 / 13.465 ~= 4249 USD which is approximately right (the small difference can be explained by the varying currency conversions). If you want to view the list of contribution payments and the final balance, click on a particular traveler\u2019s root account, e.g., \u201cIncome:Caroline\u201d (click on \u201cCaroline\u201d) which should take you to the Journal for that root account: This journal includes all the transactions in its sub-accounts. The final value at the bottom should show the total balance of those accounts, and thus, the amount of money Caroline contributed to this trip: 415 USD, and kept 5 MXN (in coin). We can do the same for Martin and find the final balance of 3839.28 USD and kept 60 MXN (in bills). You can also get the same amounts by using bean-query to achieve the same thing: ~/p/.../examples/sharing$ bean-query cozumel2015.beancount Input file: \"Mexico Trip: Cozumel & Tulum SCUBA Diving\" Ready with 105 directives (160 postings in 45 transactions). beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' sum_positio ----------- -415.00 USD 5 MXN Splitting Expenses \uf0c1 The expenses side of the Income Statement shows the breakdown of expenses. Note how some of the expense accounts are explicitly booked to each member separately by their account name, e.g., \u201c Expenses:Scuba:Martin \u201d. The other accounts, e.g. \u201c Expenses:Groceries \u201d are intended to be split. How we\u2019re going to carry this out is by adding a plugin that will transform all the transactions to actually split the postings which are intended to be shared, that is, postings without any member\u2019s name as a component. For example, the following input transaction: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation 1201.90 USD Will be transformed by the plugin into one like this: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation:Martin 600.95 USD Expenses:Accommodation:Caroline 600.95 USD Note that: Only Expenses accounts get split into multiple postings. Accounts with the name of a member are assumed by convention to be already attributed to that person. In order to achieve this, the plugin has to be provided with the names of the members. All the resulting Income and Expenses accounts include the name of a member. The plugin is enabled like this: plugin \"beancount.plugins.split_expenses\" \"Martin Caroline\" Reloading the web page after uncommenting this line from the input file and visiting the Income Statement page should show a long list of Expenses accounts split by person. Now, in order to generate the total amount of expenses incurred for each person, we have to produce the balance of all Expenses accounts with a member\u2019s name in it, accounts like \u201c Expenses:.*Martin \u201d. The web tool does not provide such a filtering capability at the moment 1 , but we can use the bean-query tool to produce the total of expenses for each person: beancount> SELECT sum(position) WHERE account ~ '^Expenses:.*Martin' sum_positio ----------- 2007.43 USD 3837.0 MXN beancount> SELECT sum(position) WHERE account '^Expenses:.*Caroline' sum_positio ----------- 1686.97 USD 3637.0 MXN This says \u201cMartin accrued expenses of 2007.43 USD and 3837.0 MXN.\u201d You can manually convert this to a dollar amount: yuzu:~$ dc -e '2007.43 3837.0 13.465 / +p' 2291.43 Or you can use the recently introduced \u201c CONVERT \u201d function of bean-query to do this: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Martin' convert_sum_position_c_ --------------------------------- 2288.528901098901098901098901 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Caroline' convert_sum_position_c_ --------------------------------- 1953.416886446886446886446886 USD (The difference between the 2291.43 and 2288.53 amounts can be attributed to the usage of slightly different exchange rates used in the converting transactions.) Similarly, you can generate the list of payments made by each person, e.g.: beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' Final Transfer \uf0c1 In order to figure out the total amount owed by each member, the process is similar: Simply sum up the balances of all the accounts attributed to that particular member: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ --------------------------------- 1538.783186813186813186813187 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ ---------------------------------- -1546.355494505494505494505494 USD Notice that this includes the Income and Expenses accounts for that person. It\u2019s as if we had two separate ledgers merged into one. (Once again, the small differences can be attributed to differences in exchange rate over time.) We can now make a final transfer amount in order to account for each of our expenses; we\u2019ve agreed to round this to 1500 USD: 2015-03-06 * \"Final transfer from Caroline to Martin to pay for difference\" Income:Caroline:Wallet -1500 USD Income:Martin:Wallet 1500 USD If you uncomment this transaction from the input file (near the end), you will find corrected balances: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ --------------------------------- -46.3554945054945054945054945 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ -------------------------------- 38.7831868131868131868131868 USD Updating your Personal Ledger \uf0c1 So great! Now we\u2019ve reconciled each other for the trip. But you still need to reflect these expenses in your personal ledger (if you have one). In this section, I will explain how you should reflect these transactions in that file. Account Names \uf0c1 First, let us take note that the accounts in your personal ledger do not have to match the account names used in the trip\u2019s ledger file. I never process those files together. (A good argument for not attempting this is that each trip will include account names from other people and I prefer not to have those leak into my main ledger file.) Using a Tag \uf0c1 I like to use a tag to report on the entire set of transactions related to the trip. In this example, I used the tag #trip-cozumel2015 . Booking Contributions \uf0c1 Your contributions into the project should be booked to a temporary holding account. I call mine \u201c Assets:Travel:Pending \u201d. For example, this transaction from the trip\u2019s file: 2015-02-25 * \"Yellow Transfers\" | \"SuperShuttle to Playa del Carmen\" Expenses:Transport:Bus 656 MXN Income:Martin:CreditCard -44.12 USD @ 14.86854 MXN will eventually be imported in my personal file and categorized like this: 2015-02-25 * \"YELLOW TRANSFER MX CO\" #trip-cozumel2015 Liabilities:US:BofA:CreditCard -44.12 USD Assets:Travel:Pending 44.12 USD You can conceptualize this as contributing money to a pending pool of cash which you will eventually receive expenses from. The same goes for cash that I brought for the trip: 2015-02-24 * \"Taking cash with me on the trip\" Assets:Cash -1200.00 USD Assets:Travel:Pending 1200.00 USD Note that absolutely all of the transactions related to the trip should be booked to that one account, including the inter-account transfers: 2015-03-06 * \"Final transfer from Mexico trip\" #trip-cozumel2015 Assets:Google:Wallet 1500 USD Assets:Travel:Pending -1500 USD Booking Expenses \uf0c1 After the trip is concluded, we want to convert the balance in the pending account to a list of Expenses. To find the list of expenses for yourself, you can issue a query like this: beancount> SELECT account, sum(position) WHERE account ~ '^Expenses:.*:Martin' GROUP BY 1 ORDER BY 1 account sum_positio ------------------------------- ----------- Expenses:Accommodation:Martin 735.45 USD Expenses:Alcohol:Martin 483 MXN Expenses:Bicycles:Martin 69.5 MXN Expenses:Flights:Martin 488.00 USD Expenses:Groceries:Martin 197.0 MXN Expenses:Museum:Martin 64 MXN Expenses:Restaurant:Martin 22.28 USD 1795.5 MXN Expenses:Scuba:Martin 506.14 USD Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Tips:Martin 225 MXN 189.16 USD Expenses:Transport:Bus:Martin 709 MXN Expenses:Transport:Taxi:Martin 53.90 USD 294 MXN Expenses:Transport:Train:Martin 5.00 USD I suppose you could script away this part to remove the member\u2019s name from the account names and have the script spit output already formatted as a nicely formatted Transaction, but because the account names will not match the personal ledger file\u2019s 1-to-1, you will have to perform a manual conversion anyway, so I did not bother automating this. Furthermore, you only have a single one of these to make after your trip, so it\u2019s not worth spending too much time making this part easier 2 . Here\u2019s what the final transaction looks like; I have a section in my input file with this: 2015-02-25 event \"location\" \"Cozumel, Mexico\" ;; (1) pushtag #trip-mexico-cozumel-2015 \u2026 other transactions related to the trip\u2026 2015-03-01 event \"location\" \"Tulum, Mexico\" ;; (1) 2015-03-07 * \"Final reconciliation - Booking pending expenses for trip\" Expenses:Travel:Accommodation 735.45 USD Expenses:Food:Alcohol 483.0 MXN Expenses:Sports:Velo 69.5 MXN Expenses:Transportation:Flights 488.00 USD Expenses:Food:Grocery 197.0 MXN Expenses:Fun:Museum 64.0 MXN Expenses:Food:Restaurant 22.28 USD Expenses:Food:Restaurant 1795.5 MXN Expenses:Scuba:Dives 506.14 USD Expenses:Scuba:Fees 7.50 USD Expenses:Scuba:Tips 225.0 MXN Expenses:Scuba:Tips 189.16 USD Expenses:Transportation:Bus 709.0 MXN Expenses:Transportation:Taxi 53.90 USD Expenses:Transportation:Taxi 294.0 MXN Expenses:Transportation:Train 5.00 USD Assets:Cash:Foreign 60.0 MXN ;; (2) Assets:Cash 330.00 USD ;; (3) Assets:Travel:Pending -2337.43 USD ;; (4) Assets:Travel:Pending -288.67 USD @@ 3897.0 MXN ;; (5) 2015-03-07 * \"Writing off difference as gift to Caroline\" ;; (6) Assets:Travel:Pending -43.18 USD Expenses:Gifts 2015-03-14 balance Assets:Travel:Pending 0 USD ;; (7) 2015-03-14 balance Assets:Travel:Pending 0 MXN poptag #trip-mexico-cozumel-2015 Observations: I used \u201cevent\u201d directives in order to track my location. I\u2019m doing this because I will eventually need it for immigration purposes (and just for fun, to track the days I spend in places). I moved the extra cash I kept to my \u201cforeign cash pocket\u201d account: Assets:Cash:Foreign I \u201cmoved\u201d back the cash I had with me after I returned from the trip. This leg removes the USD expenses from the Pending account. The leg removes the MXN expenses from the Pending account. I calculated its amount manually (3897 / 13.5 ~= 288.67 USD) and use the full amount as the exchange rate. I want to completely zero out the Pending account after the trip, and so I write off the excess amount we agreed not to pay (the difference between the imbalance and 1500.00 USD). Finally, I assert that the Pending account is empty of USD and of MXN. I found the missing amounts by running bean-check or using bean-doctor context on an incomplete transaction from Emacs. Generating Reports \uf0c1 If you want to automate the generation of reports for each of the participants in a trip, there is a script that generates text (and eventually CSV) reports for the queries mentioned in the previous sections. You can use this script to provide expenses status after or even during the trip or project, for each of the participants. The script lives in the split_expenses plugin itself, and you invoke it like this: python3 -m beancount.plugins.split_expenses --text= For each person, it will generate the following reports: A detailed journal of expenses A detailed journal of contributions A breakdown of expenses for each category (account type) Finally, it will also generate a final balance for each person, which you can use to send final reconciling transfers to each other. Try it now on one of the example files provided with Beancount. Other Examples \uf0c1 There is another example file that shows how to share expenses between three participants in duxbury2015.beancount . Look to more example files to be introduced over time. Conclusion \uf0c1 There is more than just one way to carry out the task we describe here. However, the method we present extends nicely to a larger group of participants, allows us to account for situations where particular expenses are incurred for one individual as part of a group, and finally, allows for a non-even split between the participants. This is pretty general. It\u2019s also a short step to accounting for an ongoing project with a longer term. The ideas presented in this document provide a nice use case for the usage of the double-entry method in a simple setting. I hope that working through this use case will help people develop the intuitions necessary in using the double-entry method. As our SQL-like query language matures, bean-web will eventually allow the user to create views from a set of transactions filtered from an expression. This will be implemented eventually. \u21a9 Note that we could implement a special \u201cbeancount\u201d supported format to the bean-query tool in order to write out balances in Transaction form. I\u2019m not sure how useful that would be but it\u2019s an idea worth considering for the future. \u21a9","title":"Sharing Expenses with Beancount"},{"location":"sharing_expenses_with_beancount.html#sharing-expenses-in-beancount","text":"Martin Blais , May 2015 http://furius.ca/beancount/doc/shared","title":"Sharing Expenses in Beancount"},{"location":"sharing_expenses_with_beancount.html#introduction","text":"This document presents a method for precisely and easily accounting for shared expenses in complicated group situations. For example, traveling with a group of friends where people pay for different things. We show it is possible to deal with expenses to be split evenly among the group as well as group expenses that should be allocated to specific persons. The method uses the double-entry accounting system. The essence of the method consists in separating the accounting of expenses and their associated payments. This makes it much easier to deal with shared costs, because using this method it doesn\u2019t matter who pays for what: we reconcile the precise amounts owed for each individual at the end and automatically compute final correcting transfers for each. This article is structured around a simple trip with expenses shared equally by two people. However, the same method generalizes to any type of project with incomes and expenses to be shared among multiple participants, and splitting expenses evenly or not.","title":"Introduction"},{"location":"sharing_expenses_with_beancount.html#a-travel-example","text":"Martin (the author) and Caroline (his girlfriend) visited Mexico in March 2015. We visited the island of Cozumel for a SCUBA diving trip for three days and then headed to Tulum for two days to relax and dive in the cenotes . Our assumptions are: Chaotic payments . We lead very busy lives\u2026 both of us are going to make payments ahead of time and during the trip, without any forethought regarding who will pay for what, though we will carefully record every payment. Each of us just pays for whatever preparation costs as needed to arrange this trip. For example, Caroline selected and booked flights early while I paid for the resort and booked the rental and activities at the local dive shop one week before departure. Use of shared and individual assets. Both of us are bringing cash, and are going to make payments during the trip from our respective wallets as well as from a shared pool of cash converted to local currency (Mexican pesos, for which I will use the \u201cMXN\u201d symbol), and we will use credit cards as necessary before and during the trip. Multiple currencies. Some of our expenses will be denominated in US dollars and some in Mexican pesos. For example, flights were paid in US dollars, local meals and the accommodation were paid in pesos, but the local dive shop charged us dollars. Converted amounts will come from both cash and credit cards sources. Individual expenses in a shared pool. While most of the expenses are to be shared equally, some of the expenses will apply to only one of us, and we want to account for those explicitly. For example, Caroline took a SCUBA certification course ( PADI Open Water ) and will pay for that on her own; similarly, she should not be paying for Martin\u2019s expensive boat diving costs. To complicate matters, the dive shop issued us a single joint bill for everything at the end of our stay.","title":"A Travel Example"},{"location":"sharing_expenses_with_beancount.html#a-note-about-sharing","text":"I feel that something should be said about the \u201csharing\u201d aspect of our expenses, as this topic has come up on previous discussions on the mailing-lists involving sharing examples. We are nitpicking on purpose. For the purpose of this exercise, we are accounting for every little penny spent in an incredibly detailed manner. The point of this document is specifically to show how a complex set of transactions well accounted for can be efficiently and simply disentangled to a precise accounting of expenses for each participant, regardless of who actually makes the payments. We are not cheapskates. We will assume that we\u2019ve decided to split expenses evenly. Our \u201cgenerosity\u201d to each other is not a topic relevant to this document. We\u2019re both well compensated working professionals and you can assume that we\u2019ve agreed to split the common costs for this trip evenly (50/50). One of the attributes of the method we show here is that the decision of how we choose to split expenses can be made separately from the actual payment of costs. For example, we may decide in the end that I pay for \u2154rd of the trip, but that can be decided precisely rather than in an ad-hoc \u201coh, I think I remember I paid for this\u201d manner. This is especially useful in a larger group of people because when expenses aren\u2019t tracked accurately, usually everyone is left with a feeling that they paid more than the others. The sum of the perceived fractions of expenses paid in a group is always greater than 100%...","title":"A Note About Sharing"},{"location":"sharing_expenses_with_beancount.html#overview-of-the-method","text":"In this section we present a brief illustrated overview of the method. A set of common Assets accounts that belong to the project, and book all our individual expenses and transfer for the trip as coming from external Income accounts: During the trip, we use the common Assets to make expenses. Most of the expenses are attributed to both of us (and to be shared eventually), but some of the expenses are intended to be attributed to each of us individually: After the trip, remaining Assets (like cash we walked home with) gets distributed back to ourselves to zero out the balances of the Assets accounts and we record this through contra postings to Income accounts: Finally, the list of shared Expenses are split between each other\u2014using a plugin that forks every posting that is intended to be a shared expense\u2014and the final amount is used to make a final transfer between each other so that we\u2019ve each paid for our respective expenses and we\u2019re square: Note that the final balance of expenses for each participant may differ, and these are due to particular expenses that were attributed separately, or if we decide to split the total unevenly.","title":"Overview of the Method"},{"location":"sharing_expenses_with_beancount.html#how-to-track-expenses","text":"In order to write down all expenses for this trip we will open a brand new Beancount input file. Despite the fact that the expenses will come from each person\u2019s personal accounts, it is useful to think of the trip as a special project, as if it were a separate entity, e.g., a company that exists only for the duration of the trip. The example file we wrote for our trip can be found here and should help you follow along this text.","title":"How to Track Expenses"},{"location":"sharing_expenses_with_beancount.html#accounts","text":"The set of accounts in the input file should not have to match your personal Beancount file\u2019s account names. The accounts we will use include accounts that correspond to Martin and Caroline\u2019s personal accounts but with generic names (e.g., Income:Martin:CreditCard instead of Liabilities:US:Chase ), and expense accounts may not match any regular expenses in my personal Beancount file\u2014it\u2019s not important. As a convention, any account that pertains specifically to one of the travelers will include that person\u2019s name in the account name. For example, Caroline\u2019s credit card will be named \u201c Income:Caroline:CreditCard \u201d. This is important, as we will use this later on to split contributions and expenses. Let\u2019s examine the different types of accounts we will need to carry this out.","title":"Accounts"},{"location":"sharing_expenses_with_beancount.html#external-income-accounts","text":"The \u201cproject\u201d will receive income in the form of transfers from personal accounts of either traveler. These are accounts we will consider external to the project and so will define them as Income accounts: ;; External accounts for Martin. 2015-02-01 open Income:Martin:Cash 2015-02-01 open Income:Martin:Cash:Foreign 2015-02-01 open Income:Martin:Wallet 2015-02-01 open Income:Martin:CreditCard ;; External accounts for Caroline. 2015-02-01 open Income:Caroline:Cash 2015-02-01 open Income:Caroline:Wallet 2015-02-01 open Income:Caroline:MetroCard 2015-02-01 open Income:Caroline:CreditCard Transactions carried out from these accounts must be copied from your personal Beancount file. Obviously, you must be careful to include all the transactions pertaining to the trip. I used a tag to do this in my personal file.","title":"External Income Accounts"},{"location":"sharing_expenses_with_beancount.html#assets-liabilities-accounts","text":"There will be a few Asset accounts that will be active and exist for the duration of the trip. These temporary accounts will be zero\u2019ed out at the end of it. One example is a pool of petty cash in local currency: 2015-02-01 open Assets:Cash:Pesos description: \"A shared account to contain our pocket of pesos\" We also carried cash in each of our pockets while traveling, so I created two separate accounts for that: 2015-02-01 open Assets:Cash:Martin description: \"Cash for the trip held by Martin\" 2015-02-01 open Assets:Cash:Caroline description: \"Cash for the trip held by Caroline\" Note however that despite their individual names, those accounts are considered as part of the project. It was just convenient to separately track balances for the cash we each held during the trip.","title":"Assets & Liabilities Accounts"},{"location":"sharing_expenses_with_beancount.html#expenses-accounts","text":"We will define various accounts to book our Expenses to. For example, \u201c Expenses:Flights \u201d will contain our costs associated with flight travel. For convenience, and because there are many types of expenses in this file, we chose to leverage the \u201cauto-accounts\u201d plugin and let Beancount automatically open these accounts: plugin \"beancount.ops.auto_accounts\" The great majority of these accounts are for shared expenses to be split between us. For example, shared SCUBA diving expenses will be booked to \u201c Expenses:Scuba \u201d. However, for expenses that are intended to be covered by one of us only, we simply include the name of the traveler in the account name. For example, Martin\u2019s extra costs for boat diving will be booked to the \u201c Expenses:Scuba:Martin \u201d account.","title":"Expenses Accounts"},{"location":"sharing_expenses_with_beancount.html#example-transactions","text":"Let\u2019s turn our attention to the different types of transactions present in the file. In this section I will walk you through some representative transactions (the names I give to these are arbitrary).","title":"Example Transactions"},{"location":"sharing_expenses_with_beancount.html#contributing-transactions","text":"Contributions to the project\u2019s costs are usually done in the form of expenses paid from external accounts. For example, Caroline paying for flights with her credit card looks like this: 2015-02-01 * \"Flights to Cancun\" Income:Caroline:CreditCard -976.00 USD Expenses:Flights Martin paying for the taxi to the airport looks like this: 2015-02-25 * \"Taxi to airport\" ^433f66ea0e4e Expenses:Transport:Taxi 62.80 USD Income:Martin:CreditCard","title":"Contributing Transactions"},{"location":"sharing_expenses_with_beancount.html#bringing-cash","text":"We both brought some cash on us, as we were warned it might be difficult to use credit cards in Mexico. In my personal Beancount file, the cash account is \u201c Assets:Cash \u201d but here it must get booked as an external contribution with my name on it: ;; Initial cash on us. 2015-02-24 * \"Getting cash for travel\" Income:Martin:Cash -1200 USD Assets:Cash:Martin 1200 USD Caroline\u2019s cash is similar: 2015-02-24 * \"Getting cash for travel\" Income:Caroline:Cash -300 USD Assets:Cash:Caroline 300 USD Once again, note that the Assets:Cash:Martin and Assets:Cash:Caroline accounts are considered a part of the project, in this case it just refers to who is carrying it. (These accounts are cleared at the end, so it does not matter that our names are in them.)","title":"Bringing Cash"},{"location":"sharing_expenses_with_beancount.html#transfers","text":"Before the trip, I was really busy. It looked like Caroline was going to make most of arrangements and upfront payments, so I made a transfer to her Google Wallet to help her cover for some expenses ahead of time: 2015-02-01 * \"Transfer Martin -> Caroline on Google Wallet\" Income:Martin:Wallet -1000 USD Income:Caroline:Wallet 1000 USD","title":"Transfers"},{"location":"sharing_expenses_with_beancount.html#cash-conversions","text":"Obtaining local currency was done by changing a small amount of cash at the airport (at a very bad rate): 2015-02-25 * \"Exchanged cash at XIC at CUN airport\" Assets:Cash:Caroline -100.00 USD @ 12.00 MXN Assets:Cash:Pesos 1200 MXN The \u201c Assets:Cash:Pesos \u201d account tracks our common pool of local currency that we use for various small expenses.","title":"Cash Conversions"},{"location":"sharing_expenses_with_beancount.html#cash-expenses-in-us-dollars","text":"Some local expenses will call for US money, which in this example I paid from my cash pocket: 2015-03-01 * \"Motmot Diving\" | \"Deposit for cenote diving\" Expenses:Scuba 50.00 USD Assets:Cash:Martin","title":"Cash Expenses in US Dollars"},{"location":"sharing_expenses_with_beancount.html#cash-expenses-in-local-currency","text":"Paying cash using pesos from our shared stash of pesos looks like this: 2015-02-25 * \"UltraMar Ferry across to Cozumel\" Expenses:Transport:Bus 326 MXN Assets:Cash:Pesos Sometimes we even had to pay with a mix of US dollars and pesos. In this example, we ran out of pesos, so we have to give them dollars and pesos (all the restaurants and hotels in the beach part of Tulum accept US currency): 2015-03-01 * \"Hartwood\" | \"Dinner - ran out of pesos\" Expenses:Restaurant 1880 MXN Assets:Cash:Pesos -1400 MXN Assets:Cash:Martin -40.00 USD @ 12.00 MXN I used the unfavorable rate the restaurant was offering to accept dollars at (the market rate was 14.5 at the time).","title":"Cash Expenses in Local Currency"},{"location":"sharing_expenses_with_beancount.html#individual-expenses","text":"Here is an example of booking individual expenses using shared money. In order for us to have access to the reef for diving, we had to pay a \u201cmarine park\u201d fee of $2.50 per day to the island. This was a short trip where I dove only three days there and Caroline\u2019s fee was included in her course except for one day: 2015-02-25 * \"Marine Park (3 days Martin, 1 day Caroline)\" Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Scuba:ParkFees:Caroline 2.50 USD Assets:Cash:Martin All that needs to be done is to book these to expense accounts with our names in them, which will get separated out at the end. Here is a more complicated example: the dive shop at Scuba Club Cozumel charged us a single bill at the end of our stay for all our rental gear and extra dives. All I did here was translate the itemized bill into a single transaction, booking to each other: 2015-03-01 * \"Scuba Club Cozumel\" | \"Dive shop bill\" ^69b409189b37 Income:Martin:CreditCard -381.64 USD Expenses:Scuba:Martin 27 USD ;; Regulator w/ Gauge Expenses:Scuba:Caroline 9 USD ;; Regulator w/ Gauge Expenses:Scuba:Martin 27 USD ;; BCD Expenses:Scuba:Caroline 9 USD ;; BCD Expenses:Scuba:Martin 6 USD ;; Fins Expenses:Scuba:Martin 24 USD ;; Wetsuit Expenses:Scuba:Caroline 8 USD ;; Wetsuit Expenses:Scuba:Caroline 9 USD ;; Dive computer Expenses:Scuba:Martin 5 USD ;; U/W Light Expenses:Scuba:Caroline 70 USD ;; Dive trip (2 tank) Expenses:Scuba:Martin 45 USD ;; Wreck Dive w/ Lite Expenses:Scuba:Martin 45 USD ;; Afternoon dive Expenses:Scuba:Caroline 45 USD ;; Afternoon dive Expenses:Scuba:Martin 28.64 USD ;; Taxes Expenses:Scuba:Caroline 24.00 USD ;; Taxes","title":"Individual Expenses"},{"location":"sharing_expenses_with_beancount.html#final-balances","text":"Of course you can use balance assertions at any time during the trip. For example, just before leaving at the Cancun airport we knew we wouldn\u2019t spend any more Mexican pesos for a while, so I counted what we had left after Caroline decided to splurge the remainder of them into overpriced chocolate sold at the airport shop: 2015-03-04 balance Assets:Cash:Pesos 65 MXN Ideally the bookkeeper should want to do this at a quiet moment at the end of every day or couple of days, which makes it easier to triangulate an expense you might have forgotten to enter (we are on vacation after all, in our relaxed state of mind we forget to write down stuff here and there).","title":"Final Balances"},{"location":"sharing_expenses_with_beancount.html#clearing-asset-accounts","text":"At the end of the trip, you should clear the final balances of all Assets and Liabilities accounts by transferring the remaining funds out to the participants, i.e., to the Income accounts. This should leave all the balances of the trip at zero and ensures all the cash put forward for trip expenses has been moved out to travelers. By the way, it doesn\u2019t matter much who keeps this money, because at the end we will end up making a single correcting transfer that should account for the balance required to create an even split. You can transfer it to anyone; the end result will be the same. Our clearing transaction looked like this: 2015-03-06 * \"Final transfer to clear internal balances to external ones\" Assets:Cash:Pesos -65 MXN Income:Martin:Cash:Foreign 60 MXN Income:Caroline:Cash 5 MXN Assets:Cash:Martin -330 USD Income:Martin:Cash 330 USD Assets:Cash:Caroline -140 USD Income:Caroline:Cash 140 USD 2015-03-07 balance Assets:Cash:Pesos 0 MXN 2015-03-07 balance Assets:Cash:Pesos 0 USD 2015-03-07 balance Assets:Cash:Martin 0 USD 2015-03-07 balance Assets:Cash:Caroline 0 USD We had three 20 peso bills, and I kept the bills for future travels. Caroline kept the 5 peso coin (forgot to hand it over as a tip). We transferred out the respective cash amounts we had been carrying together during the trip.","title":"Clearing Asset Accounts"},{"location":"sharing_expenses_with_beancount.html#how-to-take-notes","text":"During this trip I did not carry a laptop\u2014this was vacation after all. I like to disconnect. I did not carry a notebook either. Instead, I just took notes at the end of the day on a few sheets of paper at the hotel. This process took about 5-10 minutes each night, just recalling from memory and writing things down. These notes look like this: I made a paper spreadsheet where each line had The narration for the transaction (a description that would allow me to select an Expenses account later on) Who paid (which Assets or Income account the money came from) The amount (either in USD or MXN) After our trip, I sat down at the computer and typed the corresponding Beancount file . If I had a computer during my vacation I probably would have typed it as we went along. Of course, I had to do a few adjustments here and there because of mistakes. The bottom line is: if you\u2019re organized well, the overhead of doing this is minimal.","title":"How to Take Notes"},{"location":"sharing_expenses_with_beancount.html#reconciling-expenses","text":"Running bean-web on the trip\u2019s file: bean-web beancount/examples/sharing/cozumel2015.beancount You can view the balances in the \u201cAll Transactions\u201d view (click on \u201cAll Transactions\u201d). The Balance Sheet should show empty balances for Assets accounts: The balances of the equity accounts should reflect the total amount of currency conversions made during the trip. You can verify this by calculating the amount-weight average rate like this: 7539.00 / 559.88 ~= 13.465 USD/MXN (which is about right).","title":"Reconciling Expenses"},{"location":"sharing_expenses_with_beancount.html#reviewing-contributions","text":"The Income Statement should show a summary of all expenses and contributions to the project: The Income account balances show the total amounts of contributions for each person. Note that in creating the Income accounts, I went through the extra step of creating some specific accounts for each source of payment, like \u201cCaroline\u2019s Credit Card\u201d, etc. From this view, we can see that we contributed a total of 4254.28 USD (and were left with 65 MXN in hand) for this trip. The expenses side should match, considering the currency exchanges: 3694.40 + 7474 / 13.465 ~= 4249 USD which is approximately right (the small difference can be explained by the varying currency conversions). If you want to view the list of contribution payments and the final balance, click on a particular traveler\u2019s root account, e.g., \u201cIncome:Caroline\u201d (click on \u201cCaroline\u201d) which should take you to the Journal for that root account: This journal includes all the transactions in its sub-accounts. The final value at the bottom should show the total balance of those accounts, and thus, the amount of money Caroline contributed to this trip: 415 USD, and kept 5 MXN (in coin). We can do the same for Martin and find the final balance of 3839.28 USD and kept 60 MXN (in bills). You can also get the same amounts by using bean-query to achieve the same thing: ~/p/.../examples/sharing$ bean-query cozumel2015.beancount Input file: \"Mexico Trip: Cozumel & Tulum SCUBA Diving\" Ready with 105 directives (160 postings in 45 transactions). beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline' sum_positio ----------- -415.00 USD 5 MXN","title":"Reviewing Contributions"},{"location":"sharing_expenses_with_beancount.html#splitting-expenses","text":"The expenses side of the Income Statement shows the breakdown of expenses. Note how some of the expense accounts are explicitly booked to each member separately by their account name, e.g., \u201c Expenses:Scuba:Martin \u201d. The other accounts, e.g. \u201c Expenses:Groceries \u201d are intended to be split. How we\u2019re going to carry this out is by adding a plugin that will transform all the transactions to actually split the postings which are intended to be shared, that is, postings without any member\u2019s name as a component. For example, the following input transaction: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation 1201.90 USD Will be transformed by the plugin into one like this: 2015-02-28 * \"Scuba Club Cozumel\" | \"Room bill\" Income:Martin:CreditCard -1380.40 USD Expenses:Scuba:Martin 178.50 USD Expenses:Accommodation:Martin 600.95 USD Expenses:Accommodation:Caroline 600.95 USD Note that: Only Expenses accounts get split into multiple postings. Accounts with the name of a member are assumed by convention to be already attributed to that person. In order to achieve this, the plugin has to be provided with the names of the members. All the resulting Income and Expenses accounts include the name of a member. The plugin is enabled like this: plugin \"beancount.plugins.split_expenses\" \"Martin Caroline\" Reloading the web page after uncommenting this line from the input file and visiting the Income Statement page should show a long list of Expenses accounts split by person. Now, in order to generate the total amount of expenses incurred for each person, we have to produce the balance of all Expenses accounts with a member\u2019s name in it, accounts like \u201c Expenses:.*Martin \u201d. The web tool does not provide such a filtering capability at the moment 1 , but we can use the bean-query tool to produce the total of expenses for each person: beancount> SELECT sum(position) WHERE account ~ '^Expenses:.*Martin' sum_positio ----------- 2007.43 USD 3837.0 MXN beancount> SELECT sum(position) WHERE account '^Expenses:.*Caroline' sum_positio ----------- 1686.97 USD 3637.0 MXN This says \u201cMartin accrued expenses of 2007.43 USD and 3837.0 MXN.\u201d You can manually convert this to a dollar amount: yuzu:~$ dc -e '2007.43 3837.0 13.465 / +p' 2291.43 Or you can use the recently introduced \u201c CONVERT \u201d function of bean-query to do this: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Martin' convert_sum_position_c_ --------------------------------- 2288.528901098901098901098901 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ '^Expenses:.*Caroline' convert_sum_position_c_ --------------------------------- 1953.416886446886446886446886 USD (The difference between the 2291.43 and 2288.53 amounts can be attributed to the usage of slightly different exchange rates used in the converting transactions.) Similarly, you can generate the list of payments made by each person, e.g.: beancount> SELECT sum(position) WHERE account ~ '^Income:.*Caroline'","title":"Splitting Expenses"},{"location":"sharing_expenses_with_beancount.html#final-transfer","text":"In order to figure out the total amount owed by each member, the process is similar: Simply sum up the balances of all the accounts attributed to that particular member: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ --------------------------------- 1538.783186813186813186813187 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ ---------------------------------- -1546.355494505494505494505494 USD Notice that this includes the Income and Expenses accounts for that person. It\u2019s as if we had two separate ledgers merged into one. (Once again, the small differences can be attributed to differences in exchange rate over time.) We can now make a final transfer amount in order to account for each of our expenses; we\u2019ve agreed to round this to 1500 USD: 2015-03-06 * \"Final transfer from Caroline to Martin to pay for difference\" Income:Caroline:Wallet -1500 USD Income:Martin:Wallet 1500 USD If you uncomment this transaction from the input file (near the end), you will find corrected balances: beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Martin' convert_sum_position_c_ --------------------------------- -46.3554945054945054945054945 USD beancount> SELECT convert(sum(position), 'USD') WHERE account ~ 'Caroline' convert_sum_position_c_ -------------------------------- 38.7831868131868131868131868 USD","title":"Final Transfer"},{"location":"sharing_expenses_with_beancount.html#updating-your-personal-ledger","text":"So great! Now we\u2019ve reconciled each other for the trip. But you still need to reflect these expenses in your personal ledger (if you have one). In this section, I will explain how you should reflect these transactions in that file.","title":"Updating your Personal Ledger"},{"location":"sharing_expenses_with_beancount.html#account-names","text":"First, let us take note that the accounts in your personal ledger do not have to match the account names used in the trip\u2019s ledger file. I never process those files together. (A good argument for not attempting this is that each trip will include account names from other people and I prefer not to have those leak into my main ledger file.)","title":"Account Names"},{"location":"sharing_expenses_with_beancount.html#using-a-tag","text":"I like to use a tag to report on the entire set of transactions related to the trip. In this example, I used the tag #trip-cozumel2015 .","title":"Using a Tag"},{"location":"sharing_expenses_with_beancount.html#booking-contributions","text":"Your contributions into the project should be booked to a temporary holding account. I call mine \u201c Assets:Travel:Pending \u201d. For example, this transaction from the trip\u2019s file: 2015-02-25 * \"Yellow Transfers\" | \"SuperShuttle to Playa del Carmen\" Expenses:Transport:Bus 656 MXN Income:Martin:CreditCard -44.12 USD @ 14.86854 MXN will eventually be imported in my personal file and categorized like this: 2015-02-25 * \"YELLOW TRANSFER MX CO\" #trip-cozumel2015 Liabilities:US:BofA:CreditCard -44.12 USD Assets:Travel:Pending 44.12 USD You can conceptualize this as contributing money to a pending pool of cash which you will eventually receive expenses from. The same goes for cash that I brought for the trip: 2015-02-24 * \"Taking cash with me on the trip\" Assets:Cash -1200.00 USD Assets:Travel:Pending 1200.00 USD Note that absolutely all of the transactions related to the trip should be booked to that one account, including the inter-account transfers: 2015-03-06 * \"Final transfer from Mexico trip\" #trip-cozumel2015 Assets:Google:Wallet 1500 USD Assets:Travel:Pending -1500 USD","title":"Booking Contributions"},{"location":"sharing_expenses_with_beancount.html#booking-expenses","text":"After the trip is concluded, we want to convert the balance in the pending account to a list of Expenses. To find the list of expenses for yourself, you can issue a query like this: beancount> SELECT account, sum(position) WHERE account ~ '^Expenses:.*:Martin' GROUP BY 1 ORDER BY 1 account sum_positio ------------------------------- ----------- Expenses:Accommodation:Martin 735.45 USD Expenses:Alcohol:Martin 483 MXN Expenses:Bicycles:Martin 69.5 MXN Expenses:Flights:Martin 488.00 USD Expenses:Groceries:Martin 197.0 MXN Expenses:Museum:Martin 64 MXN Expenses:Restaurant:Martin 22.28 USD 1795.5 MXN Expenses:Scuba:Martin 506.14 USD Expenses:Scuba:ParkFees:Martin 7.50 USD Expenses:Tips:Martin 225 MXN 189.16 USD Expenses:Transport:Bus:Martin 709 MXN Expenses:Transport:Taxi:Martin 53.90 USD 294 MXN Expenses:Transport:Train:Martin 5.00 USD I suppose you could script away this part to remove the member\u2019s name from the account names and have the script spit output already formatted as a nicely formatted Transaction, but because the account names will not match the personal ledger file\u2019s 1-to-1, you will have to perform a manual conversion anyway, so I did not bother automating this. Furthermore, you only have a single one of these to make after your trip, so it\u2019s not worth spending too much time making this part easier 2 . Here\u2019s what the final transaction looks like; I have a section in my input file with this: 2015-02-25 event \"location\" \"Cozumel, Mexico\" ;; (1) pushtag #trip-mexico-cozumel-2015 \u2026 other transactions related to the trip\u2026 2015-03-01 event \"location\" \"Tulum, Mexico\" ;; (1) 2015-03-07 * \"Final reconciliation - Booking pending expenses for trip\" Expenses:Travel:Accommodation 735.45 USD Expenses:Food:Alcohol 483.0 MXN Expenses:Sports:Velo 69.5 MXN Expenses:Transportation:Flights 488.00 USD Expenses:Food:Grocery 197.0 MXN Expenses:Fun:Museum 64.0 MXN Expenses:Food:Restaurant 22.28 USD Expenses:Food:Restaurant 1795.5 MXN Expenses:Scuba:Dives 506.14 USD Expenses:Scuba:Fees 7.50 USD Expenses:Scuba:Tips 225.0 MXN Expenses:Scuba:Tips 189.16 USD Expenses:Transportation:Bus 709.0 MXN Expenses:Transportation:Taxi 53.90 USD Expenses:Transportation:Taxi 294.0 MXN Expenses:Transportation:Train 5.00 USD Assets:Cash:Foreign 60.0 MXN ;; (2) Assets:Cash 330.00 USD ;; (3) Assets:Travel:Pending -2337.43 USD ;; (4) Assets:Travel:Pending -288.67 USD @@ 3897.0 MXN ;; (5) 2015-03-07 * \"Writing off difference as gift to Caroline\" ;; (6) Assets:Travel:Pending -43.18 USD Expenses:Gifts 2015-03-14 balance Assets:Travel:Pending 0 USD ;; (7) 2015-03-14 balance Assets:Travel:Pending 0 MXN poptag #trip-mexico-cozumel-2015 Observations: I used \u201cevent\u201d directives in order to track my location. I\u2019m doing this because I will eventually need it for immigration purposes (and just for fun, to track the days I spend in places). I moved the extra cash I kept to my \u201cforeign cash pocket\u201d account: Assets:Cash:Foreign I \u201cmoved\u201d back the cash I had with me after I returned from the trip. This leg removes the USD expenses from the Pending account. The leg removes the MXN expenses from the Pending account. I calculated its amount manually (3897 / 13.5 ~= 288.67 USD) and use the full amount as the exchange rate. I want to completely zero out the Pending account after the trip, and so I write off the excess amount we agreed not to pay (the difference between the imbalance and 1500.00 USD). Finally, I assert that the Pending account is empty of USD and of MXN. I found the missing amounts by running bean-check or using bean-doctor context on an incomplete transaction from Emacs.","title":"Booking Expenses"},{"location":"sharing_expenses_with_beancount.html#generating-reports","text":"If you want to automate the generation of reports for each of the participants in a trip, there is a script that generates text (and eventually CSV) reports for the queries mentioned in the previous sections. You can use this script to provide expenses status after or even during the trip or project, for each of the participants. The script lives in the split_expenses plugin itself, and you invoke it like this: python3 -m beancount.plugins.split_expenses --text= For each person, it will generate the following reports: A detailed journal of expenses A detailed journal of contributions A breakdown of expenses for each category (account type) Finally, it will also generate a final balance for each person, which you can use to send final reconciling transfers to each other. Try it now on one of the example files provided with Beancount.","title":"Generating Reports"},{"location":"sharing_expenses_with_beancount.html#other-examples","text":"There is another example file that shows how to share expenses between three participants in duxbury2015.beancount . Look to more example files to be introduced over time.","title":"Other Examples"},{"location":"sharing_expenses_with_beancount.html#conclusion","text":"There is more than just one way to carry out the task we describe here. However, the method we present extends nicely to a larger group of participants, allows us to account for situations where particular expenses are incurred for one individual as part of a group, and finally, allows for a non-even split between the participants. This is pretty general. It\u2019s also a short step to accounting for an ongoing project with a longer term. The ideas presented in this document provide a nice use case for the usage of the double-entry method in a simple setting. I hope that working through this use case will help people develop the intuitions necessary in using the double-entry method. As our SQL-like query language matures, bean-web will eventually allow the user to create views from a set of transactions filtered from an expression. This will be implemented eventually. \u21a9 Note that we could implement a special \u201cbeancount\u201d supported format to the bean-query tool in order to write out balances in Transaction form. I\u2019m not sure how useful that would be but it\u2019s an idea worth considering for the future. \u21a9","title":"Conclusion"},{"location":"stock_vesting_in_beancount.html","text":"Stock Vesting in Beancount \uf0c1 Martin Blais , June 2015 http://furius.ca/beancount/doc/vesting Introduction \uf0c1 This document explains the vesting of restricted stock units in Beancount, by way of an example. This example may not exactly match your situation, but enough detail is provided that you should be able to adapt it for your own particular differences. A working example file can be found here to follow along with this text. Restricted Stock Compensation \uf0c1 Many technology companies offer their employees incentive compensation in the form of \u201cgrants\u201d (or \u201cawards\u201d) of \u201crestricted stock units\u201d (RSU), which is essentially a promise for the \u201crelease\u201d to you of actual shares in the future. The stock is \u201crestricted\u201d in the sense that you cannot access it\u2014you only receive it when it \u201cvests\u201d, and this happens based on a schedule. Typically, you are promised a fixed number of shares that vest every quarter or every month over a period of 3 or 4 years. If you leave the company, your remaining unvested shares are lost. One way you can view these RSUs is as an asset, a receivable that arrives regularly over time. These RSUs are essentially compensation denominated in the currency of the company\u2019s shares itself. We want to track the unraveling of these unvested units, and correctly account for their conversion to real stock with a cost basis and including whatever taxes were paid upon vesting. Tracking Awards \uf0c1 Commodities \uf0c1 First we want to define some commodities. In this example, I work for \u201cHooli Inc.\u201d and will eventually receive shares of that company (valued in US dollars): 1990-12-02 commodity HOOL name: \"Common shares of Hooli Inc.\" quote: USD We will also want to track the amount of unvested shares: 2013-01-28 commodity HOOL.UNVEST name: \"Unvested shares of Hooli from awards.\" Accounts for Awards \uf0c1 Grants received is income. I use \u201cIncome:US:Hooli\u201d as the root for all income accounts from Hooli, but in particular, I define an account for the awards, which contains units of unvested stock: 2013-01-28 open Income:US:Hooli:Awards HOOL.UNVEST When the stock vests, we will need to book the other side of this income somewhere, so we define an expenses account to count how much stock has been vested over a period of time: 2014-01-28 open Expenses:Hooli:Vested HOOL.UNVEST Receiving Awards \uf0c1 When you receive a new award (this may occur every year, for example, some people call this a \u201cstock refresh\u201d), you receive it as income and deposit it into a fresh new account, used to track this particular award: 2014-04-02 * \"Award S0012345\" Income:US:Hooli:Awards -1680 HOOL.UNVEST Assets:US:Hooli:Unvested:S0012345 1680 HOOL.UNVEST 2014-04-02 open Assets:US:Hooli:Unvested:S0012345 You may have multiple active awards at the same time. It\u2019s nice to have a separate account per award, as it offers a natural way to list their contents and when the award expires, you can close the account\u2014the list of open award accounts gives you the list of outstanding & actively vesting awards. In this example I used the number of the award (#S0012345) as the sub-account name. It\u2019s useful to use the number as the statements typically include it. I like to keep all the awards in a small dedicated section. Vesting Events \uf0c1 Then I have a different section that contains all the transactions that follow a vesting event. Accounts \uf0c1 First, when we vest stock, it\u2019s a taxable income event. The cash value for the stock needs an Income account: 2013-04-04 open Income:US:Hooli:RSU Taxes paid should be on the annual expenses accounts you should have defined somewhere else to account for that year\u2019s taxes (this is covered elsewhere in the cookbook): 2015-01-01 open Expenses:Taxes:TY2015:US:StateNY 2015-01-01 open Expenses:Taxes:TY2015:US:Federal 2015-01-01 open Expenses:Taxes:TY2015:US:SocSec 2015-01-01 open Expenses:Taxes:TY2015:US:SDI 2015-01-01 open Expenses:Taxes:TY2015:US:Medicare 2015-01-01 open Expenses:Taxes:TY2015:US:CityNYC After paying taxes on the received income, the remaining cash is deposited in a limbo account before getting converted: 2013-01-28 open Assets:US:Hooli:RSURefund Also, in another section we should have an account for the brokerage which holds and manages the shares for you: 2013-04-04 open Assets:US:Schwab:HOOL Generally you don\u2019t have a choice for this broker because the company you work normally makes an arrangement with an external firm in order to administer the restricted stock program. Typical firms doing this type of administration are Morgan Stanley, Salomon Smith Barney, Schwab, even E*Trade. In the example we\u2019ll use a Schwab account. And we also need some sort of checking account to receive cash in lieu of fractional shares. I\u2019ll assume a Bank of America account in the example: 2001-01-01 open Assets:US:BofA:Checking Vesting \uf0c1 First, the vesting events themselves: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD Assets:US:Hooli:RSURefund 2458.97 USD This corresponds line-by-line to a payroll stub that I receive each vesting event for each award. Since all the RSU awards at Hooli vest on the same day of the month, this means that I have clusters of these, one for each award (in the example file I show two awards). Some observations are in order: The value of the Income posting consists of the number of shares vested times the FMV of the stock. In this case, that is 35 shares \u2a09 $131.37/share = $4597.95. I just write the dollar amount because it is provided to me on the pay stub. Receiving vested stock is a taxable income event, and Hooli automatically withholds taxes and pays them to the government on its employees\u2019 behalf. These are the Expenses:Taxes accounts corresponding to your W-2. Finally, I don\u2019t directly deposit the converted shares to the brokerage account; this is because the remainder of cash after paying tax expenses is not necessarily a round number of shares. Therefore, I used a limbo account ( Assets:US:Hooli:RSURefund ) and decouple the receipt from the conversion. This way, each transaction corresponds exactly to one pay stub. It makes it easier to enter the data. Also note that I used a unique link ( ^392f97dd62d0 ) to group all the transactions for a particular vesting event. You could also use tags if you prefer. Conversion to Actual Stock \uf0c1 Now we\u2019re ready to convert the remaining cash to stock units. This happens in the brokerage firm and you should see the new shares in your brokerage account. The brokerage will typically issue a \u201cstock release report\u201d statement for each vesting event for each award, with the details necessary to make the conversion, namely, the actual number of shares converted from cash and the cost basis (the FMV on the vesting day): 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST The first two postings deposit the shares and subtract the dollar value from the limbo account where the vesting transaction left the remaining cash. You should make sure to use the specific FMV price provided by the statement and not an approximate price, because this has tax consequences later on. Note that you cannot buy fractional shares, so the cost of the rounded amount of shares (18) will leave some remaining cash in the limbo account. The last two postings deduct from the balance of unvested shares and I \u201creceive\u201d an expenses; that expenses account basically counts how many shares were vested over a particular time period. Here again you will probably have one of these conversions for each stock grant you have. I enter them separately, so that one statement matches one transaction. This is a good rule to follow. Refund for Fractions \uf0c1 After all the conversion events have moved cash out of the limbo account, it is left the fractional remainders from all the conversions. In my case, this remainder is refunded by Hooli 3-4 weeks after vesting as a single separate pay stub that includes all the remainders (it even lists each of them separately). I enter this as a transaction as well: 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.31 USD Assets:US:Hooli:RSURefund -2.88 USD ; (For second award in example) Assets:US:BofA:Checking 97.19 USD After the fractions have been paid the limbo account should be empty. I verify this claim using a balance assertion: 2015-06-14 balance Assets:US:Hooli:RSURefund 0 USD This provides me with some sense that the numbers are right. Organizing your Input \uf0c1 I like to put all the vesting events together in my input file; this makes them much easier to update and reconcile, especially with multiple awards. For example, with two awards I would have multiple chunks of transactions like this, separated with 4-5 empty lines to delineate them: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Assets:US:Hooli:RSURefund 2458.97 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD 2015-05-27 * \"Vesting Event - C123456 - HOOL\" #award-C123456 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -1970.55 USD Assets:US:Hooli:RSURefund 1053.84 USD Expenses:Taxes:TY2015:US:Medicare 28.58 USD Expenses:Taxes:TY2015:US:Federal 492.63 USD Expenses:Taxes:TY2015:US:CityNYC 83.75 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 189.57 USD Expenses:Taxes:TY2015:US:SocSec 122.18 USD 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 9 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:C123456 -15 HOOL.UNVEST Expenses:Hooli:Vested 15 HOOL.UNVEST 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.30 USD Assets:US:Hooli:RSURefund -2.88 USD Assets:US:BofA:Checking 97.18 USD 2015-02-14 balance Assets:US:Hooli:RSURefund 0 USD Unvested Shares \uf0c1 Asserting Unvested Balances \uf0c1 Finally, you may occasionally want to assert the number of unvested shares. I like to do this semi-annually, for example. The brokerage company that handles the RSUs for Hooli should be able to list how many unvested shares of each award remain, so it\u2019s as simple as looking it up on a website: 2015-06-04 balance Assets:US:Hooli:Unvested:S0012345 1645 HOOL.UNVEST 2015-06-04 balance Assets:US:Hooli:Unvested:C123456 705 HOOL.UNVEST Pricing Unvested Shares \uf0c1 You can also put a price on the unvested shares in order to estimate the unvested dollar amount. You should use a fictional currency for this, because we want to avoid a situation where a balance sheet is produced that includes these unvested assets as regular dollars: 2015-06-02 price HOOL.UNVEST 132.4300 USD.UNVEST At the time of this writing, the bean-web interface does not convert the units if they are not held at cost, but using the SQL query interface or writing a custom script you should be able to produce those numbers: $ bean-query examples/vesting/vesting.beancount beancount> select account, sum(convert(position, 'USD.UNVEST')) as unvested where account ~ 'Unvested' group by account; account unvested --------------------------------- ---------------------- Assets:US:Hooli:Unvested:S0012345 217847.3500 USD.UNVEST Assets:US:Hooli:Unvested:C123456 93363.1500 USD.UNVEST Selling Vested Stock \uf0c1 After each vesting event, the stock is left in your brokerage account. Selling this stock proceeds just as in any other trading transaction (see Trading with Beancount for full details). For example, selling the shares from the example would look something like this: 2015-09-10 * \"Selling shares\" Assets:US:Schwab:HOOL -26 HOOL {131.3700 USD} @ 138.23 USD Assets:US:Schwab:Cash 3593.98 USD Income:US:Schwab:Gains Here you can see why it matters that the cost basis you used on the conversion event is the correct one: You will have to pay taxes on the difference (in Income:US:Schwab:Gains ). In this example the taxable difference is (138.23 - 131.37) dollars per share. I like to keep all the brokerage transactions in a separate section of my document, where other transactions related to the brokerage occur, such as fees, dividends and transfers. Conclusion \uf0c1 This is a simple example that is modeled after how technology companies deal with this type of compensation. It is by no means comprehensive, and some of the details will necessarily vary in your situation. In particular, it does not explain how to deal with options (ISOs). My hope is that there is enough meat in this document to allow you to extrapolate and adapt to your particular situation. If you get stuck, please reach out on the mailing-list .","title":"Stock Vesting in Beancount"},{"location":"stock_vesting_in_beancount.html#stock-vesting-in-beancount","text":"Martin Blais , June 2015 http://furius.ca/beancount/doc/vesting","title":"Stock Vesting in Beancount"},{"location":"stock_vesting_in_beancount.html#introduction","text":"This document explains the vesting of restricted stock units in Beancount, by way of an example. This example may not exactly match your situation, but enough detail is provided that you should be able to adapt it for your own particular differences. A working example file can be found here to follow along with this text.","title":"Introduction"},{"location":"stock_vesting_in_beancount.html#restricted-stock-compensation","text":"Many technology companies offer their employees incentive compensation in the form of \u201cgrants\u201d (or \u201cawards\u201d) of \u201crestricted stock units\u201d (RSU), which is essentially a promise for the \u201crelease\u201d to you of actual shares in the future. The stock is \u201crestricted\u201d in the sense that you cannot access it\u2014you only receive it when it \u201cvests\u201d, and this happens based on a schedule. Typically, you are promised a fixed number of shares that vest every quarter or every month over a period of 3 or 4 years. If you leave the company, your remaining unvested shares are lost. One way you can view these RSUs is as an asset, a receivable that arrives regularly over time. These RSUs are essentially compensation denominated in the currency of the company\u2019s shares itself. We want to track the unraveling of these unvested units, and correctly account for their conversion to real stock with a cost basis and including whatever taxes were paid upon vesting.","title":"Restricted Stock Compensation"},{"location":"stock_vesting_in_beancount.html#tracking-awards","text":"","title":"Tracking Awards"},{"location":"stock_vesting_in_beancount.html#commodities","text":"First we want to define some commodities. In this example, I work for \u201cHooli Inc.\u201d and will eventually receive shares of that company (valued in US dollars): 1990-12-02 commodity HOOL name: \"Common shares of Hooli Inc.\" quote: USD We will also want to track the amount of unvested shares: 2013-01-28 commodity HOOL.UNVEST name: \"Unvested shares of Hooli from awards.\"","title":"Commodities"},{"location":"stock_vesting_in_beancount.html#accounts-for-awards","text":"Grants received is income. I use \u201cIncome:US:Hooli\u201d as the root for all income accounts from Hooli, but in particular, I define an account for the awards, which contains units of unvested stock: 2013-01-28 open Income:US:Hooli:Awards HOOL.UNVEST When the stock vests, we will need to book the other side of this income somewhere, so we define an expenses account to count how much stock has been vested over a period of time: 2014-01-28 open Expenses:Hooli:Vested HOOL.UNVEST","title":"Accounts for Awards"},{"location":"stock_vesting_in_beancount.html#receiving-awards","text":"When you receive a new award (this may occur every year, for example, some people call this a \u201cstock refresh\u201d), you receive it as income and deposit it into a fresh new account, used to track this particular award: 2014-04-02 * \"Award S0012345\" Income:US:Hooli:Awards -1680 HOOL.UNVEST Assets:US:Hooli:Unvested:S0012345 1680 HOOL.UNVEST 2014-04-02 open Assets:US:Hooli:Unvested:S0012345 You may have multiple active awards at the same time. It\u2019s nice to have a separate account per award, as it offers a natural way to list their contents and when the award expires, you can close the account\u2014the list of open award accounts gives you the list of outstanding & actively vesting awards. In this example I used the number of the award (#S0012345) as the sub-account name. It\u2019s useful to use the number as the statements typically include it. I like to keep all the awards in a small dedicated section.","title":"Receiving Awards"},{"location":"stock_vesting_in_beancount.html#vesting-events","text":"Then I have a different section that contains all the transactions that follow a vesting event.","title":"Vesting Events"},{"location":"stock_vesting_in_beancount.html#accounts","text":"First, when we vest stock, it\u2019s a taxable income event. The cash value for the stock needs an Income account: 2013-04-04 open Income:US:Hooli:RSU Taxes paid should be on the annual expenses accounts you should have defined somewhere else to account for that year\u2019s taxes (this is covered elsewhere in the cookbook): 2015-01-01 open Expenses:Taxes:TY2015:US:StateNY 2015-01-01 open Expenses:Taxes:TY2015:US:Federal 2015-01-01 open Expenses:Taxes:TY2015:US:SocSec 2015-01-01 open Expenses:Taxes:TY2015:US:SDI 2015-01-01 open Expenses:Taxes:TY2015:US:Medicare 2015-01-01 open Expenses:Taxes:TY2015:US:CityNYC After paying taxes on the received income, the remaining cash is deposited in a limbo account before getting converted: 2013-01-28 open Assets:US:Hooli:RSURefund Also, in another section we should have an account for the brokerage which holds and manages the shares for you: 2013-04-04 open Assets:US:Schwab:HOOL Generally you don\u2019t have a choice for this broker because the company you work normally makes an arrangement with an external firm in order to administer the restricted stock program. Typical firms doing this type of administration are Morgan Stanley, Salomon Smith Barney, Schwab, even E*Trade. In the example we\u2019ll use a Schwab account. And we also need some sort of checking account to receive cash in lieu of fractional shares. I\u2019ll assume a Bank of America account in the example: 2001-01-01 open Assets:US:BofA:Checking","title":"Accounts"},{"location":"stock_vesting_in_beancount.html#vesting","text":"First, the vesting events themselves: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD Assets:US:Hooli:RSURefund 2458.97 USD This corresponds line-by-line to a payroll stub that I receive each vesting event for each award. Since all the RSU awards at Hooli vest on the same day of the month, this means that I have clusters of these, one for each award (in the example file I show two awards). Some observations are in order: The value of the Income posting consists of the number of shares vested times the FMV of the stock. In this case, that is 35 shares \u2a09 $131.37/share = $4597.95. I just write the dollar amount because it is provided to me on the pay stub. Receiving vested stock is a taxable income event, and Hooli automatically withholds taxes and pays them to the government on its employees\u2019 behalf. These are the Expenses:Taxes accounts corresponding to your W-2. Finally, I don\u2019t directly deposit the converted shares to the brokerage account; this is because the remainder of cash after paying tax expenses is not necessarily a round number of shares. Therefore, I used a limbo account ( Assets:US:Hooli:RSURefund ) and decouple the receipt from the conversion. This way, each transaction corresponds exactly to one pay stub. It makes it easier to enter the data. Also note that I used a unique link ( ^392f97dd62d0 ) to group all the transactions for a particular vesting event. You could also use tags if you prefer.","title":"Vesting"},{"location":"stock_vesting_in_beancount.html#conversion-to-actual-stock","text":"Now we\u2019re ready to convert the remaining cash to stock units. This happens in the brokerage firm and you should see the new shares in your brokerage account. The brokerage will typically issue a \u201cstock release report\u201d statement for each vesting event for each award, with the details necessary to make the conversion, namely, the actual number of shares converted from cash and the cost basis (the FMV on the vesting day): 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST The first two postings deposit the shares and subtract the dollar value from the limbo account where the vesting transaction left the remaining cash. You should make sure to use the specific FMV price provided by the statement and not an approximate price, because this has tax consequences later on. Note that you cannot buy fractional shares, so the cost of the rounded amount of shares (18) will leave some remaining cash in the limbo account. The last two postings deduct from the balance of unvested shares and I \u201creceive\u201d an expenses; that expenses account basically counts how many shares were vested over a particular time period. Here again you will probably have one of these conversions for each stock grant you have. I enter them separately, so that one statement matches one transaction. This is a good rule to follow.","title":"Conversion to Actual Stock"},{"location":"stock_vesting_in_beancount.html#refund-for-fractions","text":"After all the conversion events have moved cash out of the limbo account, it is left the fractional remainders from all the conversions. In my case, this remainder is refunded by Hooli 3-4 weeks after vesting as a single separate pay stub that includes all the remainders (it even lists each of them separately). I enter this as a transaction as well: 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.31 USD Assets:US:Hooli:RSURefund -2.88 USD ; (For second award in example) Assets:US:BofA:Checking 97.19 USD After the fractions have been paid the limbo account should be empty. I verify this claim using a balance assertion: 2015-06-14 balance Assets:US:Hooli:RSURefund 0 USD This provides me with some sense that the numbers are right.","title":"Refund for Fractions"},{"location":"stock_vesting_in_beancount.html#organizing-your-input","text":"I like to put all the vesting events together in my input file; this makes them much easier to update and reconcile, especially with multiple awards. For example, with two awards I would have multiple chunks of transactions like this, separated with 4-5 empty lines to delineate them: 2015-05-27 * \"Vesting Event - S0012345 - HOOL\" #award-S0012345 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -4597.95 USD Assets:US:Hooli:RSURefund 2458.97 USD Expenses:Taxes:TY2015:US:Medicare 66.68 USD Expenses:Taxes:TY2015:US:Federal 1149.48 USD Expenses:Taxes:TY2015:US:CityNYC 195.42 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 442.32 USD Expenses:Taxes:TY2015:US:SocSec 285.08 USD 2015-05-27 * \"Vesting Event - C123456 - HOOL\" #award-C123456 ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Income:US:Hooli:RSU -1970.55 USD Assets:US:Hooli:RSURefund 1053.84 USD Expenses:Taxes:TY2015:US:Medicare 28.58 USD Expenses:Taxes:TY2015:US:Federal 492.63 USD Expenses:Taxes:TY2015:US:CityNYC 83.75 USD Expenses:Taxes:TY2015:US:SDI 0.00 USD Expenses:Taxes:TY2015:US:StateNY 189.57 USD Expenses:Taxes:TY2015:US:SocSec 122.18 USD 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 18 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:S0012345 -35 HOOL.UNVEST Expenses:Hooli:Vested 35 HOOL.UNVEST 2015-05-25 * \"Conversion into shares\" ^392f97dd62d0 Assets:US:Schwab:HOOL 9 HOOL {131.3700 USD} Assets:US:Hooli:RSURefund Assets:US:Hooli:Unvested:C123456 -15 HOOL.UNVEST Expenses:Hooli:Vested 15 HOOL.UNVEST 2015-06-13 * \"HOOLI INC PAYROLL\" ^392f97dd62d0 doc: \"2015-02-13.hooli.38745783.pdf\" Assets:US:Hooli:RSURefund -94.30 USD Assets:US:Hooli:RSURefund -2.88 USD Assets:US:BofA:Checking 97.18 USD 2015-02-14 balance Assets:US:Hooli:RSURefund 0 USD","title":"Organizing your Input"},{"location":"stock_vesting_in_beancount.html#unvested-shares","text":"","title":"Unvested Shares"},{"location":"stock_vesting_in_beancount.html#asserting-unvested-balances","text":"Finally, you may occasionally want to assert the number of unvested shares. I like to do this semi-annually, for example. The brokerage company that handles the RSUs for Hooli should be able to list how many unvested shares of each award remain, so it\u2019s as simple as looking it up on a website: 2015-06-04 balance Assets:US:Hooli:Unvested:S0012345 1645 HOOL.UNVEST 2015-06-04 balance Assets:US:Hooli:Unvested:C123456 705 HOOL.UNVEST","title":"Asserting Unvested Balances"},{"location":"stock_vesting_in_beancount.html#pricing-unvested-shares","text":"You can also put a price on the unvested shares in order to estimate the unvested dollar amount. You should use a fictional currency for this, because we want to avoid a situation where a balance sheet is produced that includes these unvested assets as regular dollars: 2015-06-02 price HOOL.UNVEST 132.4300 USD.UNVEST At the time of this writing, the bean-web interface does not convert the units if they are not held at cost, but using the SQL query interface or writing a custom script you should be able to produce those numbers: $ bean-query examples/vesting/vesting.beancount beancount> select account, sum(convert(position, 'USD.UNVEST')) as unvested where account ~ 'Unvested' group by account; account unvested --------------------------------- ---------------------- Assets:US:Hooli:Unvested:S0012345 217847.3500 USD.UNVEST Assets:US:Hooli:Unvested:C123456 93363.1500 USD.UNVEST","title":"Pricing Unvested Shares"},{"location":"stock_vesting_in_beancount.html#selling-vested-stock","text":"After each vesting event, the stock is left in your brokerage account. Selling this stock proceeds just as in any other trading transaction (see Trading with Beancount for full details). For example, selling the shares from the example would look something like this: 2015-09-10 * \"Selling shares\" Assets:US:Schwab:HOOL -26 HOOL {131.3700 USD} @ 138.23 USD Assets:US:Schwab:Cash 3593.98 USD Income:US:Schwab:Gains Here you can see why it matters that the cost basis you used on the conversion event is the correct one: You will have to pay taxes on the difference (in Income:US:Schwab:Gains ). In this example the taxable difference is (138.23 - 131.37) dollars per share. I like to keep all the brokerage transactions in a separate section of my document, where other transactions related to the brokerage occur, such as fees, dividends and transfers.","title":"Selling Vested Stock"},{"location":"stock_vesting_in_beancount.html#conclusion","text":"This is a simple example that is modeled after how technology companies deal with this type of compensation. It is by no means comprehensive, and some of the details will necessarily vary in your situation. In particular, it does not explain how to deal with options (ISOs). My hope is that there is enough meat in this document to allow you to extrapolate and adapt to your particular situation. If you get stuck, please reach out on the mailing-list .","title":"Conclusion"},{"location":"the_double_entry_counting_method.html","text":"The Double-Entry Counting Method \uf0c1 Martin Blais, December 2016 http://furius.ca/beancount/doc/double-entry Introduction Basics of Double-Entry Bookkeeping Statements Single-Entry Bookkeeping Double-Entry Bookkeeping Many Accounts Multiple Postings Types of Accounts Trial Balance Income Statement Clearing Income Equity Balance Sheet Summarizing Period Reporting Chart of Accounts Country-Institution Convention Credits & Debits Accounting Equations Plain-Text Accounting The Table Perspective Introduction \uf0c1 This document is a gentle introduction to the double-entry counting method, as written from the perspective of a computer scientist. It is an attempt to explain basic bookkeeping using as simple an approach as possible, doing away with some of the idiosyncrasies normally involved in accounting. It is also representative of how Beancount works, and it should be useful to all users of plain-text accounting . Note that I am not an accountant, and in the process of writing this document I may have used terminology that is slightly different or unusual to that which is taught in perhaps more traditional training in accounting. I granted myself license to create something new and perhaps even unusual in order to explain those ideas as simply and clearly as possible to someone unfamiliar with them. I believe that the method of double-entry counting should be taught to everyone at the high school level everywhere as it is a tremendously useful organizational skill, and I hope that this text can help spread its knowledge beyond professional circles. Basics of Double-Entry Bookkeeping \uf0c1 The double-entry system is just a simple method of counting , with some simple rules. Let\u2019s begin by defining the notion of an account . An account is something that can contain things, like a bag. It is used to count things, to accumulate things. Let\u2019s draw a horizontal arrow to visually represent the evolving contents of an account over time: On the left, we have the past, and to the right, increasing time: the present, the future, etc. For now, let\u2019s assume that accounts can contain only one kind of thing, for example, dollars . All accounts begin with an empty content of zero dollars. We will call the number of units in the account the balance of an account. Note that it represents its contents at a particular point in time. I will draw the balance using a number above the account\u2019s timeline: The contents of accounts can change over time. In order to change the content of an account, we have to add something to it. We will call this addition a posting to an account, and I will draw this change as a circled number on the account\u2019s timeline, for example, adding $100 to the account: Now, we can draw the updated balance of the account after the posting with another little number right after it: The account\u2019s balance, after adding $100, is now $100. We can also remove from the contents of an account. For example, we could remove $25, and the resulting account balance is now $75: Account balances can also become negative , if we remove more dollars than there are in the account. For example, if we remove $200 from this account, the balance now becomes $-125: It\u2019s perfectly fine for accounts to contain a negative balance number. Remember that all we\u2019re doing is counting things. As we will see shortly, some accounts will remain with a negative balance for most of their timeline. Statements \uf0c1 Something worthy of notice is how the timeline notation I\u2019ve written in the previous section is analogous to paper account statements institutions maintain for each client and which you typically receive through the mail: Date Description Amount Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... -25.00 1075.00 2016-10-06 ... -200.00 875.00 Final Balance 875.00 Sometimes the amount column is split into two, one showing the positive amounts and the other the negative ones: Date Description Debit Credit Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... 25.00 1075.00 2016-10-06 ... 200.00 875.00 Final Balance 875.00 Here, \u201cdebit\u201d means \u201cremoved from your account\u201d and \u201ccredit\u201d means \u201cdeposited in your account.\u201d Sometimes the words \u201cwithdrawals\u201d and \u201cdeposits\u201d will be used. It all depends on context: for checking and savings accounts it is usual to have both types of postings, but for a credit card account typically it shows only positive numbers and then the occasional monthly payment so the single column format is used. In any case, the \u201cbalance\u201d column always shows the resulting balance after the amount has been posted to the account. And sometimes the statements are rendered in decreasing order of time. Single-Entry Bookkeeping \uf0c1 In this story, this account belongs to someone. We\u2019ll call this person the owner of the account. The account can be used to represent a real world account, for example, imagine that we use it to represent the content of the owner\u2019s checking account at a bank. So we\u2019re going to label the account by giving it a name, in this case \u201cChecking\u201d: Imagine that at some point, this account has a balance of $1000, like I\u2019ve drawn on the picture. Now, if the owner spends $79 of this account, we would represent it like this: Furthermore, if the expense was for a meal at a restaurant, we could flag the posting with a category to indicate what the change was used for. Let\u2019s say, \u201cRestaurant\u201d, like this: Now, if we have a lot of these, we could write a computer program to accumulate all the changes for each category and calculate the sums for each of them. That would tell us how much we spent in restaurants in total, for example. This is called the single-entry method of accounting. But we\u2019re not going to do it this way; we have a better way. Bear with me for a few more sections. Double-Entry Bookkeeping \uf0c1 An owner may have multiple accounts. I will represent this by drawing many similar account timelines on the same graphic. As before, these are labeled with unique names. Let\u2019s assume that the owner has the same \u201cChecking\u201d account as previously, but now also a \u201c Restaurant \u201d account as well, which can be used to accumulate all food expenses at restaurants. It looks like this: Now, instead of categorizing the posting to a \u201crestaurant category\u201d as we did previously, we could create a matching posting on the \u201cRestaurant\u201d account to record how much we spent for food, with the amount spent ($79): The \u201cRestaurant\u201d account, like all other accounts, also has an accumulated balance, so we can find out how much we spent in \u201cRestaurant\u201d in total. This is entirely symmetrical to counting changes in a checking account. Now, we can associate the two postings together, by creating a kind of \u201cparent\u201d box that refers to both of them. We will call this object a transaction : Notice here that we\u2019ve also associated a description to this transaction: \u201cDinner at Uncle Boons\u201d. A transaction also has a date , and all of its postings are recorded to occur on that date. We call this the transaction date. We can now introduce the fundamental rule of double-entry bookkeeping system: The sum of all the postings of a transaction must equal zero. Remember this, as this is the foundation of the double-entry method, and its most important characteristic. It has important consequences which I will discuss later in this document. In our example, we remove $79 from the \u201cChecking\u201d account and \u201cgive it\u201d to the \u201cRestaurant\u201d account. ($79) + ($-79) = $0. To emphasize this, I could draw a little summation line under the postings of the transaction, like this: Many Accounts \uf0c1 There may be many such transactions, over many different accounts. For example, if the owner of the accounts had a lunch the next day which she paid using a credit card, it could be represented by creating a \u201cCredit Card\u201d account dedicated to tracking the real world credit card balance, and with a corresponding transaction: In this example, the owner spent $35 at a restaurant called \u201cEataly.\u201d The previous balance of the owner\u2019s credit card was $-450; after the expense, the new balance is $-485. For each real world account, the owner can create a bookkeeping account like we did. Also, for each category of expenditure, the owner also creates a bookkeeping account. In this system, there are no limits to how many accounts can be created. Note that the balance in the example is a negative number; this is not an error. Balances for credit card accounts are normally negative: they represent an amount you owe , that the bank is lending you on credit . When your credit card company keeps track of your expenses, they write out your statement from their perspective, as positive numbers. For you, those are amounts you need to eventually pay. But here, in our accounting system, we\u2019re representing numbers from the owner\u2019s point-of-view, and from her perspective, this is money she owes, not something she has. What we have is a meal sitting in our stomach (a positive number of $ of \u201cRestaurant\u201d). Multiple Postings \uf0c1 Finally, transactions may have more than two postings; in fact, they may have any number of postings. The only thing that matters is that the sum of their amounts is zero (from the rule of double-entry bookkeeping above). For example, let\u2019s look at what would happen if the owner gets her salary paid for December: Her gross salary received in this example is recorded as $-2,905 (I\u2019ll explain the sign in a moment). $905 is set aside for taxes. Her \u201cnet\u201d salary of $2,000, the remainder, is deposited in her \u201cChecking\u201d account and the resulting balance of that account is $2,921 (the previous balance of $921 + $2,000 = $2,921). This transaction has three postings: (+2,000) + (-2,905) + (+905) = 0. The double-entry rule is respected. Now, you may ask: Why is her salary recorded as a negative number? The reasoning here is similar to that of the credit card above, though perhaps a bit more subtle. These accounts exist to track all the amounts from the owner\u2019s point-of-view. The owner gives out work, and receives money and taxes in exchange for it (positive amounts). The work given away is denominated in dollar units. It \u201cleaves\u201d the owner (imagine that the owner has potential work stored in her pocket and as she goes into work every day sprinkles that work potential giving it to the company). The owner gave $2,905\u2019s worth of work away. We want to track how much work was given, and it\u2019s done with the \u201cSalary\u201d account. That\u2019s her gross salary. Note also that we\u2019ve simplified this paycheck transaction a bit, for the sake of keeping things simple. A more realistic recording of one\u2019s pay stub would have many more accounts; we would separately account for state and federal tax amounts, as well as social security and medicare payments, deductions, insurance paid through work, and vacation time accrued during the period. But it wouldn\u2019t be much more complicated: the owner would simply translate all the amounts available from her pay stub into a single transaction with more postings. The structure remains similar. Types of Accounts \uf0c1 Let\u2019s now turn our attention to the different types of accounts an owner can have. Balance or Delta. First, the most important distinction between accounts is about whether we care about the balance at a particular point in time, or whether it only makes sense to care about differences over a period of time. For example, the balance of someone\u2019s Checking or Savings accounts is a meaningful number that both the owner and its corresponding bank will care about. Similarly, the total amount owed on someone\u2019s Credit Card account is also meaningful. The same goes with someone\u2019s remaining Mortgage amount to pay on a house. On the other hand, the total amount of Restaurant expenses since the beginning of somebody\u2019s life on earth is not particularly interesting. What we might care about for this account is the amount of Restaurant expenses incurred over a particular period of time . For example, \u201chow much did you spend in restaurants last month?\u201d Or last quarter. Or last year. Similarly, the total amount of gross salary since the beginning of someone\u2019s employment at a company a few years ago is not very important. But we would care about the total amount earned during a tax year, that is, for that time period, because it is used for reporting one\u2019s income to the tax man. Accounts whose balance at a point in time is meaningful are called balance sheet accounts . There are two types of such accounts: \u201c Assets \u201d and \u201c Liabilities .\u201d The other accounts, that is, those whose balance is not particularly meaningful but for which we are interested in calculating changes over a period of time are called income statement accounts . Again, there are two kinds: \u201c Income \u201d and \u201c Expenses .\u201d Normal sign. Secondly, we consider the usual sign of an account\u2019s balance . The great majority of accounts in the double-entry system tend to have a balance with always a positive sign, or always a negative sign (though as we\u2019ve seen previously, it is not impossible that an account\u2019s balance could change signs). This is how we will distinguish between the pairs of accounts mentioned before: For a balance sheet account, Assets normally have positive balances, and Liabilities normally have negative balances. For income statement accounts, Expenses normally have a positive balance, and Income accounts normally have a negative balance. This situation is summarized in the following table: Balance: Positive (+) Balance: Negative (-) Balance matters at a point in time (Balance Sheet) Assets Liabilities Change in balance matters over a period of time (Income Statement) Expenses Income Let\u2019s discuss each type of account and provide some examples, so that it doesn\u2019t remain too abstract. Assets. (+) Asset accounts represent something the owner has . A canonical example is banking accounts. Another one is a \u201ccash\u201d account, which counts how much money is in your wallet. Investments are also assets (their units aren\u2019t dollars in this case, but rather some number of shares of some mutual fund or stock). Finally, if you own a home, the home itself is considered an asset (and its market value fluctuates over time). Liabilities. (-) A liability account represents something the owner owes . The most common example is a credit card. Again, the statement provided by your bank will show positive numbers, but from your own perspective, they are negative numbers. A loan is also a liability account. For example, if you take out a mortgage on a home, this is money you owe, and will be tracked by an account with a negative amount. As you pay off the mortgage every month the negative number goes up, that is, its absolute value gets smaller and smaller over time (e.g., -120,000 -> -117,345). Expenses. (+) An expense account represents something you\u2019ve received , perhaps by exchanging something else to purchase it. This type of account will seem pretty natural: food, drinks, clothing, rent, flights, hotels and most other categories of things you typically spend your disposable income on. However, taxes are also typically tracked by an expense account: when you receive some salary income, the amount of taxes withheld at the source is recorded immediately as an expense. Think of it as paying for government services you receive throughout the year. Income. (-) An income account is used to count something you\u2019ve given away in order to receive something else (typically assets or expenses). For most people with jobs, that is the value of their time (a salary income). Specifically, here we\u2019re talking about the gross income. For example, if you\u2019re earning a salary of $120,000/year, that number is $120,000, not whatever amount remains after paying for taxes. Other types of income includes dividends received from investments, or interest paid from bonds held. There are also a number of oddball things received you might record as income, such the value of rewards received, e.g., cash back from a credit card, or monetary gifts from someone. In Beancount, all account names, without exception, must be associated to one of the types of accounts described previously. Since the type of an account never changes during its lifetime, we will make its type a part of an account\u2019s name, as a prefix , by convention. For example, the qualified account name for restaurant will be \u201cExpenses:Restaurant\u201d. For the bank checking account, the qualified account name will be \u201cAssets:Checking\u201d. Other than that, you can select any name you like for your accounts. You can create as many accounts as you like, and as we will see later, you can organize them in a hierarchy. As of the writing of this document, I\u2019m using more than 700 accounts to track my personal affairs. Let us now revisit our example and add some more accounts: And let\u2019s imagine there are more transactions: \u2026 and even more of them: Finally, we can label each of those accounts with one of the four types of accounts by prepending the type to their account names: A realistic book from someone tracking all of their personal affairs might easily contain thousands of transactions per year. But the principles remain simple and they remain the same: postings are applied to accounts over time, and must be parented to a transaction, and within this transaction the sum of all the postings is zero. When you do bookkeeping for a set of accounts, you are essentially describing all the postings that happen on all the accounts over time, subject to the constraint of the rule. You are creating a database of those postings in a book . You are \u201ckeeping the book,\u201d that is, traditionally, the book which contains all those transactions. Some people call this \u201cmaintaining a journal.\u201d We will now turn our attention to obtaining useful information from this data, summarizing information from the book. Trial Balance \uf0c1 Take our last example: we can easily reorder all the accounts such that all the Asset accounts appear together at the top, then all the Liabilities accounts, then Income, and finally Expenses accounts. We are simply changing the order without modifying the structure of transactions, in order to group each type of accounts together: We\u2019ve reordered the accounts with Assets accounts grouped at the top, then Liabilities, then some Equity accounts (which we have just introduced, more about them is discussed later), then Income and finally Expenses at the bottom. If we sum up the postings on all of the accounts and render just the account name and its final balance on the right, we obtain a report we call the \u201ctrial balance.\u201d This simply reflects the balance of each account at a particular point in time. And because each of the accounts began with a zero balance, and each transaction has itself a zero balance, we know that the sum of all those balances must equal zero. 1 This is a consequence of our constraining that each of the postings be part of a transaction, and that each transaction have postings that balance each other out. Income Statement \uf0c1 One kind of common information that is useful to extract from the list of transactions is a summary of changes in income statement accounts during a particular period of time. This tells us how much money was earned and spent during this period, and the difference tells us how much profit (or loss) was incurred. We call this the \u201cnet income.\u201d In order to generate this summary, we simply turn our attention to the balances of the accounts of types Income and Expenses, summing up just the transactions for a particular period, and we draw the Income balances on the left, and Expenses balances on the right: It is important to take note of the signs here: Income numbers are negative, and Expenses numbers are positive. So if you earned more than you spent (a good outcome), the final sum of Income + Expenses balances will be a negative number. Like any other income, a net income that has a negative number means that there is a corresponding amount of Assets and/or Liabilities with positive numbers (this is good for you). An Income Statement tells us what changed during a particular period of time. Companies typically report this information quarterly to investors and perhaps the public (if they are a publicly traded company) in order to share how much profit they were able to make. Individuals typically report this information on their annual tax returns. Clearing Income \uf0c1 Notice how in the income statement only the transactions within a particular interval of time are summed up. This allows one, for instance, to compute the sum of all income earned during a year. If we were to sum up all of the transactions of this account since its inception we would obtain the total amount of income earned since the account was created. A better way to achieve the same thing is to zero out the balances of the Income and Expenses accounts. Beancount calls this basic transformation \u201cclearing 2 .\u201d It is carried out by: Computing the balances of those accounts from the beginning of time to the start of the reporting period. For example, if you created your accounts in year 2000 and you wanted to generate an income statement for year 2016, you would sum up the balances from 2000 to Jan 1, 2016. Inserting transactions to empty those balances and transfer them to some other account that isn\u2019t Income nor Expenses. For instance, if the restaurant expense account for those 16 years amounts to $85,321 on Jan 1, 2016, it would insert a transaction of $-85,321 to restaurants and $+85,321 to \u201cprevious earnings\u201d. The transactions would be dated Jan 1, 2016. Including this transaction, the sum of that account would zero on that date. This is what we want. Those transactions inserted for all income statement accounts are pictured in green below. Now summing the entire set of transactions through the end of the ledger would yield only the changes during year 2016 because the balances were zero on that date: This is the semantics of the \u201cCLEAR\u201d operation of the bean-query shell. (Note that another way to achieve the same thing for income statement accounts would be to segregate and count amounts only for the transactions after the clearing date; however, jointly reporting on income statement accounts and balance sheet accounts would have incorrect balances for the balance sheet accounts.) Equity \uf0c1 The account that receives those previously accumulated incomes is called \u201cPrevious Earnings\u201d. It lives in a fifth and final type of accounts: Equity . We did not talk about this type of accounts earlier because they are most often only used to transfer amounts to build up reports, and the owner usually doesn\u2019t post changes to those types of accounts; the software does that automatically, e.g., when clearing net income. The account type \u201cequity\u201d is used for accounts that hold a summary of the net income implied by all the past activity. The point is that if we now list together the Assets, Liabilities and Equity accounts, because the Income and Expenses accounts have been zero\u2019ed out, the sum total of all these balances should equal exactly zero. And summing up all the Equity accounts clearly tells us what\u2019s our stake in the entity, in other words, if you used the assets to pay off all the liabilities, how much is left in the business\u2026 how much it\u2019s worth. Note that the normal sign of the Equity accounts is negative . There is no particular meaning to that, just that they are used to counterbalance Assets and Liabilities and if the owner has any value, that number should be negative. (A negative Equity means some positive net worth.) There are a few different Equity accounts in use in Beancount: Previous Earnings or Retained Earnings. An account used to hold the sum total of Income & Expenses balances from the beginning of time until the beginning of a reporting period. This is the account we were referring to in the previous section. Current Earnings , also called Net Income. An account used to contain the sum of Income & Expenses balances incurred during the reporting period. They are filled in by \u201cclearing\u201d the Income & Expenses accounts at the end of the reporting period. Opening Balances. An equity account used to counterbalance deposits used to initialize accounts. This type of account is used when we truncate the past history of transactions, but we also need to ensure that an account\u2019s balance begins its history with a particular amount. Once again: you don\u2019t need to define nor use these accounts yourself, as these are created for the purpose of summarizing transactions. Generally, the accounts are filled in by the clearing process described above, or filled in by Pad directives to \u201copening balances\u201d equity accounts, to account for summarized balances from the past. They are created and filled in automatically by the software. We\u2019ll see how these get used in the following sections. Balance Sheet \uf0c1 Another kind of summary is a listing of the owner\u2019s assets and debts, for each of the accounts. This answers the question: \u201c Where\u2019s the money? \u201d In theory, we could just restrict our focus to the Assets and Liabilities accounts and draw those up in a report: However, in practice, there is another closely related question that comes up and which is usually answered at the same time: \u201c Once all debts are paid off, how much are we left with? \u201d This is called the net worth . If the Income & Expenses accounts have been cleared to zero and all their balances have been transferred to Equity accounts, the net worth should be equal to the sum of all the Equity accounts. So in building up the Balance Sheet, it it customary to clear the net income and then display the balances of the Equity accounts. The report looks like this: Note that the balance sheet can be drawn for any point in time , simply by truncating the list of transactions following a particular date. A balance sheet displays a snapshot of balances at one date; an income statement displays the difference of those balances between two dates. Summarizing \uf0c1 It is useful to summarize a history of past transactions into a single equivalent deposit. For example, if we\u2019re interested in transactions for year 2016 for an account which has a balance of $450 on Jan 1, 2016, we can delete all the previous transactions and replace them with a single one that deposits $450 on Dec 31, 2015 and that takes it from somewhere else. That somewhere else will be the Equity account Opening Balances . First, we can do this for all Assets and Liabilities accounts (see transactions in blue): Then we delete all the transactions that precede the opening date, to obtain a truncated list of transactions: This is a useful operation when we\u2019re focused on the transactions for a particular interval of time. (This is a bit of an implementation detail: these operations are related to how Beancount is designed. Instead of making all the reporting operations with parameters, all of its reporting routines are simplified and instead operate on the entire stream of transactions; in this way, we convert the list of transactions to include only the data we want to report on. In this case, summarization is just a transformation which accepts the full set of transactions and returns an equivalent truncated stream. Then, from this stream, a journal can be produced that excludes the transactions from the past. From a program design perspective, this is appealing because the only state of the program is a stream of transactions, and it is never modified directly. It\u2019s simple and robust.) Period Reporting \uf0c1 Now we know we can produce a statement of changes over a period of time, by \u201cclearing\u201d and looking at just the Income & Expenses accounts (the Income Statement). We also know we can clear to produce a snapshot of Assets, Liabilities & Equity at any point in time (the Balance Sheet). More generally, we\u2019re interested in inspecting a particular period of time. That implies an income statement, but also two balance sheet statements: the balance sheet at the beginning of the period, and the balance sheet at the end of the period. In order to do this, we apply the following transformations: Open. We first clear net income at the beginning of the period, to move all previous income balances to the Equity Previous Earnings account. We then summarize up to the beginning of the period. We call the combination of clearing + summarizing: \u201cOpening.\u201d Close. We also truncate all the transactions following the end of the reporting period. We call this operation \u201cClosing.\u201d These are the meaning of the \u201cOPEN\u201d and \u201cCLOSE\u201d operations of the bean-query shell 3 . The resulting set of transactions should look like this. \u201cClosing\u201d involves two steps. First, we remove all transactions following the closing date: We can process this stream of transactions to produce an Income Statement for the period. Then we clear again at the end date of the desired report, but this time we clear the net income to \u201cEquity:Earnings:Current\u201d: From these transactions, we produce the Balance Sheet at the end of the period. This sums up the operations involved in preparing the streams of transactions to produce reports with Beancount, as well as a basic introduction to those types of reports. Chart of Accounts \uf0c1 New users are often wondering how much detail they should use in their account names. For example, should one include the payee in the account name itself, such as in these examples? Expenses:Phone:Mobile:VerizonWireless Assets:AccountsReceivable:Clients:AcmeInc Or should one use simpler names like the following, relying instead on the \u201cpayee\u201d, \u201ctags\u201d, or perhaps some other metadata in order to group the postings? Expenses:Phone Assets:AccountsReceivable The answer is that it depends on you . This is an arbitrary choice to make. It\u2019s a matter of taste. Personally I like to abuse the account names a bit and create long descriptive ones, other people prefer to keep them simple and use tags to group their postings. Sometimes one doesn\u2019t even need to filter subgroups of postings. There\u2019s no right answer, it depends on what you\u2019d like to do. One consideration to keep in mind is that account names implicitly define a hierarchy. The \u201c:\u201d separator is interpreted by some reporting code to create an in-memory tree and can allow you to collapse a node\u2019s children subaccounts and compute aggregates on the parent. Think of this as an additional way to group postings. Country-Institution Convention \uf0c1 One convention I\u2019ve come up with that works well for my assets, liabilities and income accounts is to root the tree with a code for the country the account lives in, followed by a short string for the institution it corresponds to. Underneath that, a unique name for the particular account in that institution. Like this: : : : For example, a checking account could be chosen to be \u201c Assets:US:BofA:Checking \u201d, where \u201cBofA\u201d stands for \u201cBank of America.\u201d A credit card account could include the name of the particular type of card as the account name, like \u201c Liabilities:US:Amex:Platinum \u201d, which can be useful if you have multiple cards. I\u2019ve found it doesn\u2019t make sense for me to use this scheme for expense accounts, since those tend to represent generic categories. For those, it seems to make more sense to group them by category, as in using \u201c Expenses:Food:Restaurant \u201d instead of just \u201c Expenses:Restaurant \u201d. In any case, Beancount doesn\u2019t enforce anything other than the root accounts; this is just a suggestion and this convention is not coded anywhere in the software. You have great freedom to experiment, and you can easily change all the names later by processing the text file. See the Cookbook for more practical guidance. Credits & Debits \uf0c1 At this point, we haven\u2019t discussed the concepts of \u201ccredits\u201d and \u201cdebits.\u201d This is on purpose: Beancount largely does away with these concepts because it makes everything else simpler. I believe that it is simpler to just learn that the signs of Income, Liabilities and Equity accounts are normally negative and to treat all accounts the same way than to deal with the debits and credits terminology and to treat different account categories differently. In any case, this section explains what these are. As I have pointed out in previous sections, we consider \u201cIncome\u201d, \u201cLiabilities\u201d and \u201cEquity\u201d accounts to normally have a negative balance. This may sound odd; after all, nobody thinks of their gross salary as a negative amount, and certainly your credit-card bill or mortgage loan statements report positive numbers. This is because in our double-entry accounting system we consider all accounts to be held from the perspective of the owner of the account . We use signs consistent from this perspective, because it makes all operations on account contents straightforward: they\u2019re all just simple additions and all the accounts are treated the same. In contrast, accountants traditionally keep all the balances of their accounts as positive numbers and then handle postings to those accounts differently depending on the account type upon which they are applied. The sign to apply to each account is entirely dictated by its type: Assets and Expenses accounts are debit accounts and Liabilities, Equity and Income accounts are credit accounts and require a sign adjustment. Moreover, posting a positive amount on an account is called \u201cdebiting\u201d and removing from an account is called \u201ccrediting.\u201d See this external document , for example, which nearly makes my head explode, and this recent thread has more detail. This way of handling postings makes everything much more complicated than it needs to be. The problem with this approach is that summing of amounts over the postings of a transaction is not a straightforward sum anymore. For example, let\u2019s say you\u2019re creating a new transaction with postings to two Asset accounts, an Expenses account and an Income account and the system tells you there is a $9.95 imbalance error somewhere. You\u2019re staring at the entry intently; which of the postings is too small? Or is one of the postings too large? Also, maybe a new posting needs to be added, but is it to a debit account or to a credit account? The mental gymnastics required to do this are taxing. Some double-entry accounting software tries to deal with this by creating separate columns for debits and credits and allowing the user enter an amount only in the column that corresponds to each posting account\u2019s type. This helps visually, but why not just use signs instead? Moreover, when you look at the accounting equations, you have to consider their signs as well. This makes it awkward to do transformations on them and make what is essentially a simple summation over postings into a convoluted mess that is difficult to understand. In plain-text accounting, we would rather just do away with this inconvenient baggage. We just use additions everywhere and learn to keep in mind that Liabilities, Equity and Income accounts normally have a negative balance. While this is unconventional, it\u2019s much easier to grok. And If there is a need to view a conventional report with positive numbers only, we will be able to trigger that in reporting code 4 , inverting the signs just to render them in the output. Save yourself some pain: Flush your brain from the \"debit\" and \"credit\" terminology. Accounting Equations \uf0c1 In light of the previous sections, we can easily express the accounting equations in signed terms. If, A = the sum of all Assets postings L = the sum of all Liabilities postings X = the sum of all Expenses postings I = the sum of all Income postings E = the sum of all Equity postings We can say that: A + L + E + X + I = 0 This follows from the fact that sum(all postings) = 0 Which follows from the fact that each transaction is guaranteed to sum up to zero (which is enforced by Beancount): for all transactions t, sum(postings of t) = 0 Moreover, the sum of postings from Income and Expenses is the Net Income (NI): NI = X + I If we adjust the equity to reflect the total Net Income effect by clearing the income to the Equity retained earnings account, we get an updated Equity value (E\u2019): E\u2019 = E + NI = E + X + I And we have a simplified accounting equation: A + L + E\u2019 = 0 If we were to adjust the signs for credits and debits (see previous section) and have sums that are all positive number, this becomes the familiar accounting equation: Assets - Liabilities = Equity As you can see, it\u2019s much easier to just always add up the numbers. Plain-Text Accounting \uf0c1 Ok, so now we understand the method and what it can do for us, at least in theory. The purpose of a double-entry bookkeeping system is to allow you to replicate the transactions that occur in various real world accounts into a single, unified system, in a common representation, and to extract various views and reports from this data. Let us now turn our attention to how we record this data in practice. This document talks about Beancount, whose purpose is \u201cdouble-entry bookkeeping using text files.\u201d Beancount implements a parser for a simple syntax that allows you to record transactions and postings. The syntax for an example transaction looks something like this: 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants You write many of declarations like these in a file, and Beancount will read it and create the corresponding data structures in memory. Verification. After parsing the transactions, Beancount also verifies the rule of the double-entry method: it checks that the sum of the postings on all your transactions is zero. If you make a mistake and record a transaction with a non-zero balance, an error will be displayed. Balance Assertions. Beancount allows you to replicate balances declared from external accounts, for example, a balance written on a monthly statement. It processes those and checks that the balances resulting from your input transactions match those declared balances. This helps you detect and find mistakes easily. Plugins. Beancount allows you to build programs which can automate and/or process the streams of transactions in your input files. You can build custom functionality by writing code which directly processes the transaction stream. Querying & Reporting. It provides tools to then process this stream of transactions to produce the kinds of reports we discussed earlier in this document. There are a few more details, for example, Beancount allows you to track cost basis and make currency conversions, but that\u2019s the essence of it. The Table Perspective \uf0c1 Almost always, questions asked by users on the mailing-list about how to calculate or track some value or other can be resolved easily simply by thinking of the data as a long list of rows, some of which need to be filtered and aggregated. If you consider that all that we\u2019re doing in the end is deriving \u201csums\u201d of these postings, and that the attributes of transactions and postings are what allows us to filter subsets of postings, it always becomes very simple. In almost all the cases, the answer is to find some way to disambiguate postings to select them, e.g. by account name, by attaching some tag, by using some metadata, etc. It can be illuminating to consider how this data can be represented as a table. Imagine that you have two tables: a table containing the fields of each Transaction such as date and description, and a table for the fields of each Posting, such as account, amount and currency, as well as a reference to its parent transaction. The simplest way to represent the data is to join those two tables, replicating values of the parent transaction across each of the postings. For example, this Beancount input: 2016-12-04 * \"Christmas gift\" Liabilities:CreditCard -153.45 USD Expenses:Gifts 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants 2016-12-07 * \"Pouring Ribbons\" \"Drinks with friends\" Assets:Cash -25.00 USD Expenses:Tips 4.00 USD Expenses:Alcohol could be rendered as a table like this: Date Fl Payee Narration Account Number Ccy 2016-12-04 * Christmas gift Liabilities:CreditCard -153.45 USD 2016-12-04 * Christmas gift Expenses:Gifts 153.45 USD 2016-12-06 * Biang! Dinner Liabilities:CreditCard -47.23 USD 2016-12-06 * Biang! Dinner Expenses:Restaurants 47.23 USD 2016-12-07 * Pouring Ribbons Drinks with friends Assets:Cash -25.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Tips 4.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Alcohol 21.00 USD Notice how the values of Transaction fields are replicated for each posting. This is exactly like a regular database join operation. The posting fields begin at column \u201cAccount.\u201d (Also note that this example table is simplified; in practice there are many more fields.) If you had a joined table just like this you could filter it and sum up amounts for arbitrary groups of postings. This is exactly what the bean-query tool allows you to do: You can run an SQL query on the data equivalent to this in-memory table and list values like this: SELECT date, payee, number WHERE account = \"Liabilities:CreditCard\"; Or sum up positions like this: SELECT account, sum(position) GROUP BY account; This simple last command generates the trial balance report. Note that the table representation does not inherently constrain the postings to sum to zero. If your selection criteria for the rows (in the WHERE clause) always selects all the postings for each of the matching transactions, you are ensured that the final sum of all the postings is zero. If not, the sum may be anything else. Just something to keep in mind. If you\u2019re familiar with SQL databases, you might ask why Beancount doesn\u2019t simply process its data in order to fill up an existing database system, so that the user could then use those database\u2019s tools. There are two main reasons for this: Reporting Operations. In order to generate income statements and balance sheets, the list of transactions needs to be preprocessed using the clear, open and close operations described previously. These operations are not trivial to implement in database queries and are dependent on just the report and ideally don\u2019t need to modify the input data. We\u2019d have to load up the posting data into memory and then run some code. We\u2019re already doing that by parsing the input file; the database step would be superfluous. Aggregating Positions. Though we haven\u2019t discussed it in this document so far, the contents of accounts may contain different types of commodities, as well as positions with an attached cost basis. The way that these positions are aggregated together requires the implementation of a custom data type because it obeys some rules about how positions are able to cancel each other out (see How Inventories Work for details). It would be very difficult to build these operations with an SQL database beyond the context of using just a single currency and ignoring cost basis. This is why Beancount provides a custom tool to directly process and query its data: It provides its own implementation of an SQL client that lets you specify open and close dates and leverages a custom \u201cInventory\u201d data structure to create sums of the positions of postings. This tools supports columns of Beancount\u2019s core types: Amount, Position and Inventory objects. (In any case, if you\u2019re not convinced, Beancount provides a tool to export its contents to a regular SQL database system. Feel free to experiment with it if you like, knock yourself out.) Please don\u2019t pay attention to the numbers in these large figures, they were randomly generated and don\u2019t reflect this. We\u2019re just interested in showing the structure, in these figures. \u21a9 Note that this is unrelated to the term \u201cclearing transactions\u201d which means acknowledging or marking that some transactions have been eyeballed by the bookkeeper and checked for correctness. \u21a9 Note that operations have nothing to do with the Open and Close directives Beancount provides. \u21a9 This is not provided yet in Beancount, but would be trivial to implement. All we'd need to do is invert the signs of balances from Liabilities, Income and Equity accounts. It's on the roadmap to provide this eventually. \u21a9","title":"The Double Entry Counting Method"},{"location":"the_double_entry_counting_method.html#the-double-entry-counting-method","text":"Martin Blais, December 2016 http://furius.ca/beancount/doc/double-entry Introduction Basics of Double-Entry Bookkeeping Statements Single-Entry Bookkeeping Double-Entry Bookkeeping Many Accounts Multiple Postings Types of Accounts Trial Balance Income Statement Clearing Income Equity Balance Sheet Summarizing Period Reporting Chart of Accounts Country-Institution Convention Credits & Debits Accounting Equations Plain-Text Accounting The Table Perspective","title":"The Double-Entry Counting Method"},{"location":"the_double_entry_counting_method.html#introduction","text":"This document is a gentle introduction to the double-entry counting method, as written from the perspective of a computer scientist. It is an attempt to explain basic bookkeeping using as simple an approach as possible, doing away with some of the idiosyncrasies normally involved in accounting. It is also representative of how Beancount works, and it should be useful to all users of plain-text accounting . Note that I am not an accountant, and in the process of writing this document I may have used terminology that is slightly different or unusual to that which is taught in perhaps more traditional training in accounting. I granted myself license to create something new and perhaps even unusual in order to explain those ideas as simply and clearly as possible to someone unfamiliar with them. I believe that the method of double-entry counting should be taught to everyone at the high school level everywhere as it is a tremendously useful organizational skill, and I hope that this text can help spread its knowledge beyond professional circles.","title":"Introduction"},{"location":"the_double_entry_counting_method.html#basics-of-double-entry-bookkeeping","text":"The double-entry system is just a simple method of counting , with some simple rules. Let\u2019s begin by defining the notion of an account . An account is something that can contain things, like a bag. It is used to count things, to accumulate things. Let\u2019s draw a horizontal arrow to visually represent the evolving contents of an account over time: On the left, we have the past, and to the right, increasing time: the present, the future, etc. For now, let\u2019s assume that accounts can contain only one kind of thing, for example, dollars . All accounts begin with an empty content of zero dollars. We will call the number of units in the account the balance of an account. Note that it represents its contents at a particular point in time. I will draw the balance using a number above the account\u2019s timeline: The contents of accounts can change over time. In order to change the content of an account, we have to add something to it. We will call this addition a posting to an account, and I will draw this change as a circled number on the account\u2019s timeline, for example, adding $100 to the account: Now, we can draw the updated balance of the account after the posting with another little number right after it: The account\u2019s balance, after adding $100, is now $100. We can also remove from the contents of an account. For example, we could remove $25, and the resulting account balance is now $75: Account balances can also become negative , if we remove more dollars than there are in the account. For example, if we remove $200 from this account, the balance now becomes $-125: It\u2019s perfectly fine for accounts to contain a negative balance number. Remember that all we\u2019re doing is counting things. As we will see shortly, some accounts will remain with a negative balance for most of their timeline.","title":"Basics of Double-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#statements","text":"Something worthy of notice is how the timeline notation I\u2019ve written in the previous section is analogous to paper account statements institutions maintain for each client and which you typically receive through the mail: Date Description Amount Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... -25.00 1075.00 2016-10-06 ... -200.00 875.00 Final Balance 875.00 Sometimes the amount column is split into two, one showing the positive amounts and the other the negative ones: Date Description Debit Credit Balance 2016-10-02 ... 100.00 1100.00 2016-10-05 ... 25.00 1075.00 2016-10-06 ... 200.00 875.00 Final Balance 875.00 Here, \u201cdebit\u201d means \u201cremoved from your account\u201d and \u201ccredit\u201d means \u201cdeposited in your account.\u201d Sometimes the words \u201cwithdrawals\u201d and \u201cdeposits\u201d will be used. It all depends on context: for checking and savings accounts it is usual to have both types of postings, but for a credit card account typically it shows only positive numbers and then the occasional monthly payment so the single column format is used. In any case, the \u201cbalance\u201d column always shows the resulting balance after the amount has been posted to the account. And sometimes the statements are rendered in decreasing order of time.","title":"Statements"},{"location":"the_double_entry_counting_method.html#single-entry-bookkeeping","text":"In this story, this account belongs to someone. We\u2019ll call this person the owner of the account. The account can be used to represent a real world account, for example, imagine that we use it to represent the content of the owner\u2019s checking account at a bank. So we\u2019re going to label the account by giving it a name, in this case \u201cChecking\u201d: Imagine that at some point, this account has a balance of $1000, like I\u2019ve drawn on the picture. Now, if the owner spends $79 of this account, we would represent it like this: Furthermore, if the expense was for a meal at a restaurant, we could flag the posting with a category to indicate what the change was used for. Let\u2019s say, \u201cRestaurant\u201d, like this: Now, if we have a lot of these, we could write a computer program to accumulate all the changes for each category and calculate the sums for each of them. That would tell us how much we spent in restaurants in total, for example. This is called the single-entry method of accounting. But we\u2019re not going to do it this way; we have a better way. Bear with me for a few more sections.","title":"Single-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#double-entry-bookkeeping","text":"An owner may have multiple accounts. I will represent this by drawing many similar account timelines on the same graphic. As before, these are labeled with unique names. Let\u2019s assume that the owner has the same \u201cChecking\u201d account as previously, but now also a \u201c Restaurant \u201d account as well, which can be used to accumulate all food expenses at restaurants. It looks like this: Now, instead of categorizing the posting to a \u201crestaurant category\u201d as we did previously, we could create a matching posting on the \u201cRestaurant\u201d account to record how much we spent for food, with the amount spent ($79): The \u201cRestaurant\u201d account, like all other accounts, also has an accumulated balance, so we can find out how much we spent in \u201cRestaurant\u201d in total. This is entirely symmetrical to counting changes in a checking account. Now, we can associate the two postings together, by creating a kind of \u201cparent\u201d box that refers to both of them. We will call this object a transaction : Notice here that we\u2019ve also associated a description to this transaction: \u201cDinner at Uncle Boons\u201d. A transaction also has a date , and all of its postings are recorded to occur on that date. We call this the transaction date. We can now introduce the fundamental rule of double-entry bookkeeping system: The sum of all the postings of a transaction must equal zero. Remember this, as this is the foundation of the double-entry method, and its most important characteristic. It has important consequences which I will discuss later in this document. In our example, we remove $79 from the \u201cChecking\u201d account and \u201cgive it\u201d to the \u201cRestaurant\u201d account. ($79) + ($-79) = $0. To emphasize this, I could draw a little summation line under the postings of the transaction, like this:","title":"Double-Entry Bookkeeping"},{"location":"the_double_entry_counting_method.html#many-accounts","text":"There may be many such transactions, over many different accounts. For example, if the owner of the accounts had a lunch the next day which she paid using a credit card, it could be represented by creating a \u201cCredit Card\u201d account dedicated to tracking the real world credit card balance, and with a corresponding transaction: In this example, the owner spent $35 at a restaurant called \u201cEataly.\u201d The previous balance of the owner\u2019s credit card was $-450; after the expense, the new balance is $-485. For each real world account, the owner can create a bookkeeping account like we did. Also, for each category of expenditure, the owner also creates a bookkeeping account. In this system, there are no limits to how many accounts can be created. Note that the balance in the example is a negative number; this is not an error. Balances for credit card accounts are normally negative: they represent an amount you owe , that the bank is lending you on credit . When your credit card company keeps track of your expenses, they write out your statement from their perspective, as positive numbers. For you, those are amounts you need to eventually pay. But here, in our accounting system, we\u2019re representing numbers from the owner\u2019s point-of-view, and from her perspective, this is money she owes, not something she has. What we have is a meal sitting in our stomach (a positive number of $ of \u201cRestaurant\u201d).","title":"Many Accounts"},{"location":"the_double_entry_counting_method.html#multiple-postings","text":"Finally, transactions may have more than two postings; in fact, they may have any number of postings. The only thing that matters is that the sum of their amounts is zero (from the rule of double-entry bookkeeping above). For example, let\u2019s look at what would happen if the owner gets her salary paid for December: Her gross salary received in this example is recorded as $-2,905 (I\u2019ll explain the sign in a moment). $905 is set aside for taxes. Her \u201cnet\u201d salary of $2,000, the remainder, is deposited in her \u201cChecking\u201d account and the resulting balance of that account is $2,921 (the previous balance of $921 + $2,000 = $2,921). This transaction has three postings: (+2,000) + (-2,905) + (+905) = 0. The double-entry rule is respected. Now, you may ask: Why is her salary recorded as a negative number? The reasoning here is similar to that of the credit card above, though perhaps a bit more subtle. These accounts exist to track all the amounts from the owner\u2019s point-of-view. The owner gives out work, and receives money and taxes in exchange for it (positive amounts). The work given away is denominated in dollar units. It \u201cleaves\u201d the owner (imagine that the owner has potential work stored in her pocket and as she goes into work every day sprinkles that work potential giving it to the company). The owner gave $2,905\u2019s worth of work away. We want to track how much work was given, and it\u2019s done with the \u201cSalary\u201d account. That\u2019s her gross salary. Note also that we\u2019ve simplified this paycheck transaction a bit, for the sake of keeping things simple. A more realistic recording of one\u2019s pay stub would have many more accounts; we would separately account for state and federal tax amounts, as well as social security and medicare payments, deductions, insurance paid through work, and vacation time accrued during the period. But it wouldn\u2019t be much more complicated: the owner would simply translate all the amounts available from her pay stub into a single transaction with more postings. The structure remains similar.","title":"Multiple Postings"},{"location":"the_double_entry_counting_method.html#types-of-accounts","text":"Let\u2019s now turn our attention to the different types of accounts an owner can have. Balance or Delta. First, the most important distinction between accounts is about whether we care about the balance at a particular point in time, or whether it only makes sense to care about differences over a period of time. For example, the balance of someone\u2019s Checking or Savings accounts is a meaningful number that both the owner and its corresponding bank will care about. Similarly, the total amount owed on someone\u2019s Credit Card account is also meaningful. The same goes with someone\u2019s remaining Mortgage amount to pay on a house. On the other hand, the total amount of Restaurant expenses since the beginning of somebody\u2019s life on earth is not particularly interesting. What we might care about for this account is the amount of Restaurant expenses incurred over a particular period of time . For example, \u201chow much did you spend in restaurants last month?\u201d Or last quarter. Or last year. Similarly, the total amount of gross salary since the beginning of someone\u2019s employment at a company a few years ago is not very important. But we would care about the total amount earned during a tax year, that is, for that time period, because it is used for reporting one\u2019s income to the tax man. Accounts whose balance at a point in time is meaningful are called balance sheet accounts . There are two types of such accounts: \u201c Assets \u201d and \u201c Liabilities .\u201d The other accounts, that is, those whose balance is not particularly meaningful but for which we are interested in calculating changes over a period of time are called income statement accounts . Again, there are two kinds: \u201c Income \u201d and \u201c Expenses .\u201d Normal sign. Secondly, we consider the usual sign of an account\u2019s balance . The great majority of accounts in the double-entry system tend to have a balance with always a positive sign, or always a negative sign (though as we\u2019ve seen previously, it is not impossible that an account\u2019s balance could change signs). This is how we will distinguish between the pairs of accounts mentioned before: For a balance sheet account, Assets normally have positive balances, and Liabilities normally have negative balances. For income statement accounts, Expenses normally have a positive balance, and Income accounts normally have a negative balance. This situation is summarized in the following table: Balance: Positive (+) Balance: Negative (-) Balance matters at a point in time (Balance Sheet) Assets Liabilities Change in balance matters over a period of time (Income Statement) Expenses Income Let\u2019s discuss each type of account and provide some examples, so that it doesn\u2019t remain too abstract. Assets. (+) Asset accounts represent something the owner has . A canonical example is banking accounts. Another one is a \u201ccash\u201d account, which counts how much money is in your wallet. Investments are also assets (their units aren\u2019t dollars in this case, but rather some number of shares of some mutual fund or stock). Finally, if you own a home, the home itself is considered an asset (and its market value fluctuates over time). Liabilities. (-) A liability account represents something the owner owes . The most common example is a credit card. Again, the statement provided by your bank will show positive numbers, but from your own perspective, they are negative numbers. A loan is also a liability account. For example, if you take out a mortgage on a home, this is money you owe, and will be tracked by an account with a negative amount. As you pay off the mortgage every month the negative number goes up, that is, its absolute value gets smaller and smaller over time (e.g., -120,000 -> -117,345). Expenses. (+) An expense account represents something you\u2019ve received , perhaps by exchanging something else to purchase it. This type of account will seem pretty natural: food, drinks, clothing, rent, flights, hotels and most other categories of things you typically spend your disposable income on. However, taxes are also typically tracked by an expense account: when you receive some salary income, the amount of taxes withheld at the source is recorded immediately as an expense. Think of it as paying for government services you receive throughout the year. Income. (-) An income account is used to count something you\u2019ve given away in order to receive something else (typically assets or expenses). For most people with jobs, that is the value of their time (a salary income). Specifically, here we\u2019re talking about the gross income. For example, if you\u2019re earning a salary of $120,000/year, that number is $120,000, not whatever amount remains after paying for taxes. Other types of income includes dividends received from investments, or interest paid from bonds held. There are also a number of oddball things received you might record as income, such the value of rewards received, e.g., cash back from a credit card, or monetary gifts from someone. In Beancount, all account names, without exception, must be associated to one of the types of accounts described previously. Since the type of an account never changes during its lifetime, we will make its type a part of an account\u2019s name, as a prefix , by convention. For example, the qualified account name for restaurant will be \u201cExpenses:Restaurant\u201d. For the bank checking account, the qualified account name will be \u201cAssets:Checking\u201d. Other than that, you can select any name you like for your accounts. You can create as many accounts as you like, and as we will see later, you can organize them in a hierarchy. As of the writing of this document, I\u2019m using more than 700 accounts to track my personal affairs. Let us now revisit our example and add some more accounts: And let\u2019s imagine there are more transactions: \u2026 and even more of them: Finally, we can label each of those accounts with one of the four types of accounts by prepending the type to their account names: A realistic book from someone tracking all of their personal affairs might easily contain thousands of transactions per year. But the principles remain simple and they remain the same: postings are applied to accounts over time, and must be parented to a transaction, and within this transaction the sum of all the postings is zero. When you do bookkeeping for a set of accounts, you are essentially describing all the postings that happen on all the accounts over time, subject to the constraint of the rule. You are creating a database of those postings in a book . You are \u201ckeeping the book,\u201d that is, traditionally, the book which contains all those transactions. Some people call this \u201cmaintaining a journal.\u201d We will now turn our attention to obtaining useful information from this data, summarizing information from the book.","title":"Types of Accounts"},{"location":"the_double_entry_counting_method.html#trial-balance","text":"Take our last example: we can easily reorder all the accounts such that all the Asset accounts appear together at the top, then all the Liabilities accounts, then Income, and finally Expenses accounts. We are simply changing the order without modifying the structure of transactions, in order to group each type of accounts together: We\u2019ve reordered the accounts with Assets accounts grouped at the top, then Liabilities, then some Equity accounts (which we have just introduced, more about them is discussed later), then Income and finally Expenses at the bottom. If we sum up the postings on all of the accounts and render just the account name and its final balance on the right, we obtain a report we call the \u201ctrial balance.\u201d This simply reflects the balance of each account at a particular point in time. And because each of the accounts began with a zero balance, and each transaction has itself a zero balance, we know that the sum of all those balances must equal zero. 1 This is a consequence of our constraining that each of the postings be part of a transaction, and that each transaction have postings that balance each other out.","title":"Trial Balance"},{"location":"the_double_entry_counting_method.html#income-statement","text":"One kind of common information that is useful to extract from the list of transactions is a summary of changes in income statement accounts during a particular period of time. This tells us how much money was earned and spent during this period, and the difference tells us how much profit (or loss) was incurred. We call this the \u201cnet income.\u201d In order to generate this summary, we simply turn our attention to the balances of the accounts of types Income and Expenses, summing up just the transactions for a particular period, and we draw the Income balances on the left, and Expenses balances on the right: It is important to take note of the signs here: Income numbers are negative, and Expenses numbers are positive. So if you earned more than you spent (a good outcome), the final sum of Income + Expenses balances will be a negative number. Like any other income, a net income that has a negative number means that there is a corresponding amount of Assets and/or Liabilities with positive numbers (this is good for you). An Income Statement tells us what changed during a particular period of time. Companies typically report this information quarterly to investors and perhaps the public (if they are a publicly traded company) in order to share how much profit they were able to make. Individuals typically report this information on their annual tax returns.","title":"Income Statement"},{"location":"the_double_entry_counting_method.html#clearing-income","text":"Notice how in the income statement only the transactions within a particular interval of time are summed up. This allows one, for instance, to compute the sum of all income earned during a year. If we were to sum up all of the transactions of this account since its inception we would obtain the total amount of income earned since the account was created. A better way to achieve the same thing is to zero out the balances of the Income and Expenses accounts. Beancount calls this basic transformation \u201cclearing 2 .\u201d It is carried out by: Computing the balances of those accounts from the beginning of time to the start of the reporting period. For example, if you created your accounts in year 2000 and you wanted to generate an income statement for year 2016, you would sum up the balances from 2000 to Jan 1, 2016. Inserting transactions to empty those balances and transfer them to some other account that isn\u2019t Income nor Expenses. For instance, if the restaurant expense account for those 16 years amounts to $85,321 on Jan 1, 2016, it would insert a transaction of $-85,321 to restaurants and $+85,321 to \u201cprevious earnings\u201d. The transactions would be dated Jan 1, 2016. Including this transaction, the sum of that account would zero on that date. This is what we want. Those transactions inserted for all income statement accounts are pictured in green below. Now summing the entire set of transactions through the end of the ledger would yield only the changes during year 2016 because the balances were zero on that date: This is the semantics of the \u201cCLEAR\u201d operation of the bean-query shell. (Note that another way to achieve the same thing for income statement accounts would be to segregate and count amounts only for the transactions after the clearing date; however, jointly reporting on income statement accounts and balance sheet accounts would have incorrect balances for the balance sheet accounts.)","title":"Clearing Income"},{"location":"the_double_entry_counting_method.html#equity","text":"The account that receives those previously accumulated incomes is called \u201cPrevious Earnings\u201d. It lives in a fifth and final type of accounts: Equity . We did not talk about this type of accounts earlier because they are most often only used to transfer amounts to build up reports, and the owner usually doesn\u2019t post changes to those types of accounts; the software does that automatically, e.g., when clearing net income. The account type \u201cequity\u201d is used for accounts that hold a summary of the net income implied by all the past activity. The point is that if we now list together the Assets, Liabilities and Equity accounts, because the Income and Expenses accounts have been zero\u2019ed out, the sum total of all these balances should equal exactly zero. And summing up all the Equity accounts clearly tells us what\u2019s our stake in the entity, in other words, if you used the assets to pay off all the liabilities, how much is left in the business\u2026 how much it\u2019s worth. Note that the normal sign of the Equity accounts is negative . There is no particular meaning to that, just that they are used to counterbalance Assets and Liabilities and if the owner has any value, that number should be negative. (A negative Equity means some positive net worth.) There are a few different Equity accounts in use in Beancount: Previous Earnings or Retained Earnings. An account used to hold the sum total of Income & Expenses balances from the beginning of time until the beginning of a reporting period. This is the account we were referring to in the previous section. Current Earnings , also called Net Income. An account used to contain the sum of Income & Expenses balances incurred during the reporting period. They are filled in by \u201cclearing\u201d the Income & Expenses accounts at the end of the reporting period. Opening Balances. An equity account used to counterbalance deposits used to initialize accounts. This type of account is used when we truncate the past history of transactions, but we also need to ensure that an account\u2019s balance begins its history with a particular amount. Once again: you don\u2019t need to define nor use these accounts yourself, as these are created for the purpose of summarizing transactions. Generally, the accounts are filled in by the clearing process described above, or filled in by Pad directives to \u201copening balances\u201d equity accounts, to account for summarized balances from the past. They are created and filled in automatically by the software. We\u2019ll see how these get used in the following sections.","title":"Equity"},{"location":"the_double_entry_counting_method.html#balance-sheet","text":"Another kind of summary is a listing of the owner\u2019s assets and debts, for each of the accounts. This answers the question: \u201c Where\u2019s the money? \u201d In theory, we could just restrict our focus to the Assets and Liabilities accounts and draw those up in a report: However, in practice, there is another closely related question that comes up and which is usually answered at the same time: \u201c Once all debts are paid off, how much are we left with? \u201d This is called the net worth . If the Income & Expenses accounts have been cleared to zero and all their balances have been transferred to Equity accounts, the net worth should be equal to the sum of all the Equity accounts. So in building up the Balance Sheet, it it customary to clear the net income and then display the balances of the Equity accounts. The report looks like this: Note that the balance sheet can be drawn for any point in time , simply by truncating the list of transactions following a particular date. A balance sheet displays a snapshot of balances at one date; an income statement displays the difference of those balances between two dates.","title":"Balance Sheet"},{"location":"the_double_entry_counting_method.html#summarizing","text":"It is useful to summarize a history of past transactions into a single equivalent deposit. For example, if we\u2019re interested in transactions for year 2016 for an account which has a balance of $450 on Jan 1, 2016, we can delete all the previous transactions and replace them with a single one that deposits $450 on Dec 31, 2015 and that takes it from somewhere else. That somewhere else will be the Equity account Opening Balances . First, we can do this for all Assets and Liabilities accounts (see transactions in blue): Then we delete all the transactions that precede the opening date, to obtain a truncated list of transactions: This is a useful operation when we\u2019re focused on the transactions for a particular interval of time. (This is a bit of an implementation detail: these operations are related to how Beancount is designed. Instead of making all the reporting operations with parameters, all of its reporting routines are simplified and instead operate on the entire stream of transactions; in this way, we convert the list of transactions to include only the data we want to report on. In this case, summarization is just a transformation which accepts the full set of transactions and returns an equivalent truncated stream. Then, from this stream, a journal can be produced that excludes the transactions from the past. From a program design perspective, this is appealing because the only state of the program is a stream of transactions, and it is never modified directly. It\u2019s simple and robust.)","title":"Summarizing"},{"location":"the_double_entry_counting_method.html#period-reporting","text":"Now we know we can produce a statement of changes over a period of time, by \u201cclearing\u201d and looking at just the Income & Expenses accounts (the Income Statement). We also know we can clear to produce a snapshot of Assets, Liabilities & Equity at any point in time (the Balance Sheet). More generally, we\u2019re interested in inspecting a particular period of time. That implies an income statement, but also two balance sheet statements: the balance sheet at the beginning of the period, and the balance sheet at the end of the period. In order to do this, we apply the following transformations: Open. We first clear net income at the beginning of the period, to move all previous income balances to the Equity Previous Earnings account. We then summarize up to the beginning of the period. We call the combination of clearing + summarizing: \u201cOpening.\u201d Close. We also truncate all the transactions following the end of the reporting period. We call this operation \u201cClosing.\u201d These are the meaning of the \u201cOPEN\u201d and \u201cCLOSE\u201d operations of the bean-query shell 3 . The resulting set of transactions should look like this. \u201cClosing\u201d involves two steps. First, we remove all transactions following the closing date: We can process this stream of transactions to produce an Income Statement for the period. Then we clear again at the end date of the desired report, but this time we clear the net income to \u201cEquity:Earnings:Current\u201d: From these transactions, we produce the Balance Sheet at the end of the period. This sums up the operations involved in preparing the streams of transactions to produce reports with Beancount, as well as a basic introduction to those types of reports.","title":"Period Reporting"},{"location":"the_double_entry_counting_method.html#chart-of-accounts","text":"New users are often wondering how much detail they should use in their account names. For example, should one include the payee in the account name itself, such as in these examples? Expenses:Phone:Mobile:VerizonWireless Assets:AccountsReceivable:Clients:AcmeInc Or should one use simpler names like the following, relying instead on the \u201cpayee\u201d, \u201ctags\u201d, or perhaps some other metadata in order to group the postings? Expenses:Phone Assets:AccountsReceivable The answer is that it depends on you . This is an arbitrary choice to make. It\u2019s a matter of taste. Personally I like to abuse the account names a bit and create long descriptive ones, other people prefer to keep them simple and use tags to group their postings. Sometimes one doesn\u2019t even need to filter subgroups of postings. There\u2019s no right answer, it depends on what you\u2019d like to do. One consideration to keep in mind is that account names implicitly define a hierarchy. The \u201c:\u201d separator is interpreted by some reporting code to create an in-memory tree and can allow you to collapse a node\u2019s children subaccounts and compute aggregates on the parent. Think of this as an additional way to group postings.","title":"Chart of Accounts"},{"location":"the_double_entry_counting_method.html#country-institution-convention","text":"One convention I\u2019ve come up with that works well for my assets, liabilities and income accounts is to root the tree with a code for the country the account lives in, followed by a short string for the institution it corresponds to. Underneath that, a unique name for the particular account in that institution. Like this: : : : For example, a checking account could be chosen to be \u201c Assets:US:BofA:Checking \u201d, where \u201cBofA\u201d stands for \u201cBank of America.\u201d A credit card account could include the name of the particular type of card as the account name, like \u201c Liabilities:US:Amex:Platinum \u201d, which can be useful if you have multiple cards. I\u2019ve found it doesn\u2019t make sense for me to use this scheme for expense accounts, since those tend to represent generic categories. For those, it seems to make more sense to group them by category, as in using \u201c Expenses:Food:Restaurant \u201d instead of just \u201c Expenses:Restaurant \u201d. In any case, Beancount doesn\u2019t enforce anything other than the root accounts; this is just a suggestion and this convention is not coded anywhere in the software. You have great freedom to experiment, and you can easily change all the names later by processing the text file. See the Cookbook for more practical guidance.","title":"Country-Institution Convention"},{"location":"the_double_entry_counting_method.html#credits-debits","text":"At this point, we haven\u2019t discussed the concepts of \u201ccredits\u201d and \u201cdebits.\u201d This is on purpose: Beancount largely does away with these concepts because it makes everything else simpler. I believe that it is simpler to just learn that the signs of Income, Liabilities and Equity accounts are normally negative and to treat all accounts the same way than to deal with the debits and credits terminology and to treat different account categories differently. In any case, this section explains what these are. As I have pointed out in previous sections, we consider \u201cIncome\u201d, \u201cLiabilities\u201d and \u201cEquity\u201d accounts to normally have a negative balance. This may sound odd; after all, nobody thinks of their gross salary as a negative amount, and certainly your credit-card bill or mortgage loan statements report positive numbers. This is because in our double-entry accounting system we consider all accounts to be held from the perspective of the owner of the account . We use signs consistent from this perspective, because it makes all operations on account contents straightforward: they\u2019re all just simple additions and all the accounts are treated the same. In contrast, accountants traditionally keep all the balances of their accounts as positive numbers and then handle postings to those accounts differently depending on the account type upon which they are applied. The sign to apply to each account is entirely dictated by its type: Assets and Expenses accounts are debit accounts and Liabilities, Equity and Income accounts are credit accounts and require a sign adjustment. Moreover, posting a positive amount on an account is called \u201cdebiting\u201d and removing from an account is called \u201ccrediting.\u201d See this external document , for example, which nearly makes my head explode, and this recent thread has more detail. This way of handling postings makes everything much more complicated than it needs to be. The problem with this approach is that summing of amounts over the postings of a transaction is not a straightforward sum anymore. For example, let\u2019s say you\u2019re creating a new transaction with postings to two Asset accounts, an Expenses account and an Income account and the system tells you there is a $9.95 imbalance error somewhere. You\u2019re staring at the entry intently; which of the postings is too small? Or is one of the postings too large? Also, maybe a new posting needs to be added, but is it to a debit account or to a credit account? The mental gymnastics required to do this are taxing. Some double-entry accounting software tries to deal with this by creating separate columns for debits and credits and allowing the user enter an amount only in the column that corresponds to each posting account\u2019s type. This helps visually, but why not just use signs instead? Moreover, when you look at the accounting equations, you have to consider their signs as well. This makes it awkward to do transformations on them and make what is essentially a simple summation over postings into a convoluted mess that is difficult to understand. In plain-text accounting, we would rather just do away with this inconvenient baggage. We just use additions everywhere and learn to keep in mind that Liabilities, Equity and Income accounts normally have a negative balance. While this is unconventional, it\u2019s much easier to grok. And If there is a need to view a conventional report with positive numbers only, we will be able to trigger that in reporting code 4 , inverting the signs just to render them in the output. Save yourself some pain: Flush your brain from the \"debit\" and \"credit\" terminology.","title":"Credits & Debits"},{"location":"the_double_entry_counting_method.html#accounting-equations","text":"In light of the previous sections, we can easily express the accounting equations in signed terms. If, A = the sum of all Assets postings L = the sum of all Liabilities postings X = the sum of all Expenses postings I = the sum of all Income postings E = the sum of all Equity postings We can say that: A + L + E + X + I = 0 This follows from the fact that sum(all postings) = 0 Which follows from the fact that each transaction is guaranteed to sum up to zero (which is enforced by Beancount): for all transactions t, sum(postings of t) = 0 Moreover, the sum of postings from Income and Expenses is the Net Income (NI): NI = X + I If we adjust the equity to reflect the total Net Income effect by clearing the income to the Equity retained earnings account, we get an updated Equity value (E\u2019): E\u2019 = E + NI = E + X + I And we have a simplified accounting equation: A + L + E\u2019 = 0 If we were to adjust the signs for credits and debits (see previous section) and have sums that are all positive number, this becomes the familiar accounting equation: Assets - Liabilities = Equity As you can see, it\u2019s much easier to just always add up the numbers.","title":"Accounting Equations"},{"location":"the_double_entry_counting_method.html#plain-text-accounting","text":"Ok, so now we understand the method and what it can do for us, at least in theory. The purpose of a double-entry bookkeeping system is to allow you to replicate the transactions that occur in various real world accounts into a single, unified system, in a common representation, and to extract various views and reports from this data. Let us now turn our attention to how we record this data in practice. This document talks about Beancount, whose purpose is \u201cdouble-entry bookkeeping using text files.\u201d Beancount implements a parser for a simple syntax that allows you to record transactions and postings. The syntax for an example transaction looks something like this: 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants You write many of declarations like these in a file, and Beancount will read it and create the corresponding data structures in memory. Verification. After parsing the transactions, Beancount also verifies the rule of the double-entry method: it checks that the sum of the postings on all your transactions is zero. If you make a mistake and record a transaction with a non-zero balance, an error will be displayed. Balance Assertions. Beancount allows you to replicate balances declared from external accounts, for example, a balance written on a monthly statement. It processes those and checks that the balances resulting from your input transactions match those declared balances. This helps you detect and find mistakes easily. Plugins. Beancount allows you to build programs which can automate and/or process the streams of transactions in your input files. You can build custom functionality by writing code which directly processes the transaction stream. Querying & Reporting. It provides tools to then process this stream of transactions to produce the kinds of reports we discussed earlier in this document. There are a few more details, for example, Beancount allows you to track cost basis and make currency conversions, but that\u2019s the essence of it.","title":"Plain-Text Accounting"},{"location":"the_double_entry_counting_method.html#the-table-perspective","text":"Almost always, questions asked by users on the mailing-list about how to calculate or track some value or other can be resolved easily simply by thinking of the data as a long list of rows, some of which need to be filtered and aggregated. If you consider that all that we\u2019re doing in the end is deriving \u201csums\u201d of these postings, and that the attributes of transactions and postings are what allows us to filter subsets of postings, it always becomes very simple. In almost all the cases, the answer is to find some way to disambiguate postings to select them, e.g. by account name, by attaching some tag, by using some metadata, etc. It can be illuminating to consider how this data can be represented as a table. Imagine that you have two tables: a table containing the fields of each Transaction such as date and description, and a table for the fields of each Posting, such as account, amount and currency, as well as a reference to its parent transaction. The simplest way to represent the data is to join those two tables, replicating values of the parent transaction across each of the postings. For example, this Beancount input: 2016-12-04 * \"Christmas gift\" Liabilities:CreditCard -153.45 USD Expenses:Gifts 2016-12-06 * \"Biang!\" \"Dinner\" Liabilities:CreditCard -47.23 USD Expenses:Restaurants 2016-12-07 * \"Pouring Ribbons\" \"Drinks with friends\" Assets:Cash -25.00 USD Expenses:Tips 4.00 USD Expenses:Alcohol could be rendered as a table like this: Date Fl Payee Narration Account Number Ccy 2016-12-04 * Christmas gift Liabilities:CreditCard -153.45 USD 2016-12-04 * Christmas gift Expenses:Gifts 153.45 USD 2016-12-06 * Biang! Dinner Liabilities:CreditCard -47.23 USD 2016-12-06 * Biang! Dinner Expenses:Restaurants 47.23 USD 2016-12-07 * Pouring Ribbons Drinks with friends Assets:Cash -25.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Tips 4.00 USD 2016-12-07 * Pouring Ribbons Drinks with friends Expenses:Alcohol 21.00 USD Notice how the values of Transaction fields are replicated for each posting. This is exactly like a regular database join operation. The posting fields begin at column \u201cAccount.\u201d (Also note that this example table is simplified; in practice there are many more fields.) If you had a joined table just like this you could filter it and sum up amounts for arbitrary groups of postings. This is exactly what the bean-query tool allows you to do: You can run an SQL query on the data equivalent to this in-memory table and list values like this: SELECT date, payee, number WHERE account = \"Liabilities:CreditCard\"; Or sum up positions like this: SELECT account, sum(position) GROUP BY account; This simple last command generates the trial balance report. Note that the table representation does not inherently constrain the postings to sum to zero. If your selection criteria for the rows (in the WHERE clause) always selects all the postings for each of the matching transactions, you are ensured that the final sum of all the postings is zero. If not, the sum may be anything else. Just something to keep in mind. If you\u2019re familiar with SQL databases, you might ask why Beancount doesn\u2019t simply process its data in order to fill up an existing database system, so that the user could then use those database\u2019s tools. There are two main reasons for this: Reporting Operations. In order to generate income statements and balance sheets, the list of transactions needs to be preprocessed using the clear, open and close operations described previously. These operations are not trivial to implement in database queries and are dependent on just the report and ideally don\u2019t need to modify the input data. We\u2019d have to load up the posting data into memory and then run some code. We\u2019re already doing that by parsing the input file; the database step would be superfluous. Aggregating Positions. Though we haven\u2019t discussed it in this document so far, the contents of accounts may contain different types of commodities, as well as positions with an attached cost basis. The way that these positions are aggregated together requires the implementation of a custom data type because it obeys some rules about how positions are able to cancel each other out (see How Inventories Work for details). It would be very difficult to build these operations with an SQL database beyond the context of using just a single currency and ignoring cost basis. This is why Beancount provides a custom tool to directly process and query its data: It provides its own implementation of an SQL client that lets you specify open and close dates and leverages a custom \u201cInventory\u201d data structure to create sums of the positions of postings. This tools supports columns of Beancount\u2019s core types: Amount, Position and Inventory objects. (In any case, if you\u2019re not convinced, Beancount provides a tool to export its contents to a regular SQL database system. Feel free to experiment with it if you like, knock yourself out.) Please don\u2019t pay attention to the numbers in these large figures, they were randomly generated and don\u2019t reflect this. We\u2019re just interested in showing the structure, in these figures. \u21a9 Note that this is unrelated to the term \u201cclearing transactions\u201d which means acknowledging or marking that some transactions have been eyeballed by the bookkeeper and checked for correctness. \u21a9 Note that operations have nothing to do with the Open and Close directives Beancount provides. \u21a9 This is not provided yet in Beancount, but would be trivial to implement. All we'd need to do is invert the signs of balances from Liabilities, Income and Equity accounts. It's on the roadmap to provide this eventually. \u21a9","title":"The Table Perspective"},{"location":"tracking_medical_claims.html","text":"Tracking Out-of-Network Medical Claims in Beancount \uf0c1 Martin Blais - Updated: November 2023 Let's illustrate how one might handle dealing with medical treatment with insurance and HSA claims. Let's use an example of psychotherapy sessions, received out-of-network and paid upfront out of pocket and reimbursed later. When a session is received, it is booked to receivables and payables: 2023-09-06 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-11 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD Later on, some payments are made for it, clearing the payables: 2023-09-12 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -520.00 USD Liabilities:AccountsPayable:Psychotherapy 520.00 USD And so on, for the entire month: 2023-09-20 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-23 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD 2023-09-27 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-28 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD At the end of the month, a claim form is produced by the therapist. We file the claim with the insurance company, clearing the receivable and shifting the remaining portion to a insurance company check to be issued: 2023-09-28 * \"Claim for September filed with insurance\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy -1040.00 USD Expenses:Mental:Psychotherapy 312.00 USD Assets:AccountsReceivable:Anthem 728.00 USD Eventually, an EOB is produced to confirm how much is covered (in this example, 70%, which was known from the terms) and a check is received and deposited at the bank: 2023-10-24 * \"ATM CHECK DEPOSIT\" #freud-2023-09 Assets:US:BofA:Checking 728.00 USD Assets:AccountsReceivable:Anthem -728.00 USD At this stage we know the amount of the remaining portion eligible to be paid from the HSA, so we file a claim to the HSA company, once again booking them to a receivable and a payable: 2023-10-25 * \"HealthEquity\" \"Filed for reimbursement\" #freud-2023-09 Liabilities:AccountsPayable:HealthEquity -312.00 USD Assets:AccountsReceivable:HealthEquity 312.00 USD The HSA company makes a direct deposit to our checking account: 2023-11-01 * \"BofA bank (Claim ID:1234567-890); EFT to bank\" #freud-2023-09 Assets:US:HealthEquity:Cash -312.00 USD Liabilities:AccountsPayable:HealthEquity 312.00 USD And on the bank side when we import this transaction we book it against the receivable: 2023-11-01 * \"HEALTHEQUITY INC\" #freud-2023-09 Assets:US:BofA:Checking 312.00 USD Assets:AccountsReceivable:HealthEquity -312.00 USD The final result is that the HSA was used to cover the uninsured portion of the cost. |-- Assets | |-- AccountsReceivable | | |-- Anthem | | |-- HealthEquity | | `-- Psychotherapy | `-- US | |-- HealthEquity | | `-- Cash -312.00 USD | `-- BofA | `-- Checking |-- Expenses | `-- Mental | `-- Psychotherapy 312.00 USD `-- Liabilities `-- AccountsPayable |-- HealthEquity `-- Psychotherapy Net Income: (-312.00 USD) There are some flaws with the approach above: The amount covered by insurance is known ahead of time; in many cases the amount is not known before the EOB is issued by the insurance (after all that's what it's for, it's the \"Explanation of Benefits\"). This would require modifying the above.","title":"Tracking Out-of-Network Medical Claims in Beancount"},{"location":"tracking_medical_claims.html#tracking-out-of-network-medical-claims-in-beancount","text":"Martin Blais - Updated: November 2023 Let's illustrate how one might handle dealing with medical treatment with insurance and HSA claims. Let's use an example of psychotherapy sessions, received out-of-network and paid upfront out of pocket and reimbursed later. When a session is received, it is booked to receivables and payables: 2023-09-06 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-11 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD Later on, some payments are made for it, clearing the payables: 2023-09-12 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -520.00 USD Liabilities:AccountsPayable:Psychotherapy 520.00 USD And so on, for the entire month: 2023-09-20 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-23 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD 2023-09-27 * \"Dr. Freud\" \"Session\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy 260.00 USD Liabilities:AccountsPayable:Psychotherapy -260.00 USD 2023-09-28 * \"ZELLE SENT\" #freud-2023-09 Assets:US:BofA:Checking -260.00 USD Liabilities:AccountsPayable:Psychotherapy 260.00 USD At the end of the month, a claim form is produced by the therapist. We file the claim with the insurance company, clearing the receivable and shifting the remaining portion to a insurance company check to be issued: 2023-09-28 * \"Claim for September filed with insurance\" #freud-2023-09 Assets:AccountsReceivable:Psychotherapy -1040.00 USD Expenses:Mental:Psychotherapy 312.00 USD Assets:AccountsReceivable:Anthem 728.00 USD Eventually, an EOB is produced to confirm how much is covered (in this example, 70%, which was known from the terms) and a check is received and deposited at the bank: 2023-10-24 * \"ATM CHECK DEPOSIT\" #freud-2023-09 Assets:US:BofA:Checking 728.00 USD Assets:AccountsReceivable:Anthem -728.00 USD At this stage we know the amount of the remaining portion eligible to be paid from the HSA, so we file a claim to the HSA company, once again booking them to a receivable and a payable: 2023-10-25 * \"HealthEquity\" \"Filed for reimbursement\" #freud-2023-09 Liabilities:AccountsPayable:HealthEquity -312.00 USD Assets:AccountsReceivable:HealthEquity 312.00 USD The HSA company makes a direct deposit to our checking account: 2023-11-01 * \"BofA bank (Claim ID:1234567-890); EFT to bank\" #freud-2023-09 Assets:US:HealthEquity:Cash -312.00 USD Liabilities:AccountsPayable:HealthEquity 312.00 USD And on the bank side when we import this transaction we book it against the receivable: 2023-11-01 * \"HEALTHEQUITY INC\" #freud-2023-09 Assets:US:BofA:Checking 312.00 USD Assets:AccountsReceivable:HealthEquity -312.00 USD The final result is that the HSA was used to cover the uninsured portion of the cost. |-- Assets | |-- AccountsReceivable | | |-- Anthem | | |-- HealthEquity | | `-- Psychotherapy | `-- US | |-- HealthEquity | | `-- Cash -312.00 USD | `-- BofA | `-- Checking |-- Expenses | `-- Mental | `-- Psychotherapy 312.00 USD `-- Liabilities `-- AccountsPayable |-- HealthEquity `-- Psychotherapy Net Income: (-312.00 USD) There are some flaws with the approach above: The amount covered by insurance is known ahead of time; in many cases the amount is not known before the EOB is issued by the insurance (after all that's what it's for, it's the \"Explanation of Benefits\"). This would require modifying the above.","title":"Tracking Out-of-Network Medical Claims in Beancount"},{"location":"trading_with_beancount.html","text":"Trading with Beancount \uf0c1 Martin Blais , July 2014 http://furius.ca/beancount/doc/trading Introduction What is Profit and Loss? Realized and Unrealized P/L Trade Lots Booking Methods Dated lots Reporting Unrealized P/L Commissions Stock Splits Cost Basis Adjustments Dividends Average Cost Booking Future Topics Introduction \uf0c1 This is a companion document for the Command-Line Accounting Cookbook that deals exclusively with the subject of trading and investments in Beancount. You probably should have read an introduction to the double-entry method before reading this document. The subject of stock trading needs to be preceded by a discussion of \u201cprofit and loss,\u201d or P/L, for short (pronounce: \u201cP and L\u201d), also called capital gains or losses. The notion of P/L against multiple trades can be difficult for a novice to understand, and I\u2019ve even seen professional traders lack sophistication in their understanding of P/L over varying time periods. It is worth spending a bit of time to explain this, and necessary to understand how to book your trades in a double-entry system. This discussion will be weaved with detailed examples of how to book these trades in Beancount, wherever possible. There is a related, active proposal for improving the booking methods in Beancount that you might also be interested in. Discussions of basis for tax-deferred accounts will not be treated here, but in the more general cookbook. What is Profit and Loss? \uf0c1 Let\u2019s imagine you have an account at the E*Trade discount broker and you buy some shares of a company, say IBM. If you buy 10 shares of IBM when its price is 160$/share, it will cost you 1600$. That value is what we will call the \u201cbook value\u201d, or equivalently, \u201cthe cost.\u201d This is how much money you had to spend in order to acquire the shares, also called \u201cthe position.\u201d This is how you would enter this transaction in Beancount: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1600.00 USD In practice you will probably pay some commission to E*Trade for this service, so let\u2019s put that in for completeness: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1609.95 USD Expenses:Financial:Commissions 9.95 USD This is how you tell Beancount to deposit some units \u201cat cost\u201d, in this case, units of \u201cIBM at 160 USD/share\u201d cost. This transaction balances because the sum of its legs is zero: 10 x 160 + -1609.95 + 9.95 = 0. Also note that we\u2019re choosing to use a subaccount dedicated to the shares of IBM; this is not strictly necessary but it is convenient for reporting in, for example, a balance sheet, because it will naturally aggregate all of your shares of each of your positions on their own line. Having a \u201ccash\u201d subaccount also emphasizes that uninvested funds you have there are not providing any return. The next day, the market opens and IBM shares are going for 170$/share. In this context, we will call this \u201cthe price.\u201d 1 The \u201cmarket value\u201d of your position, your shares, is the number of them x the market price, that is, 10 shares x 170$/share = 1700$. The difference between these two amounts is what we will call the P/L: market value - book value = P/L 10 x 170$ - 10 x 160$ = 1700$ - 1600$ = 100$ (profit) We will call a positive amount \u201ca profit\u201d and if the amount is negative, \u201ca loss.\u201d Realized and Unrealized P/L \uf0c1 The profit from the previous section is called an \u201cunrealized profit.\u201d That is because the shares have not actually been sold yet - this is a hypothetical profit: if I can sell those shares at the market value, this is how much I would pocket. The 100$ I mentioned in the previous section is actually an \u201cunrealized P/L.\u201d So let\u2019s say you like this unrealized profit and you feel that it\u2019s temporary luck that IBM went up. You decide to sell 3 of these 10 shares to the market at 170$/share. The profit on these share will now be \u201crealized\u201d: market value - book value = P/L 3 x 170$ - 3 x 160$ = 3 x (170 - 160) = 30$ (profit) This 30$ is a \u201crealized P/L.\u201d The remaining portion of your position is still showing an unrealized profit, that is, the price could fluctuate some more until you sell it: market value - book value = P/L 7 x 170$ - 7 x 160$ = 70$ This is how you would book this partial sale of your position in Beancount (again including a commission): 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Do you notice something funny going on here? -3 x 160 = -480, -480 + 500.05 + 9.95 = 30\u2026 This transaction does not balance to zero! The problem is that we received 510$ in cash in exchange for the 3 shares we sold. This is because the actual price we sold them at was 170$: 3 x 170 = 510$. This is where we need to account for the profit, by adding another leg which will absorb this profit, and conveniently enough, automatically calculate and track our profits for us: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL The last leg will be automatically filled in by Beancount to -30 USD , as we\u2019re allowed one posting without an amount (and remember that in the double-entry system without credits and debits, a profit is a negative number for \u201cIncome\u201d accounts). This is the number the government is interested in for your taxes. In summary, you now have: A position of 7 \u201cshares at book value of 160$\u201d = 1120$ (its book value) A realized P/L of 30$ An unrealized P/L of 70$ Now at this point, some of you will jump up and down and say: \u201cBut wait, waiiit! I sold at 170$/share, not 160$/share, why do you put 160$ here?\u201d The answer is that you did not have shares held at 170$ to sell. In order to explain this, I need to make a little detour to explain how we keep track of things in accounts... So how do we keep track of these shares? It\u2019s actually easy: when Beancount stores things in accounts, we use something called \u201can inventory.\u201d Imagine that an \u201cinventory\u201d is a bag with the name of that account on it. Each account has one such bag to hold the things in the account at a particular point in time, the \u201cbalance\u201d of this account at that time. Imagine that the things it contains have a little label attached to each of them, with their cost, that is, the price that was paid to acquire them. Whenever you put a thing in the bag, you attach a new label to the thing. For things to work right, all things need to be labeled 2 . In our example, the bag contained 10 items of \u201cshares of IBM bought at 160$/share\u201d. The syntax we used to put the IBM in the account can seem a little misleading; we wrote: Assets:US:ETrade:IBM 10 IBM {160.00 USD} but really, this is understood by Beancount closer to the following syntax: Assets:US:ETrade:IBM 10 {IBM 160.00 USD} But \u2026 it would be annoying to write this, so we use a syntax more intuitive to humans. So the thing is, you can\u2019t subtract units of {IBM at 170.00 USD} ... because there just aren\u2019t any in that bag. What you have in the bag are units of {IBM at 160.00 USD} . You can only take out these ones. Now that being said, do you see how it\u2019s the amount that was exchanged to us for the shares that really helps us track the P/L? Nowhere did we actually need to indicate the price at which we sold the shares. It\u2019s the fact that we received a certain amount of cash that is different than the cost of the position we\u2019re selling that triggers the imbalance, which we book to a capital gain. Hmmm\u2026 Beancount maintains a price database, wouldn\u2019t it be nice to at least record and attach that price to the transaction for documentation purposes? Indeed. Beancount allows you to also attach a price to that posting, but for the purpose of balancing the transaction, it ignores it completely. It is mainly there for documentation, and you can use it if you write scripts. And if you use the beancount.plugins.implicit_prices plugin, it will be used to automatically synthesize a price entry that will enrich our historical price database, which may be used in reporting the market value of the account contents (more details on this follow). So the complete and final transaction for selling those shares should be: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} @ 170.00 USD Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Trade Lots \uf0c1 In practice, the reality of trading gets a tiny bit more complicated than this. You might decide to buy some IBM multiple times, and each time, it is likely that you would buy them at a different price. Let\u2019s see how this works with another example trade. Given your previous position of 7 shares held at 160$ cost, the following day you see that the price went up some more, you change your mind on IBM and decide to \u201cgo long\u201d and buy 5 more shares. The price you get is 180$/share this time: 2014-02-18 * \"I put my chips on big blue!\" Assets:US:ETrade:IBM 5 IBM {180.00 USD} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD Now, what do we have in the bag for Assets:US:ETrade:IBM ? We have two kinds of things: 7 shares of \u201cIBM held at 160 USD/share\u201d, from the first trade 5 shares of \u201cIBM held at 180 USD/share\u201d, from this last trade We will call these \u201clots,\u201d or \u201ctrade lots.\u201d In fact, if you were to sell this entire position, say, a month later, the way to legally sell it in Beancount (that is, without issuing an error), is by specifying both legs. Say the price is 172$/share at that moment: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -7 IBM {160.00 USD} @ 172.00 USD Assets:US:ETrade:IBM -5 IBM {180.00 USD} Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Now your final position of IBM would be 0 shares. Alternatively, since you\u2019re selling the entire position, Beancount should be able to unambiguously match all the lots against an unspecified cost. This is equivalent: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -12 IBM {} @ 172.00 USD Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that this won\u2019t work if the total amount of shares doesn\u2019t match all the lots (this would be ambiguous\u2026 which subset of the lots should be chosen isn\u2019t obvious). Booking Methods \uf0c1 But what if you decided to sell only some of those shares? Say you need some cash to buy a gift to your loved one and you want to sell 4 shares this time. Say the price is now 175$/share. Now you have a choice to make. You can choose to sell the older shares and realize a larger profit: 2014-03-18 * \"Selling my older blue chips.\" Assets:US:ETrade:IBM -4 IBM {160.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; -60.00 USD (profit) Or you may choose to sell the most recently acquired ones and realize a loss: 2014-03-18 * \"Selling my most recent blue chips.\" Assets:US:ETrade:IBM -4 IBM {180.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; 20.00 USD (loss) Or you can choose to sell a mix of both: just use two legs. Note that in practice this choice will depend on a number of factors: The tax law of the jurisdiction where you trade the shares may have a defined method for how to book the shares and you may not actually have a choice. For example, they may state that you must trade the oldest lot you bought, a method called \u201cfirst-in-first out.\u201d If you have a choice, the various lots you\u2019re holding may have different taxation characteristics because you\u2019ve held them for a different period of time. In the USA, for example, positions held for more than one year benefit from a lower taxation rate (the \u201clong-term\u201d capital gains rate). You may have other gains or losses that you want to offset in order to minimize your cash flow requirements on your tax liability. This is sometimes called \u201c tax loss harvesting .\u201d There are more\u2026 but I\u2019m not going to elaborate on them here. My goal is to show you how to book these things with the double-entry method. Dated lots \uf0c1 We\u2019ve almost completed the whole picture of how this works. There is one more rather technical detail to add and it begins with a question: What if I bought multiple lots of share at the same price? As we alluded to in the previous section, the duration for which you held a position may have an impact on your taxation, even if the P/L ends up being the same. How do we differentiate between these lots? Well\u2026 I had simplified things a tiny bit earlier, just to make it simpler to understand. When we put positions in an inventory, on the label that we attach to the things we put in it, we also mark down the date that lot was acquired if you supply it. This is how you would book entering the position this way: 2014-05-20 * \"First trade\" Assets:US:ETrade:IBM 5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD 2014-05-21 * \"Second trade\" Assets:US:ETrade:IBM 3 IBM {180.00 USD, 2014-05-21} Assets:US:ETrade:Cash -549.95 USD Expenses:Financial:Commissions 9.95 USD Now when you sell, you can do the same thing to disambiguate which lot\u2019s position you want to reduce: 2014-08-04 * \"Selling off first trade\" Assets:US:ETrade:IBM -5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash 815.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that it\u2019s really unlikely that your broker will provide the information in the downloadable CSV or OFX files from their website\u2026 you probably won\u2019t be able to automate the lot detail of this transaction, you might have to pick up the PDF trade confirmations your broker provides to enter this manually, if it ever happens. But how often does it happen that you buy two lots at the same price? I trade relatively frequently - about every two weeks - and in 8 years worth of data I don\u2019t have a single occurrence of it. In practice, unless you do thousands of trades per day- and Beancount isn\u2019t really designed to handle that kind of activity, at least not in the most efficient way - it just won\u2019t happen very much. ( Technical Detail : that we\u2019re working on bettering the mechanism for lot selection so that you never have to insert the lot-date yourself, and so that you could disambiguate lot selection by supplying a name instead. See upcoming changes.) Reporting Unrealized P/L \uf0c1 Okay, so our account balances are holding the cost of each unit, and that provides us with the book value of these positions. Nice. But what about viewing the market value? The market value of the positions is simply the number of units of these instruments x the market price at the time we\u2019re interested in. This price fluctuates. So we need the price. Beancount supports a type of entry called a price entry that allows you to tell it what the price of an instrument was at a particular point in time, e.g. 2014-05-25 price IBM 182.27 USD In order to keep Beancount simple and with few dependencies, the software does not automatically fetch these prices (you can check out LedgerHub for this purpose, or write your own script that will insert the latest prices in your input file if so desired\u2026 there are many libraries to fetch prices from the internet online). It only knows about market prices from all these price entries. Using these, it builds an in-memory historical database of prices over time and can query it to obtain the most current values. Instead of supporting different reporting modes with options, you can trigger the insertion of unrealized gains by enabling a plugin: plugin \"beancount.plugins.unrealized\" \"Unrealized\" This will create a synthetic transaction at the date of the last of directives, that reflects the unrealized P/L. It books one side as Income and the other side as a change in Asset: 2014-05-25 U \"Unrealized gain for 7 units of IBM (price: 182.2700 USD as of 2014-05-25, average cost: 160.0000 USD)\" Assets:US:ETrade:IBM:Unrealized 155.89 USD Income:US:ETrade:IBM:Unrealized -155.89 USD Note that I used an option in this example to specify a sub-account to book the unrealized gains to. The unrealized P/L shows up on a separate line in the balance sheet and the parent account should show the market value on its balance (which includes that of its sub-accounts). Commissions \uf0c1 So far we have not discussed trading commissions. Depending on the tax law that applies to you, the costs associated with trading may be deductible from the raw capital gain as we\u2019ve calculated it in the previous examples. These are considered expenses by the government, and it is often the case that you can deduct those trading commissions (it\u2019s entirely reasonable from their part, you did not pocket that money after all). In the examples above, the capital gains and commission expenses get tracked into two separate accounts. For example, you could end up with reported balances that look like this: Income:US:ETrade:PnL -645.02 USD Expenses:Financial:Commissions 39.80 USD (Just to be clear, this is to be interpreted as a profit of $645.02 and an expense of $39.80.) You could subtract these numbers to obtain an approximation of the P/L without costs: 645.02 - 39.80 = $605.22. However, this is only an approximation of the correct P/L value. To understand why, we need to look at an example where a partial number of shares are sold across a reporting period. Imagine that we have an account with a commission rate of $10 per trade, 100 shares of ITOT were bought in 2013, 40 of those shares were later sold in that same year, and the remaining 60 were sold the year after, a scenario that looks like this: 2013-09-01 Buy 100 ITOT at $80, commission = 10$ 2013-11-01 Sell 40 ITOT at $82, commission = 10$ 2014-02-01 Sell 60 ITOT at $84, commission = 10$ If you computed the sum of commissions paid at the end of 2013, you would have $20, and using the approximate method outlined previously, for so 2013 and 2014 you would declare 2013: P/L of 40 x ($82 - $80) - ($10 + $10) = $60 2014: P/L of 60 x ($84 - $80) - $10 = $230 However, strictly speaking, this is incorrect. The $10 commission paid on acquiring the 100 shares has to be pro-rated with respect to the number of shares sold. This means that on that first sale of 40 shares only 4$ of the commission is deductible: $10 x (40 shares / 100 shares), and so we obtain: 2013: P/L of 40 x ($82 - $80) - $(4 + 10) = $66 2014: P/L of 60 x ($84 - $80) - $(6 + 10) = $224 As you can see, the P/L declared for each year differs, even if the sum of the P/L for both years is the same ($290). A convenient method to automatically allocate the acquisition costs to the pro-rata value of the number of shares sold is to add the acquisition trading cost to the total book value of the position. In this example, you would say that the position of 100 shares has a book value $8010 instead of $8000: 100 share x $80/share + $10, or equivalently, that the individual shares have a book value of $80.10 each. This would result in the following calculation: 2013: P/L of 40 x ($82 - $80.10) - $10 = $66 2014: P/L of 60 x ($84 - $80.10) - $10 = $224 You could even go one step further and fold the commission on sale into the price of each share sold as well: 2013: P/L of 40 x ($81.75 - $80.10) = $66 2014: P/L of 60 x ($83.8333 - $80.10) = $224 This may seem overkill, but imagine that those costs were much higher, as is the case on large commercial transactions; the details do begin to matter to the tax man. Accurate accounting is important, and we need to develop a method to do this more precisely. We don\u2019t currently have a good method of doing this with our input syntax. A suitable method is currently being developed and a proposal is on the table. Also see mailing-list for details. [June 2014] Stock Splits \uf0c1 Stock splits are currently dealt with by emptying an account\u2019s positions and recreating the positions at a different price: 2004-12-21 * \"Autodesk stock splits\" Assets:US:MSSB:ADSK -100 ADSK {66.30 USD} Assets:US:MSSB:ADSK 200 ADSK {33.15 USD} The postings balance each other, so the rule is respected. As you can see, this requires no special syntax feature. It also handles more general scenarios, such as the odd split of the Google company that occurred on the NASDAQ exchange in April 2014, into two different classes of stock (voting and non-voting shares, at 50.08% and 49.92%, respectively): 2014-04-07 * \"Stock splits into voting and non-voting shares\" Assets:US:MSSB:GOOG -25 GOOG {1212.51 USD} ; Old GOOG Assets:US:MSSB:GOOG 25 GOOG { 605.2850 USD} ; New GOOG Assets:US:MSSB:GOOGL 25 GOOG { 607.2250 USD} Ultimately, maybe a plug-in module should be provided to more easily create such stock split transactions, as there is some amount of redundancy involved. We need to figure out the most general way to do this. But the above will work for now. One problem with this approach is that the continuity of the trade lots is lost, that is, the purchase date of each lot has now been reset as a result of the transaction above, and it becomes impossible to automatically figure out the duration of the trade and its associated impact on taxation, i.e. long-term vs. short-term trade. Even without this the profit is still calculated correctly, but it is an annoying detail nonetheless. One way to handle this is by using the Dated Lots (see the appropriate section of this doc). That way, the original trade date can be preserved on the new lots. This provides accurate timing information in addition to the capital gain/loss based on the price. Another method for solving this and for easily propagating the lot trade date has been proposed and will be implemented in Beancount later on. A more important problem with the current implementation is that the meaning of a unit of ADSK before and after the stock split is different. The price graph for this commodity unit will show a radical discontinuity! This is a more general problem that has yet to be addressed in both Beancount and Ledger. The Commodity Definition Changes document has a discussion to address this topic. Cost Basis Adjustment and Return of Capital \uf0c1 Readjustment in cost basis may occur in managed funds, due to the fund\u2019s internal trading activities. This will typically occur in tax-sheltered accounts where the gain that occurs from such an adjustment has no impact on taxes, and where the cost basis is held at the average cost of all shares in each position. If we have the specific lot prices being adjusted, it is doable to book these in the same manner as we dealt with stock splits: 2014-04-07 * \"Cost basis adjustment for XSP\" Assets:CA:RRSP:XSP -100 ADSK {21.10 CAD} Assets:CA:RRSP:XSP 100 ADSK {23.40 CAD} Income:CA:RRSP:Gains -230.00 CAD However, this is really uncommon. The more common case of this is of an account using the average cost booking method, we don\u2019t currently have a way to deal with this. There is an active proposal in place to make this possible. The cost basis adjustment is commonly found in Return of Capital events. These happen, for example, when funds are returning capital to the shareholders. This can be caused by winding down the operation. From the taxation point of view, these are non-taxable events and affect the cost basis of the equity in the fund. The number of shares might stay the same, but their cost basis needs to be adjusted for potential Gain/Loss calculation at the point of sale in the future. Dividends \uf0c1 Dividends don\u2019t pose a particular problem. They are just income. They can be received as cash: 2014-02-01 * \"Cash dividends received from mutual fund RBF1005\" Assets:Investments:Cash 171.02 CAD Income:Investments:Dividends Or they can be received as stock itself: 2014-02-01 * \"Stock dividends received in shares\" Assets:Investments:RBF1005 7.234 RBF1005 {23.64 CAD} Income:Investments:Dividends In the case of dividends received as stock, as for stock purchases, you provide the cost basis at which the dividend was received (this should be available in your statements). If the account is held at average cost, this posting will simply merge with the other legs at the time an average cost booking is needed to be performed. Average Cost Booking \uf0c1 At the moment, the only way to perform booking at average cost is painful: you would have to use the method outlined in the Stock Split section in order to revalue your inventory. This is impractical, however. There is an active proposal with an associated syntax to fully solve this problem. Once the proposal is implemented, it will look like this: 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 GOOG {*} Assets:Investments:Cash 2740.05 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains Any posting with a cost of \u201c*\u201d acting on an inventory will select all the shares of that currency (GOOG), merge them into a single one at the average cost, and then reduce that position at this new average cost. Future Topics \uf0c1 I\u2019ll be handling the following topics later on: Mark-to-Market : Handling end-of-year mark-to-market for Section 1256 instruments (i.e., futures and options), by re-evaluating the cost basis. This is similar to a cost basis readjustment applied at the end of each year for all of these types of instruments. Short Sales : these require little changes. We just have to allow negative numbers of units held at cost. At the moment we spit a warning when units held at cost go negative in order to detect data entry errors, but it would be easy to extend the Open directive syntax to allow this to occur on specific accounts which can hold short sales, which should just show as negative shares. All the arithmetic should otherwise just work naturally. Interest payments on margins would show up as distinct transactions. Also, when you short the stock, you don\u2019t receive dividends for those positions, but rather you have to pay them out. You would have expense account for this, e.g., Expenses:StockLoans:Dividends . Trading Options : I have no idea how to do this at the moment, but I imagine these could be held like shares of stock, with no distinctions. I don\u2019t foresee any difficulty. Currency Trading : At the moment, I\u2019m not accounting for the positions in my FOREX accounts, just their P/L and interest payments. This poses interesting problems: Positions held in a FOREX account aren\u2019t just long or short the way that stocks are: they are actually offsetting two commodities at the same time. For example, a long position in USD/CAD should increase the exposure of USD and decrease the exposure in CAD, it can be seen as holding a long asset of USD and a short asset in CAD, at the same time. While it is possible to hold these positions as if they were distinct instruments (e.g., units of \u201cUSDCAD\u201d with disregard for its components) but for large positions, especially if held over long periods of time for hedging purposes, it is important to deal with this and somehow allow the user to reflect the net currency exposures of multiple currency positions against the rest of their assets and liabilities. We also need to deal with the gains generated by the closing of these positions: those generate a gain in the currency of the account, after conversion to this currency. For example, if you hold a currency account denominated in USD, and you go long EUR/JPY, when you close the position you will obtain a gain in EUR, and after conversion of the P/L from the EUR into the equivalent number of USD (via EUR/USD) the USD gain will be deposited in your account. This means that two rates are being used to estimate the current market value of any position: the differential between the current rate and the rate at the time of buying, and the rate of the base currency (e.g., EUR) in the account currency (e.g., USD). Some of these involve new features in Beancount, but some not. Ideas welcome. This is a misleading notion, however. In reality, there is no price , there exist only markets where you get a \u201chint\u201d of how much someone else might be willing to exchange your shares for (for different amounts to buy or to sell them, and for some limited number of them, we call this \u201ca market\u201d), but until you\u2019ve actually completed selling your shares, you don\u2019t really know precisely how much you will be able to execute that trade at, only an estimate. If you\u2019re not intimately familiar with trading, this should give you pause, and hopefully a big \u201cah-ha! \u201cmoment about how the world works - there really does not exist a price for anything in the world - but in the context of this discussion, let\u2019s make abstraction of this and assume that you can buy or sell as many shares as you have instantly on the markets at the middle price, such as Google Finance or Yahoo Finance or your broker would report it. The process of deciding that we would be able to sell the shares at 170$ each is called \u201cmarking\u201d, that is, under reasonable assumptions, we believe that we would be able to actually sell those shares at that price (the term of art \u201cmarking to market\u201d refers to the fact that we use the market price as the best indicator to our knowledge of our capability to realize the trade). For an individual buying and selling small amounts of shares that don\u2019t move the market and with an honest broker, this is mostly true in practice. \u21a9 As an aside, putting regular currencies in an account is just the degenerate case of a thing with an empty label. This is an implementation detail that works great in practice. \u21a9","title":"Trading with Beancount"},{"location":"trading_with_beancount.html#trading-with-beancount","text":"Martin Blais , July 2014 http://furius.ca/beancount/doc/trading Introduction What is Profit and Loss? Realized and Unrealized P/L Trade Lots Booking Methods Dated lots Reporting Unrealized P/L Commissions Stock Splits Cost Basis Adjustments Dividends Average Cost Booking Future Topics","title":"Trading with Beancount"},{"location":"trading_with_beancount.html#introduction","text":"This is a companion document for the Command-Line Accounting Cookbook that deals exclusively with the subject of trading and investments in Beancount. You probably should have read an introduction to the double-entry method before reading this document. The subject of stock trading needs to be preceded by a discussion of \u201cprofit and loss,\u201d or P/L, for short (pronounce: \u201cP and L\u201d), also called capital gains or losses. The notion of P/L against multiple trades can be difficult for a novice to understand, and I\u2019ve even seen professional traders lack sophistication in their understanding of P/L over varying time periods. It is worth spending a bit of time to explain this, and necessary to understand how to book your trades in a double-entry system. This discussion will be weaved with detailed examples of how to book these trades in Beancount, wherever possible. There is a related, active proposal for improving the booking methods in Beancount that you might also be interested in. Discussions of basis for tax-deferred accounts will not be treated here, but in the more general cookbook.","title":"Introduction"},{"location":"trading_with_beancount.html#what-is-profit-and-loss","text":"Let\u2019s imagine you have an account at the E*Trade discount broker and you buy some shares of a company, say IBM. If you buy 10 shares of IBM when its price is 160$/share, it will cost you 1600$. That value is what we will call the \u201cbook value\u201d, or equivalently, \u201cthe cost.\u201d This is how much money you had to spend in order to acquire the shares, also called \u201cthe position.\u201d This is how you would enter this transaction in Beancount: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1600.00 USD In practice you will probably pay some commission to E*Trade for this service, so let\u2019s put that in for completeness: 2014-02-16 * \"Buying some IBM\" Assets:US:ETrade:IBM 10 IBM {160.00 USD} Assets:US:ETrade:Cash -1609.95 USD Expenses:Financial:Commissions 9.95 USD This is how you tell Beancount to deposit some units \u201cat cost\u201d, in this case, units of \u201cIBM at 160 USD/share\u201d cost. This transaction balances because the sum of its legs is zero: 10 x 160 + -1609.95 + 9.95 = 0. Also note that we\u2019re choosing to use a subaccount dedicated to the shares of IBM; this is not strictly necessary but it is convenient for reporting in, for example, a balance sheet, because it will naturally aggregate all of your shares of each of your positions on their own line. Having a \u201ccash\u201d subaccount also emphasizes that uninvested funds you have there are not providing any return. The next day, the market opens and IBM shares are going for 170$/share. In this context, we will call this \u201cthe price.\u201d 1 The \u201cmarket value\u201d of your position, your shares, is the number of them x the market price, that is, 10 shares x 170$/share = 1700$. The difference between these two amounts is what we will call the P/L: market value - book value = P/L 10 x 170$ - 10 x 160$ = 1700$ - 1600$ = 100$ (profit) We will call a positive amount \u201ca profit\u201d and if the amount is negative, \u201ca loss.\u201d","title":"What is Profit and Loss?"},{"location":"trading_with_beancount.html#realized-and-unrealized-pl","text":"The profit from the previous section is called an \u201cunrealized profit.\u201d That is because the shares have not actually been sold yet - this is a hypothetical profit: if I can sell those shares at the market value, this is how much I would pocket. The 100$ I mentioned in the previous section is actually an \u201cunrealized P/L.\u201d So let\u2019s say you like this unrealized profit and you feel that it\u2019s temporary luck that IBM went up. You decide to sell 3 of these 10 shares to the market at 170$/share. The profit on these share will now be \u201crealized\u201d: market value - book value = P/L 3 x 170$ - 3 x 160$ = 3 x (170 - 160) = 30$ (profit) This 30$ is a \u201crealized P/L.\u201d The remaining portion of your position is still showing an unrealized profit, that is, the price could fluctuate some more until you sell it: market value - book value = P/L 7 x 170$ - 7 x 160$ = 70$ This is how you would book this partial sale of your position in Beancount (again including a commission): 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Do you notice something funny going on here? -3 x 160 = -480, -480 + 500.05 + 9.95 = 30\u2026 This transaction does not balance to zero! The problem is that we received 510$ in cash in exchange for the 3 shares we sold. This is because the actual price we sold them at was 170$: 3 x 170 = 510$. This is where we need to account for the profit, by adding another leg which will absorb this profit, and conveniently enough, automatically calculate and track our profits for us: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL The last leg will be automatically filled in by Beancount to -30 USD , as we\u2019re allowed one posting without an amount (and remember that in the double-entry system without credits and debits, a profit is a negative number for \u201cIncome\u201d accounts). This is the number the government is interested in for your taxes. In summary, you now have: A position of 7 \u201cshares at book value of 160$\u201d = 1120$ (its book value) A realized P/L of 30$ An unrealized P/L of 70$ Now at this point, some of you will jump up and down and say: \u201cBut wait, waiiit! I sold at 170$/share, not 160$/share, why do you put 160$ here?\u201d The answer is that you did not have shares held at 170$ to sell. In order to explain this, I need to make a little detour to explain how we keep track of things in accounts... So how do we keep track of these shares? It\u2019s actually easy: when Beancount stores things in accounts, we use something called \u201can inventory.\u201d Imagine that an \u201cinventory\u201d is a bag with the name of that account on it. Each account has one such bag to hold the things in the account at a particular point in time, the \u201cbalance\u201d of this account at that time. Imagine that the things it contains have a little label attached to each of them, with their cost, that is, the price that was paid to acquire them. Whenever you put a thing in the bag, you attach a new label to the thing. For things to work right, all things need to be labeled 2 . In our example, the bag contained 10 items of \u201cshares of IBM bought at 160$/share\u201d. The syntax we used to put the IBM in the account can seem a little misleading; we wrote: Assets:US:ETrade:IBM 10 IBM {160.00 USD} but really, this is understood by Beancount closer to the following syntax: Assets:US:ETrade:IBM 10 {IBM 160.00 USD} But \u2026 it would be annoying to write this, so we use a syntax more intuitive to humans. So the thing is, you can\u2019t subtract units of {IBM at 170.00 USD} ... because there just aren\u2019t any in that bag. What you have in the bag are units of {IBM at 160.00 USD} . You can only take out these ones. Now that being said, do you see how it\u2019s the amount that was exchanged to us for the shares that really helps us track the P/L? Nowhere did we actually need to indicate the price at which we sold the shares. It\u2019s the fact that we received a certain amount of cash that is different than the cost of the position we\u2019re selling that triggers the imbalance, which we book to a capital gain. Hmmm\u2026 Beancount maintains a price database, wouldn\u2019t it be nice to at least record and attach that price to the transaction for documentation purposes? Indeed. Beancount allows you to also attach a price to that posting, but for the purpose of balancing the transaction, it ignores it completely. It is mainly there for documentation, and you can use it if you write scripts. And if you use the beancount.plugins.implicit_prices plugin, it will be used to automatically synthesize a price entry that will enrich our historical price database, which may be used in reporting the market value of the account contents (more details on this follow). So the complete and final transaction for selling those shares should be: 2014-02-17 * \"Selling some IBM\" Assets:US:ETrade:IBM -3 IBM {160.00 USD} @ 170.00 USD Assets:US:ETrade:Cash 500.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL","title":"Realized and Unrealized P/L"},{"location":"trading_with_beancount.html#trade-lots","text":"In practice, the reality of trading gets a tiny bit more complicated than this. You might decide to buy some IBM multiple times, and each time, it is likely that you would buy them at a different price. Let\u2019s see how this works with another example trade. Given your previous position of 7 shares held at 160$ cost, the following day you see that the price went up some more, you change your mind on IBM and decide to \u201cgo long\u201d and buy 5 more shares. The price you get is 180$/share this time: 2014-02-18 * \"I put my chips on big blue!\" Assets:US:ETrade:IBM 5 IBM {180.00 USD} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD Now, what do we have in the bag for Assets:US:ETrade:IBM ? We have two kinds of things: 7 shares of \u201cIBM held at 160 USD/share\u201d, from the first trade 5 shares of \u201cIBM held at 180 USD/share\u201d, from this last trade We will call these \u201clots,\u201d or \u201ctrade lots.\u201d In fact, if you were to sell this entire position, say, a month later, the way to legally sell it in Beancount (that is, without issuing an error), is by specifying both legs. Say the price is 172$/share at that moment: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -7 IBM {160.00 USD} @ 172.00 USD Assets:US:ETrade:IBM -5 IBM {180.00 USD} Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Now your final position of IBM would be 0 shares. Alternatively, since you\u2019re selling the entire position, Beancount should be able to unambiguously match all the lots against an unspecified cost. This is equivalent: 2014-03-18 * \"Selling all my blue chips.\" Assets:US:ETrade:IBM -12 IBM {} @ 172.00 USD Assets:US:ETrade:Cash 2054.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that this won\u2019t work if the total amount of shares doesn\u2019t match all the lots (this would be ambiguous\u2026 which subset of the lots should be chosen isn\u2019t obvious).","title":"Trade Lots"},{"location":"trading_with_beancount.html#booking-methods","text":"But what if you decided to sell only some of those shares? Say you need some cash to buy a gift to your loved one and you want to sell 4 shares this time. Say the price is now 175$/share. Now you have a choice to make. You can choose to sell the older shares and realize a larger profit: 2014-03-18 * \"Selling my older blue chips.\" Assets:US:ETrade:IBM -4 IBM {160.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; -60.00 USD (profit) Or you may choose to sell the most recently acquired ones and realize a loss: 2014-03-18 * \"Selling my most recent blue chips.\" Assets:US:ETrade:IBM -4 IBM {180.00 USD} @ 175.00 USD Assets:US:ETrade:Cash 690.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL ;; 20.00 USD (loss) Or you can choose to sell a mix of both: just use two legs. Note that in practice this choice will depend on a number of factors: The tax law of the jurisdiction where you trade the shares may have a defined method for how to book the shares and you may not actually have a choice. For example, they may state that you must trade the oldest lot you bought, a method called \u201cfirst-in-first out.\u201d If you have a choice, the various lots you\u2019re holding may have different taxation characteristics because you\u2019ve held them for a different period of time. In the USA, for example, positions held for more than one year benefit from a lower taxation rate (the \u201clong-term\u201d capital gains rate). You may have other gains or losses that you want to offset in order to minimize your cash flow requirements on your tax liability. This is sometimes called \u201c tax loss harvesting .\u201d There are more\u2026 but I\u2019m not going to elaborate on them here. My goal is to show you how to book these things with the double-entry method.","title":"Booking Methods"},{"location":"trading_with_beancount.html#dated-lots","text":"We\u2019ve almost completed the whole picture of how this works. There is one more rather technical detail to add and it begins with a question: What if I bought multiple lots of share at the same price? As we alluded to in the previous section, the duration for which you held a position may have an impact on your taxation, even if the P/L ends up being the same. How do we differentiate between these lots? Well\u2026 I had simplified things a tiny bit earlier, just to make it simpler to understand. When we put positions in an inventory, on the label that we attach to the things we put in it, we also mark down the date that lot was acquired if you supply it. This is how you would book entering the position this way: 2014-05-20 * \"First trade\" Assets:US:ETrade:IBM 5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash -909.95 USD Expenses:Financial:Commissions 9.95 USD 2014-05-21 * \"Second trade\" Assets:US:ETrade:IBM 3 IBM {180.00 USD, 2014-05-21} Assets:US:ETrade:Cash -549.95 USD Expenses:Financial:Commissions 9.95 USD Now when you sell, you can do the same thing to disambiguate which lot\u2019s position you want to reduce: 2014-08-04 * \"Selling off first trade\" Assets:US:ETrade:IBM -5 IBM {180.00 USD, 2014-05-20} Assets:US:ETrade:Cash 815.05 USD Expenses:Financial:Commissions 9.95 USD Income:US:ETrade:PnL Note that it\u2019s really unlikely that your broker will provide the information in the downloadable CSV or OFX files from their website\u2026 you probably won\u2019t be able to automate the lot detail of this transaction, you might have to pick up the PDF trade confirmations your broker provides to enter this manually, if it ever happens. But how often does it happen that you buy two lots at the same price? I trade relatively frequently - about every two weeks - and in 8 years worth of data I don\u2019t have a single occurrence of it. In practice, unless you do thousands of trades per day- and Beancount isn\u2019t really designed to handle that kind of activity, at least not in the most efficient way - it just won\u2019t happen very much. ( Technical Detail : that we\u2019re working on bettering the mechanism for lot selection so that you never have to insert the lot-date yourself, and so that you could disambiguate lot selection by supplying a name instead. See upcoming changes.)","title":"Dated lots"},{"location":"trading_with_beancount.html#reporting-unrealized-pl","text":"Okay, so our account balances are holding the cost of each unit, and that provides us with the book value of these positions. Nice. But what about viewing the market value? The market value of the positions is simply the number of units of these instruments x the market price at the time we\u2019re interested in. This price fluctuates. So we need the price. Beancount supports a type of entry called a price entry that allows you to tell it what the price of an instrument was at a particular point in time, e.g. 2014-05-25 price IBM 182.27 USD In order to keep Beancount simple and with few dependencies, the software does not automatically fetch these prices (you can check out LedgerHub for this purpose, or write your own script that will insert the latest prices in your input file if so desired\u2026 there are many libraries to fetch prices from the internet online). It only knows about market prices from all these price entries. Using these, it builds an in-memory historical database of prices over time and can query it to obtain the most current values. Instead of supporting different reporting modes with options, you can trigger the insertion of unrealized gains by enabling a plugin: plugin \"beancount.plugins.unrealized\" \"Unrealized\" This will create a synthetic transaction at the date of the last of directives, that reflects the unrealized P/L. It books one side as Income and the other side as a change in Asset: 2014-05-25 U \"Unrealized gain for 7 units of IBM (price: 182.2700 USD as of 2014-05-25, average cost: 160.0000 USD)\" Assets:US:ETrade:IBM:Unrealized 155.89 USD Income:US:ETrade:IBM:Unrealized -155.89 USD Note that I used an option in this example to specify a sub-account to book the unrealized gains to. The unrealized P/L shows up on a separate line in the balance sheet and the parent account should show the market value on its balance (which includes that of its sub-accounts).","title":"Reporting Unrealized P/L"},{"location":"trading_with_beancount.html#commissions","text":"So far we have not discussed trading commissions. Depending on the tax law that applies to you, the costs associated with trading may be deductible from the raw capital gain as we\u2019ve calculated it in the previous examples. These are considered expenses by the government, and it is often the case that you can deduct those trading commissions (it\u2019s entirely reasonable from their part, you did not pocket that money after all). In the examples above, the capital gains and commission expenses get tracked into two separate accounts. For example, you could end up with reported balances that look like this: Income:US:ETrade:PnL -645.02 USD Expenses:Financial:Commissions 39.80 USD (Just to be clear, this is to be interpreted as a profit of $645.02 and an expense of $39.80.) You could subtract these numbers to obtain an approximation of the P/L without costs: 645.02 - 39.80 = $605.22. However, this is only an approximation of the correct P/L value. To understand why, we need to look at an example where a partial number of shares are sold across a reporting period. Imagine that we have an account with a commission rate of $10 per trade, 100 shares of ITOT were bought in 2013, 40 of those shares were later sold in that same year, and the remaining 60 were sold the year after, a scenario that looks like this: 2013-09-01 Buy 100 ITOT at $80, commission = 10$ 2013-11-01 Sell 40 ITOT at $82, commission = 10$ 2014-02-01 Sell 60 ITOT at $84, commission = 10$ If you computed the sum of commissions paid at the end of 2013, you would have $20, and using the approximate method outlined previously, for so 2013 and 2014 you would declare 2013: P/L of 40 x ($82 - $80) - ($10 + $10) = $60 2014: P/L of 60 x ($84 - $80) - $10 = $230 However, strictly speaking, this is incorrect. The $10 commission paid on acquiring the 100 shares has to be pro-rated with respect to the number of shares sold. This means that on that first sale of 40 shares only 4$ of the commission is deductible: $10 x (40 shares / 100 shares), and so we obtain: 2013: P/L of 40 x ($82 - $80) - $(4 + 10) = $66 2014: P/L of 60 x ($84 - $80) - $(6 + 10) = $224 As you can see, the P/L declared for each year differs, even if the sum of the P/L for both years is the same ($290). A convenient method to automatically allocate the acquisition costs to the pro-rata value of the number of shares sold is to add the acquisition trading cost to the total book value of the position. In this example, you would say that the position of 100 shares has a book value $8010 instead of $8000: 100 share x $80/share + $10, or equivalently, that the individual shares have a book value of $80.10 each. This would result in the following calculation: 2013: P/L of 40 x ($82 - $80.10) - $10 = $66 2014: P/L of 60 x ($84 - $80.10) - $10 = $224 You could even go one step further and fold the commission on sale into the price of each share sold as well: 2013: P/L of 40 x ($81.75 - $80.10) = $66 2014: P/L of 60 x ($83.8333 - $80.10) = $224 This may seem overkill, but imagine that those costs were much higher, as is the case on large commercial transactions; the details do begin to matter to the tax man. Accurate accounting is important, and we need to develop a method to do this more precisely. We don\u2019t currently have a good method of doing this with our input syntax. A suitable method is currently being developed and a proposal is on the table. Also see mailing-list for details. [June 2014]","title":"Commissions"},{"location":"trading_with_beancount.html#stock-splits","text":"Stock splits are currently dealt with by emptying an account\u2019s positions and recreating the positions at a different price: 2004-12-21 * \"Autodesk stock splits\" Assets:US:MSSB:ADSK -100 ADSK {66.30 USD} Assets:US:MSSB:ADSK 200 ADSK {33.15 USD} The postings balance each other, so the rule is respected. As you can see, this requires no special syntax feature. It also handles more general scenarios, such as the odd split of the Google company that occurred on the NASDAQ exchange in April 2014, into two different classes of stock (voting and non-voting shares, at 50.08% and 49.92%, respectively): 2014-04-07 * \"Stock splits into voting and non-voting shares\" Assets:US:MSSB:GOOG -25 GOOG {1212.51 USD} ; Old GOOG Assets:US:MSSB:GOOG 25 GOOG { 605.2850 USD} ; New GOOG Assets:US:MSSB:GOOGL 25 GOOG { 607.2250 USD} Ultimately, maybe a plug-in module should be provided to more easily create such stock split transactions, as there is some amount of redundancy involved. We need to figure out the most general way to do this. But the above will work for now. One problem with this approach is that the continuity of the trade lots is lost, that is, the purchase date of each lot has now been reset as a result of the transaction above, and it becomes impossible to automatically figure out the duration of the trade and its associated impact on taxation, i.e. long-term vs. short-term trade. Even without this the profit is still calculated correctly, but it is an annoying detail nonetheless. One way to handle this is by using the Dated Lots (see the appropriate section of this doc). That way, the original trade date can be preserved on the new lots. This provides accurate timing information in addition to the capital gain/loss based on the price. Another method for solving this and for easily propagating the lot trade date has been proposed and will be implemented in Beancount later on. A more important problem with the current implementation is that the meaning of a unit of ADSK before and after the stock split is different. The price graph for this commodity unit will show a radical discontinuity! This is a more general problem that has yet to be addressed in both Beancount and Ledger. The Commodity Definition Changes document has a discussion to address this topic.","title":"Stock Splits"},{"location":"trading_with_beancount.html#cost-basis-adjustment-and-return-of-capital","text":"Readjustment in cost basis may occur in managed funds, due to the fund\u2019s internal trading activities. This will typically occur in tax-sheltered accounts where the gain that occurs from such an adjustment has no impact on taxes, and where the cost basis is held at the average cost of all shares in each position. If we have the specific lot prices being adjusted, it is doable to book these in the same manner as we dealt with stock splits: 2014-04-07 * \"Cost basis adjustment for XSP\" Assets:CA:RRSP:XSP -100 ADSK {21.10 CAD} Assets:CA:RRSP:XSP 100 ADSK {23.40 CAD} Income:CA:RRSP:Gains -230.00 CAD However, this is really uncommon. The more common case of this is of an account using the average cost booking method, we don\u2019t currently have a way to deal with this. There is an active proposal in place to make this possible. The cost basis adjustment is commonly found in Return of Capital events. These happen, for example, when funds are returning capital to the shareholders. This can be caused by winding down the operation. From the taxation point of view, these are non-taxable events and affect the cost basis of the equity in the fund. The number of shares might stay the same, but their cost basis needs to be adjusted for potential Gain/Loss calculation at the point of sale in the future.","title":"Cost Basis Adjustment and Return of Capital"},{"location":"trading_with_beancount.html#dividends","text":"Dividends don\u2019t pose a particular problem. They are just income. They can be received as cash: 2014-02-01 * \"Cash dividends received from mutual fund RBF1005\" Assets:Investments:Cash 171.02 CAD Income:Investments:Dividends Or they can be received as stock itself: 2014-02-01 * \"Stock dividends received in shares\" Assets:Investments:RBF1005 7.234 RBF1005 {23.64 CAD} Income:Investments:Dividends In the case of dividends received as stock, as for stock purchases, you provide the cost basis at which the dividend was received (this should be available in your statements). If the account is held at average cost, this posting will simply merge with the other legs at the time an average cost booking is needed to be performed.","title":"Dividends"},{"location":"trading_with_beancount.html#average-cost-booking","text":"At the moment, the only way to perform booking at average cost is painful: you would have to use the method outlined in the Stock Split section in order to revalue your inventory. This is impractical, however. There is an active proposal with an associated syntax to fully solve this problem. Once the proposal is implemented, it will look like this: 2014-02-01 * \"Selling 5 shares at market price 550 USD\" Assets:Investments:Stock -5 GOOG {*} Assets:Investments:Cash 2740.05 USD Expenses:Commissions 9.95 USD Income:Investments:CapitalGains Any posting with a cost of \u201c*\u201d acting on an inventory will select all the shares of that currency (GOOG), merge them into a single one at the average cost, and then reduce that position at this new average cost.","title":"Average Cost Booking"},{"location":"trading_with_beancount.html#future-topics","text":"I\u2019ll be handling the following topics later on: Mark-to-Market : Handling end-of-year mark-to-market for Section 1256 instruments (i.e., futures and options), by re-evaluating the cost basis. This is similar to a cost basis readjustment applied at the end of each year for all of these types of instruments. Short Sales : these require little changes. We just have to allow negative numbers of units held at cost. At the moment we spit a warning when units held at cost go negative in order to detect data entry errors, but it would be easy to extend the Open directive syntax to allow this to occur on specific accounts which can hold short sales, which should just show as negative shares. All the arithmetic should otherwise just work naturally. Interest payments on margins would show up as distinct transactions. Also, when you short the stock, you don\u2019t receive dividends for those positions, but rather you have to pay them out. You would have expense account for this, e.g., Expenses:StockLoans:Dividends . Trading Options : I have no idea how to do this at the moment, but I imagine these could be held like shares of stock, with no distinctions. I don\u2019t foresee any difficulty. Currency Trading : At the moment, I\u2019m not accounting for the positions in my FOREX accounts, just their P/L and interest payments. This poses interesting problems: Positions held in a FOREX account aren\u2019t just long or short the way that stocks are: they are actually offsetting two commodities at the same time. For example, a long position in USD/CAD should increase the exposure of USD and decrease the exposure in CAD, it can be seen as holding a long asset of USD and a short asset in CAD, at the same time. While it is possible to hold these positions as if they were distinct instruments (e.g., units of \u201cUSDCAD\u201d with disregard for its components) but for large positions, especially if held over long periods of time for hedging purposes, it is important to deal with this and somehow allow the user to reflect the net currency exposures of multiple currency positions against the rest of their assets and liabilities. We also need to deal with the gains generated by the closing of these positions: those generate a gain in the currency of the account, after conversion to this currency. For example, if you hold a currency account denominated in USD, and you go long EUR/JPY, when you close the position you will obtain a gain in EUR, and after conversion of the P/L from the EUR into the equivalent number of USD (via EUR/USD) the USD gain will be deposited in your account. This means that two rates are being used to estimate the current market value of any position: the differential between the current rate and the rate at the time of buying, and the rate of the base currency (e.g., EUR) in the account currency (e.g., USD). Some of these involve new features in Beancount, but some not. Ideas welcome. This is a misleading notion, however. In reality, there is no price , there exist only markets where you get a \u201chint\u201d of how much someone else might be willing to exchange your shares for (for different amounts to buy or to sell them, and for some limited number of them, we call this \u201ca market\u201d), but until you\u2019ve actually completed selling your shares, you don\u2019t really know precisely how much you will be able to execute that trade at, only an estimate. If you\u2019re not intimately familiar with trading, this should give you pause, and hopefully a big \u201cah-ha! \u201cmoment about how the world works - there really does not exist a price for anything in the world - but in the context of this discussion, let\u2019s make abstraction of this and assume that you can buy or sell as many shares as you have instantly on the markets at the middle price, such as Google Finance or Yahoo Finance or your broker would report it. The process of deciding that we would be able to sell the shares at 170$ each is called \u201cmarking\u201d, that is, under reasonable assumptions, we believe that we would be able to actually sell those shares at that price (the term of art \u201cmarking to market\u201d refers to the fact that we use the market price as the best indicator to our knowledge of our capability to realize the trade). For an individual buying and selling small amounts of shares that don\u2019t move the market and with an honest broker, this is mostly true in practice. \u21a9 As an aside, putting regular currencies in an account is just the degenerate case of a thing with an empty label. This is an implementation detail that works great in practice. \u21a9","title":"Future Topics"},{"location":"tutorial_example.html","text":"Beancount Example & Tutorial \uf0c1 Martin Blais , October 2014 http://furius.ca/beancount/doc/example Example File Generator Converting to Ledger Input Profile of Example User Future Additions Tutorial Generate an Example File Generating Reports Generating Balances Generating a Balance Sheet and Income Statement Journals Holdings Other Reports Other Formats Viewing Reports through the Web Interface The Future of Beancount Reports Example File Generator \uf0c1 Beancount has a command to generate a few years of a realistic user\u2019s historical entries: bean-example > example.beancount The script checks if the output fails to process cleanly in Beancount (if this occurs, try rerunning or contact the author at blais@furius.ca ). Note that there is an option to fix the random generator\u2019s seed to be used to generate the entries. You can generate multiple years of ledger data for this user; see available options with bean-example --help The purpose of the script is: To provide a realistic corpus of anonymous data for users to experiment with, to kick the tires of Beancount, generate reports, etc. To compare reports with those generated by Ledger. As an example file to be used in this tutorial. As input for stress testing Beancount. Converting to Ledger Input \uf0c1 It should be possible to convert example files generated from the script to Ledger syntax, like this: bean-report example.beancount ledger > example.lgr If you encounter any problems with the conversion, please let me know. While I have an automated test to check that the conversion succeeds, I\u2019m focusing my efforts on Beancount itself and I\u2019m not using Ledger much other than for the occasional comparison or for illustrating something on the mailing-list. Profile of Example User \uf0c1 Here\u2019s a high-level profile of this user. In the text that follows, I\u2019ll use the masculine \u201che\u201d for simplicity\u2019s sake. He lives somewhere in the USA and uses a single operating currency: \u201cUSD\u201d. He rents an apartment. Income : He is a software engineer (sorry I could not resist), receives a modest salary income from a software company. He is paid bi-weekly. He maximizes contributions to his company\u2019s 401k plan. His company offers a match to his contributions. His salary is paid as a direct deposit to his checking account. He benefits from and pays premiums for a health insurance plan provided by his employer. He tracks his vacation hours explicitly. Expenses : Checking account : He pays his apartment\u2019s rent by check. He also pays his electricity, internet ISP and mobile phone bills directly from his checking account. His bank also takes a monthly bank fee. Credit cards : He also has other regular expenses, such as groceries and restaurant outings with friends, which he pays from his credit card. Assets : Banking : He has an account at a major US bank. Retirement account : His 401k account is held at Fidelity or Vanguard and is allocated in its entirety to two funds, a mix of stocks and bonds. There are never any sales in this account. This account is held at average cost. Other investments : Extra money that he is able to save is transferred from his checking account towards a taxable investment account in which he purchases a mix of stocks and ETFs, and occasionally makes sales and realizes profits. Liabilities : Credit cards : He has one or two credit cards issued by another bank. Events/Tags : He takes a short vacation or two per year, where he travels somewhere fun. Future Additions \uf0c1 Note that as I beef up the example generator script, this profile will increase in complexity. If there is a particular situation from the cookbook or otherwise that you\u2019d like to see in the example file, please let me know (a patch is the best way to let me know). Generate fake PDF files for documents (from an option) along with suitable document directives for them, most of them implicit but some explicit. Generate more expenses: Some amount of realistic cash expenses. Add a vehicle: car repairs, gas, etc. Remove tram example, it is too urban-specific. Add donations for charity. Add a second credit card and vary sources of expenses. Add some real uses for links, for the 401k match, for instance, the employer contributions should be linked with the employee contributions. Or other ones. Generate a loan and payments for a student loan. Having a large liability is a common case, I\u2019d like to represent that, even if it\u2019s not a mortgage. Convert retirement accounts to book using the average cost method, and add corresponding fees which would otherwise be impossible to track with explicit lots. Add tracking of health care expenses as per the cookbook. Add foreign accounts in another currency and transactions and transfers in that currency. Tutorial \uf0c1 This section provides basic examples of generating reports with Beancount, on the typical example file that would be output by it. The list of reports here is not meant to be exhaustive. You may follow the tutorial and generate your own example file, or look at the files from beancount/examples/tutorial if you prefer. Generate an Example File \uf0c1 Begin by generating the example file: bean-example > example.beancount Open example.beancount and examine it. Next, before we begin generating reports, verify that the file loads without any errors: bean-check example.beancount It should return quietly, without outputting anything (bean-check only writes errors when there are some, otherwise on success it writes nothing). Generating Reports \uf0c1 Let\u2019s generate a report of the final balances of all accounts [output] : bean-report example.beancount balances As you can see, the bean-report script has subcommands for the various reports it generates. To list the available reports, use --help-reports [output] : bean-report --help-reports To list the options available for a particular report, use --help on it [output] : bean-report example.beancount balances --help The report-specific options vary for each report type. You can also list the global options for the script [output] : bean-report --help The global options allow you to specify the desired output format for the reports. To see which formats are available for which report, use --help-formats [output] : bean-report --help-formats Generating Balances \uf0c1 Good, so we know how to generate a report of balances for all accounts. This is a pretty detailed list of accounts though. Let\u2019s just restrict the output to the accounts that we\u2019re interested in [output] : bean-report example.beancount balances -e ETrade Here you can view the number of units held in each of the investment subaccounts. To render the cost, use the --cost option [output] : bean-report example.beancount balances -e ETrade --cost Formatting Tools \uf0c1 Sometimes it\u2019s nice to render a hierarchical list of accounts as a tree. You can use the \u201ctreeify\u201d tool provided by Beancount to do this: bean-report example.beancount balances | treeify This tool will work on any column of data that looks like a column of account names (you can also configure it work with filenames as well, or other patterns). Generating a Balance Sheet and Income Statement \uf0c1 Let us generate a balance sheet: bean-report example.beancount balsheet Unfortunately, the only output format supported for it at this point is HTML. Also, filtering balance sheet entries from the command-line is not supported. Generate this to a file and open a browser to it [output] : bean-report example.beancount balsheet > /tmp/balsheet.html You can to the same for income statements: bean-report example.beancount income > /tmp/income.html Journals \uf0c1 You can also generate journals (in Ledger parlance, these are \u201cregisters\u201d). Let\u2019s look at a checking account postings, for instance [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking To render a column of running balances, add the --balance option [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking --balance The inventories are rendered in the number of units they\u2019re holding [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD If you want to render the values at cost, use the --cost option [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD --cost To render journals, you will typically restrict the set of postings that you want to view. If you did not, the postings would render no change, because all the legs of the transactions would be applied to the running balance and the total would be zero. But try it, nonetheless [output] : bean-report example.beancount journal --balance Holdings \uf0c1 There are a variety of ways to obtain aggregations for the total list of holdings. List the detailed holdings [output] : bean-report example.beancount holdings To aggregate the holdings by account, do this [output] : bean-report example.beancount holdings --by=account Because it\u2019s common practice to create and use a dedicated Beancount subaccount name for each commodity held in a real-world account, we can refine this further to aggregate to the parent (\u201croot\u201d) accounts of those subaccounts [output] : bean-report example.beancount holdings --by=root-account We can aggregate by commodity [output] : bean-report example.beancount holdings --by=commodity Or by the currency in which the commodities are quoted, which gives us a glimpse into our currency exposure [output] : bean-report example.beancount holdings --by=currency Finally, we can aggregate all the holdings to obtain net worth [output] : bean-report example.beancount networth There are a few more options for converting amounts to a common currency. See help for details. Other Reports \uf0c1 There are many other miscellaneous reports available. Try a few of those. Listing all accounts [output] : bean-report example.beancount accounts Listing events [output] : bean-report example.beancount events Number of directives by type [output] : bean-report example.beancount stats-directives Number of postings by type [output] : bean-report example.beancount stats-postings Other Formats \uf0c1 Reports may support various output formats. You can change the output format with the --format (-f) global option [output] : bean-report -f csv example.beancount holdings Note that \u201cbeancount\u201d and \u201cledger\u201d are valid output formats: they refer to the Beancount and Ledger input syntaxes. Viewing Reports through the Web Interface \uf0c1 The original way to access reports in Beancount is via its web interface that serves to a local web server on your machine. Serve the example file like this: bean-web example.beancount Then navigate with a web browser to http://localhost:8080 . From there, you can click on any number of filtered views and access some of the reports previously demonstrated. For example, click on a year view; that will provide balance sheets and income statements and various other reports for this subset of transactions. The bean-web tool has many options for restricting what is being served. (Note that by default the server listens only for connections from your computer; if you want to host this on a web server and accept connections from anywhere, use --public ). Note: There exists a separate project which provides a better web interface than the one which comes with Beancount: Fava . You might want to check it out. The Future of Beancount Reports \uf0c1 I find my command-line reports above to be rather unsatisfying, from the point-of-view of customizability. They bother me a lot. This is largely because I\u2019ve been happy and satisfied with the capabilities of the web interface to Beancount. As I\u2019m beginning to use the command-line interface more and more, I\u2019m finding myself desiring for more explicit and well-defined way to apply all the filters I have available from the web interface, and more. For this reason, I\u2019m currently implementing a query language that looks similar to SQL . This syntax will allow me to remove all the custom reports above and to replace them by a single consistent query syntax. This work is underway. All the holdings reports and even their internal objects will be replaced by the SQL-like query syntax as well. Holdings need not be treated separately: they are simply the list of positions available at a particular point in time, and along with suitable accessors from the query syntax and support for aggregations over their many columns, we should be able to generate all of those reports equivalently and simplify the code. \u2014 Martin Blais, October 2014","title":"Tutorial & Example"},{"location":"tutorial_example.html#beancount-example-tutorial","text":"Martin Blais , October 2014 http://furius.ca/beancount/doc/example Example File Generator Converting to Ledger Input Profile of Example User Future Additions Tutorial Generate an Example File Generating Reports Generating Balances Generating a Balance Sheet and Income Statement Journals Holdings Other Reports Other Formats Viewing Reports through the Web Interface The Future of Beancount Reports","title":"Beancount Example & Tutorial"},{"location":"tutorial_example.html#example-file-generator","text":"Beancount has a command to generate a few years of a realistic user\u2019s historical entries: bean-example > example.beancount The script checks if the output fails to process cleanly in Beancount (if this occurs, try rerunning or contact the author at blais@furius.ca ). Note that there is an option to fix the random generator\u2019s seed to be used to generate the entries. You can generate multiple years of ledger data for this user; see available options with bean-example --help The purpose of the script is: To provide a realistic corpus of anonymous data for users to experiment with, to kick the tires of Beancount, generate reports, etc. To compare reports with those generated by Ledger. As an example file to be used in this tutorial. As input for stress testing Beancount.","title":"Example File Generator"},{"location":"tutorial_example.html#converting-to-ledger-input","text":"It should be possible to convert example files generated from the script to Ledger syntax, like this: bean-report example.beancount ledger > example.lgr If you encounter any problems with the conversion, please let me know. While I have an automated test to check that the conversion succeeds, I\u2019m focusing my efforts on Beancount itself and I\u2019m not using Ledger much other than for the occasional comparison or for illustrating something on the mailing-list.","title":"Converting to Ledger Input"},{"location":"tutorial_example.html#profile-of-example-user","text":"Here\u2019s a high-level profile of this user. In the text that follows, I\u2019ll use the masculine \u201che\u201d for simplicity\u2019s sake. He lives somewhere in the USA and uses a single operating currency: \u201cUSD\u201d. He rents an apartment. Income : He is a software engineer (sorry I could not resist), receives a modest salary income from a software company. He is paid bi-weekly. He maximizes contributions to his company\u2019s 401k plan. His company offers a match to his contributions. His salary is paid as a direct deposit to his checking account. He benefits from and pays premiums for a health insurance plan provided by his employer. He tracks his vacation hours explicitly. Expenses : Checking account : He pays his apartment\u2019s rent by check. He also pays his electricity, internet ISP and mobile phone bills directly from his checking account. His bank also takes a monthly bank fee. Credit cards : He also has other regular expenses, such as groceries and restaurant outings with friends, which he pays from his credit card. Assets : Banking : He has an account at a major US bank. Retirement account : His 401k account is held at Fidelity or Vanguard and is allocated in its entirety to two funds, a mix of stocks and bonds. There are never any sales in this account. This account is held at average cost. Other investments : Extra money that he is able to save is transferred from his checking account towards a taxable investment account in which he purchases a mix of stocks and ETFs, and occasionally makes sales and realizes profits. Liabilities : Credit cards : He has one or two credit cards issued by another bank. Events/Tags : He takes a short vacation or two per year, where he travels somewhere fun.","title":"Profile of Example User"},{"location":"tutorial_example.html#future-additions","text":"Note that as I beef up the example generator script, this profile will increase in complexity. If there is a particular situation from the cookbook or otherwise that you\u2019d like to see in the example file, please let me know (a patch is the best way to let me know). Generate fake PDF files for documents (from an option) along with suitable document directives for them, most of them implicit but some explicit. Generate more expenses: Some amount of realistic cash expenses. Add a vehicle: car repairs, gas, etc. Remove tram example, it is too urban-specific. Add donations for charity. Add a second credit card and vary sources of expenses. Add some real uses for links, for the 401k match, for instance, the employer contributions should be linked with the employee contributions. Or other ones. Generate a loan and payments for a student loan. Having a large liability is a common case, I\u2019d like to represent that, even if it\u2019s not a mortgage. Convert retirement accounts to book using the average cost method, and add corresponding fees which would otherwise be impossible to track with explicit lots. Add tracking of health care expenses as per the cookbook. Add foreign accounts in another currency and transactions and transfers in that currency.","title":"Future Additions"},{"location":"tutorial_example.html#tutorial","text":"This section provides basic examples of generating reports with Beancount, on the typical example file that would be output by it. The list of reports here is not meant to be exhaustive. You may follow the tutorial and generate your own example file, or look at the files from beancount/examples/tutorial if you prefer.","title":"Tutorial"},{"location":"tutorial_example.html#generate-an-example-file","text":"Begin by generating the example file: bean-example > example.beancount Open example.beancount and examine it. Next, before we begin generating reports, verify that the file loads without any errors: bean-check example.beancount It should return quietly, without outputting anything (bean-check only writes errors when there are some, otherwise on success it writes nothing).","title":"Generate an Example File"},{"location":"tutorial_example.html#generating-reports","text":"Let\u2019s generate a report of the final balances of all accounts [output] : bean-report example.beancount balances As you can see, the bean-report script has subcommands for the various reports it generates. To list the available reports, use --help-reports [output] : bean-report --help-reports To list the options available for a particular report, use --help on it [output] : bean-report example.beancount balances --help The report-specific options vary for each report type. You can also list the global options for the script [output] : bean-report --help The global options allow you to specify the desired output format for the reports. To see which formats are available for which report, use --help-formats [output] : bean-report --help-formats","title":"Generating Reports"},{"location":"tutorial_example.html#generating-balances","text":"Good, so we know how to generate a report of balances for all accounts. This is a pretty detailed list of accounts though. Let\u2019s just restrict the output to the accounts that we\u2019re interested in [output] : bean-report example.beancount balances -e ETrade Here you can view the number of units held in each of the investment subaccounts. To render the cost, use the --cost option [output] : bean-report example.beancount balances -e ETrade --cost","title":"Generating Balances"},{"location":"tutorial_example.html#formatting-tools","text":"Sometimes it\u2019s nice to render a hierarchical list of accounts as a tree. You can use the \u201ctreeify\u201d tool provided by Beancount to do this: bean-report example.beancount balances | treeify This tool will work on any column of data that looks like a column of account names (you can also configure it work with filenames as well, or other patterns).","title":"Formatting Tools"},{"location":"tutorial_example.html#generating-a-balance-sheet-and-income-statement","text":"Let us generate a balance sheet: bean-report example.beancount balsheet Unfortunately, the only output format supported for it at this point is HTML. Also, filtering balance sheet entries from the command-line is not supported. Generate this to a file and open a browser to it [output] : bean-report example.beancount balsheet > /tmp/balsheet.html You can to the same for income statements: bean-report example.beancount income > /tmp/income.html","title":"Generating a Balance Sheet and Income Statement"},{"location":"tutorial_example.html#journals","text":"You can also generate journals (in Ledger parlance, these are \u201cregisters\u201d). Let\u2019s look at a checking account postings, for instance [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking To render a column of running balances, add the --balance option [output] : bean-report example.beancount journal -a Assets:US:BofA:Checking --balance The inventories are rendered in the number of units they\u2019re holding [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD If you want to render the values at cost, use the --cost option [output] : bean-report example.beancount journal -a Assets:US:ETrade:GLD --cost To render journals, you will typically restrict the set of postings that you want to view. If you did not, the postings would render no change, because all the legs of the transactions would be applied to the running balance and the total would be zero. But try it, nonetheless [output] : bean-report example.beancount journal --balance","title":"Journals"},{"location":"tutorial_example.html#holdings","text":"There are a variety of ways to obtain aggregations for the total list of holdings. List the detailed holdings [output] : bean-report example.beancount holdings To aggregate the holdings by account, do this [output] : bean-report example.beancount holdings --by=account Because it\u2019s common practice to create and use a dedicated Beancount subaccount name for each commodity held in a real-world account, we can refine this further to aggregate to the parent (\u201croot\u201d) accounts of those subaccounts [output] : bean-report example.beancount holdings --by=root-account We can aggregate by commodity [output] : bean-report example.beancount holdings --by=commodity Or by the currency in which the commodities are quoted, which gives us a glimpse into our currency exposure [output] : bean-report example.beancount holdings --by=currency Finally, we can aggregate all the holdings to obtain net worth [output] : bean-report example.beancount networth There are a few more options for converting amounts to a common currency. See help for details.","title":"Holdings"},{"location":"tutorial_example.html#other-reports","text":"There are many other miscellaneous reports available. Try a few of those. Listing all accounts [output] : bean-report example.beancount accounts Listing events [output] : bean-report example.beancount events Number of directives by type [output] : bean-report example.beancount stats-directives Number of postings by type [output] : bean-report example.beancount stats-postings","title":"Other Reports"},{"location":"tutorial_example.html#other-formats","text":"Reports may support various output formats. You can change the output format with the --format (-f) global option [output] : bean-report -f csv example.beancount holdings Note that \u201cbeancount\u201d and \u201cledger\u201d are valid output formats: they refer to the Beancount and Ledger input syntaxes.","title":"Other Formats"},{"location":"tutorial_example.html#viewing-reports-through-the-web-interface","text":"The original way to access reports in Beancount is via its web interface that serves to a local web server on your machine. Serve the example file like this: bean-web example.beancount Then navigate with a web browser to http://localhost:8080 . From there, you can click on any number of filtered views and access some of the reports previously demonstrated. For example, click on a year view; that will provide balance sheets and income statements and various other reports for this subset of transactions. The bean-web tool has many options for restricting what is being served. (Note that by default the server listens only for connections from your computer; if you want to host this on a web server and accept connections from anywhere, use --public ). Note: There exists a separate project which provides a better web interface than the one which comes with Beancount: Fava . You might want to check it out.","title":"Viewing Reports through the Web Interface"},{"location":"tutorial_example.html#the-future-of-beancount-reports","text":"I find my command-line reports above to be rather unsatisfying, from the point-of-view of customizability. They bother me a lot. This is largely because I\u2019ve been happy and satisfied with the capabilities of the web interface to Beancount. As I\u2019m beginning to use the command-line interface more and more, I\u2019m finding myself desiring for more explicit and well-defined way to apply all the filters I have available from the web interface, and more. For this reason, I\u2019m currently implementing a query language that looks similar to SQL . This syntax will allow me to remove all the custom reports above and to replace them by a single consistent query syntax. This work is underway. All the holdings reports and even their internal objects will be replaced by the SQL-like query syntax as well. Holdings need not be treated separately: they are simply the list of positions available at a particular point in time, and along with suitable accessors from the query syntax and support for aggregations over their many columns, we should be able to generate all of those reports equivalently and simplify the code. \u2014 Martin Blais, October 2014","title":"The Future of Beancount Reports"},{"location":"api_reference/index.html","text":"beancount \uf0c1 beancount.core beancount.loader beancount.ops beancount.parser beancount.plugins beancount.scripts beancount.tools beancount.utils","title":"beancount"},{"location":"api_reference/index.html#beancount","text":"beancount.core beancount.loader beancount.ops beancount.parser beancount.plugins beancount.scripts beancount.tools beancount.utils","title":"beancount"},{"location":"api_reference/beancount.core.html","text":"beancount.core \uf0c1 Core basic objects and data structures to represent a list of entries. beancount.core.account \uf0c1 Functions that operate on account strings. These account objects are rather simple and dumb; they do not contain the list of their associated postings. This is achieved by building a realization; see realization.py for details. beancount.core.account.AccountTransformer \uf0c1 Account name transformer. This is used to support Win... huh, filesystems and platforms which do not support colon characters. Attributes: Name Type Description rsep A character string, the new separator to use in link names. beancount.core.account.AccountTransformer.parse(self, transformed_name) \uf0c1 Convert the transform account name to an account name. Source code in beancount/core/account.py def parse(self, transformed_name): \"Convert the transform account name to an account name.\" return (transformed_name if self.rsep is None else transformed_name.replace(self.rsep, sep)) beancount.core.account.AccountTransformer.render(self, account_name) \uf0c1 Convert the account name to a transformed account name. Source code in beancount/core/account.py def render(self, account_name): \"Convert the account name to a transformed account name.\" return (account_name if self.rsep is None else account_name.replace(sep, self.rsep)) beancount.core.account.commonprefix(accounts) \uf0c1 Return the common prefix of a list of account names. Parameters: accounts \u2013 A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. Source code in beancount/core/account.py def commonprefix(accounts): \"\"\"Return the common prefix of a list of account names. Args: accounts: A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. \"\"\" accounts_lists = [account_.split(sep) for account_ in accounts] # Note: the os.path.commonprefix() function just happens to work here. # Inspect its code, and even the special case of no common prefix # works well with str.join() below. common_list = path.commonprefix(accounts_lists) return sep.join(common_list) beancount.core.account.has_component(account_name, component) \uf0c1 Return true if one of the account contains a given component. Parameters: account_name \u2013 A string, an account name. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/account.py def has_component(account_name, component): \"\"\"Return true if one of the account contains a given component. Args: account_name: A string, an account name. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return bool(re.search('(^|:){}(:|$)'.format(component), account_name)) beancount.core.account.is_valid(string) \uf0c1 Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Parameters: string \u2013 A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. Source code in beancount/core/account.py def is_valid(string): \"\"\"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Args: string: A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. \"\"\" return (isinstance(string, str) and bool(re.match('{}$'.format(ACCOUNT_RE), string))) beancount.core.account.join(*components) \uf0c1 Join the names with the account separator. Parameters: *components \u2013 Strings, the components of an account name. Returns: A string, joined in a single account name. Source code in beancount/core/account.py def join(*components): \"\"\"Join the names with the account separator. Args: *components: Strings, the components of an account name. Returns: A string, joined in a single account name. \"\"\" return sep.join(components) beancount.core.account.leaf(account_name) \uf0c1 Get the name of the leaf of this account. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. Source code in beancount/core/account.py def leaf(account_name): \"\"\"Get the name of the leaf of this account. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. \"\"\" assert isinstance(account_name, str) return account_name.split(sep)[-1] if account_name else None beancount.core.account.parent(account_name) \uf0c1 Return the name of the parent account of the given account. Parameters: account_name \u2013 A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. Source code in beancount/core/account.py def parent(account_name): \"\"\"Return the name of the parent account of the given account. Args: account_name: A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. \"\"\" assert isinstance(account_name, str), account_name if not account_name: return None components = account_name.split(sep) components.pop(-1) return sep.join(components) beancount.core.account.parent_matcher(account_name) \uf0c1 Build a predicate that returns whether an account is under the given one. Parameters: account_name \u2013 The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of account_name . Source code in beancount/core/account.py def parent_matcher(account_name): \"\"\"Build a predicate that returns whether an account is under the given one. Args: account_name: The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of ``account_name``. \"\"\" return re.compile(r'{}($|{})'.format(re.escape(account_name), sep)).match beancount.core.account.parents(account_name) \uf0c1 A generator of the names of the parents of this account, including this account. Parameters: account_name \u2013 The name of the account we want to start iterating from. Returns: A generator of account name strings. Source code in beancount/core/account.py def parents(account_name): \"\"\"A generator of the names of the parents of this account, including this account. Args: account_name: The name of the account we want to start iterating from. Returns: A generator of account name strings. \"\"\" while account_name: yield account_name account_name = parent(account_name) beancount.core.account.root(num_components, account_name) \uf0c1 Return the first few components of an account's name. Parameters: num_components \u2013 An integer, the number of components to return. account_name \u2013 A string, an account name. Returns: A string, the account root up to 'num_components' components. Source code in beancount/core/account.py def root(num_components, account_name): \"\"\"Return the first few components of an account's name. Args: num_components: An integer, the number of components to return. account_name: A string, an account name. Returns: A string, the account root up to 'num_components' components. \"\"\" return join(*(split(account_name)[:num_components])) beancount.core.account.sans_root(account_name) \uf0c1 Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. Source code in beancount/core/account.py def sans_root(account_name): \"\"\"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. \"\"\" assert isinstance(account_name, str) components = account_name.split(sep)[1:] return join(*components) if account_name else None beancount.core.account.split(account_name) \uf0c1 Split an account's name into its components. Parameters: account_name \u2013 A string, an account name. Returns: A list of strings, the components of the account name (without the separators). Source code in beancount/core/account.py def split(account_name): \"\"\"Split an account's name into its components. Args: account_name: A string, an account name. Returns: A list of strings, the components of the account name (without the separators). \"\"\" return account_name.split(sep) beancount.core.account.walk(root_directory) \uf0c1 A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Parameters: root_directory \u2013 A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). Source code in beancount/core/account.py def walk(root_directory): \"\"\"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Args: root_directory: A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). \"\"\" for root, dirs, files in os.walk(root_directory): dirs.sort() files.sort() relroot = root[len(root_directory)+1:] account_name = relroot.replace(os.sep, sep) if is_valid(account_name): yield (root, account_name, dirs, files) beancount.core.account_types \uf0c1 Definition for global account types. This is where we keep the global account types value and definition. Note that it's unfortunate that we're using globals and side-effect here, but this is the best solution in the short-term, the account types are used in too many places to pass around that state everywhere. Maybe we change this later on. beancount.core.account_types.AccountTypes ( tuple ) \uf0c1 AccountTypes(assets, liabilities, equity, income, expenses) beancount.core.account_types.AccountTypes.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/account_types.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.account_types.AccountTypes.__new__(_cls, assets, liabilities, equity, income, expenses) special staticmethod \uf0c1 Create new instance of AccountTypes(assets, liabilities, equity, income, expenses) beancount.core.account_types.AccountTypes.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/account_types.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.account_types.get_account_sign(account_name, account_types=None) \uf0c1 Return the sign of the normal balance of a particular account. Parameters: account_name \u2013 A string, the name of the account whose sign is to return. account_types \u2013 An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. Source code in beancount/core/account_types.py def get_account_sign(account_name, account_types=None): \"\"\"Return the sign of the normal balance of a particular account. Args: account_name: A string, the name of the account whose sign is to return. account_types: An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. \"\"\" if account_types is None: account_types = DEFAULT_ACCOUNT_TYPES assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) account_type = get_account_type(account_name) return (+1 if account_type in (account_types.assets, account_types.expenses) else -1) beancount.core.account_types.get_account_sort_key(account_types, account_name) \uf0c1 Return a tuple that can be used to order/sort account names. Parameters: account_types \u2013 An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. Source code in beancount/core/account_types.py def get_account_sort_key(account_types, account_name): \"\"\"Return a tuple that can be used to order/sort account names. Args: account_types: An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. \"\"\" return (account_types.index(get_account_type(account_name)), account_name) beancount.core.account_types.get_account_type(account_name) \uf0c1 Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_name \u2013 A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. Source code in beancount/core/account_types.py def get_account_type(account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_name: A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) return account.split(account_name)[0] beancount.core.account_types.is_account_type(account_type, account_name) \uf0c1 Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_type \u2013 A string, the prefix type of the account. account_name \u2013 A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. Source code in beancount/core/account_types.py def is_account_type(account_type, account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_type: A string, the prefix type of the account. account_name: A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. \"\"\" return bool(re.match('^{}{}'.format(account_type, account.sep), account_name)) beancount.core.account_types.is_balance_sheet_account(account_name, account_types) \uf0c1 Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. Source code in beancount/core/account_types.py def is_balance_sheet_account(account_name, account_types): \"\"\"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.assets, account_types.liabilities, account_types.equity) beancount.core.account_types.is_equity_account(account_name, account_types) \uf0c1 Return true if the given account is an equity account. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. Source code in beancount/core/account_types.py def is_equity_account(account_name, account_types): \"\"\"Return true if the given account is an equity account. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type == account_types.equity beancount.core.account_types.is_income_statement_account(account_name, account_types) \uf0c1 Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. Source code in beancount/core/account_types.py def is_income_statement_account(account_name, account_types): \"\"\"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.income, account_types.expenses) beancount.core.account_types.is_root_account(account_name, account_types=None) \uf0c1 Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Parameters: account_name \u2013 A string, the name of the account to check for. account_types \u2013 An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. Source code in beancount/core/account_types.py def is_root_account(account_name, account_types=None): \"\"\"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Args: account_name: A string, the name of the account to check for. account_types: An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) if account_types is not None: assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) return account_name in account_types else: return (account_name and bool(re.match(r'([A-Z][A-Za-z0-9\\-]+)$', account_name))) beancount.core.amount \uf0c1 Amount class. This simple class is used to associate a number of units of a currency with its currency: (number, currency). beancount.core.amount.Amount ( _Amount ) \uf0c1 An 'Amount' represents a number of a particular unit of something. It's essentially a typed number, with corresponding manipulation operations defined on it. beancount.core.amount.Amount.__bool__(self) special \uf0c1 Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. Source code in beancount/core/amount.py def __bool__(self): \"\"\"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. \"\"\" return self.number != ZERO beancount.core.amount.Amount.__eq__(self, other) special \uf0c1 Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. Source code in beancount/core/amount.py def __eq__(self, other): \"\"\"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. \"\"\" if other is None: return False return (self.number, self.currency) == (other.number, other.currency) beancount.core.amount.Amount.__hash__(self) special \uf0c1 A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. Source code in beancount/core/amount.py def __hash__(self): \"\"\"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. \"\"\" return hash((self.number, self.currency)) beancount.core.amount.Amount.__lt__(self, other) special \uf0c1 Ordering comparison. This is used in the sorting key of positions. Parameters: other \u2013 An instance of Amount. Returns: True if this is less than the other Amount. Source code in beancount/core/amount.py def __lt__(self, other): \"\"\"Ordering comparison. This is used in the sorting key of positions. Args: other: An instance of Amount. Returns: True if this is less than the other Amount. \"\"\" return sortkey(self) < sortkey(other) beancount.core.amount.Amount.__neg__(self) special \uf0c1 Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. Source code in beancount/core/amount.py def __neg__(self): \"\"\"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. \"\"\" return Amount(-self.number, self.currency) beancount.core.amount.Amount.__new__(cls, number, currency) special staticmethod \uf0c1 Constructor from a number and currency. Parameters: number \u2013 A string or Decimal instance. Will get converted automatically. currency \u2013 A string, the currency symbol to use. Source code in beancount/core/amount.py def __new__(cls, number, currency): \"\"\"Constructor from a number and currency. Args: number: A string or Decimal instance. Will get converted automatically. currency: A string, the currency symbol to use. \"\"\" assert isinstance(number, Amount.valid_types_number), repr(number) assert isinstance(currency, Amount.valid_types_currency), repr(currency) return _Amount.__new__(cls, number, currency) beancount.core.amount.Amount.__repr__(self) special \uf0c1 Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string() beancount.core.amount.Amount.__str__(self) special \uf0c1 Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string() beancount.core.amount.Amount.from_string(string) staticmethod \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.Amount.to_string(self, dformat=) \uf0c1 Convert an Amount instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def to_string(self, dformat=DEFAULT_FORMATTER): \"\"\"Convert an Amount instance to a printable string. Args: dformat: An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. \"\"\" number_fmt = (dformat.format(self.number, self.currency) if isinstance(self.number, Decimal) else str(self.number)) return \"{} {}\".format(number_fmt, self.currency) beancount.core.amount.A(string) \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.abs(amount) \uf0c1 Return the absolute value of the given amount. Parameters: amount \u2013 An instance of Amount. Returns: An instance of Amount. Source code in beancount/core/amount.py def abs(amount): \"\"\"Return the absolute value of the given amount. Args: amount: An instance of Amount. Returns: An instance of Amount. \"\"\" return (amount if amount.number >= ZERO else Amount(-amount.number, amount.currency)) beancount.core.amount.add(amount1, amount2) \uf0c1 Add the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def add(amount1, amount2): \"\"\"Add the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number + amount2.number, amount1.currency) beancount.core.amount.div(amount, number) \uf0c1 Divide the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. Source code in beancount/core/amount.py def div(amount, number): \"\"\"Divide the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number / number, amount.currency) beancount.core.amount.from_string(string) \uf0c1 Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency) beancount.core.amount.mul(amount, number) \uf0c1 Multiply the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. Source code in beancount/core/amount.py def mul(amount, number): \"\"\"Multiply the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number * number, amount.currency) beancount.core.amount.sortkey(amount) \uf0c1 A comparison function that sorts by currency first. Parameters: amount \u2013 An instance of Amount. Returns: A sort key, composed of the currency first and then the number. Source code in beancount/core/amount.py def sortkey(amount): \"\"\"A comparison function that sorts by currency first. Args: amount: An instance of Amount. Returns: A sort key, composed of the currency first and then the number. \"\"\" return (amount.currency, amount.number) beancount.core.amount.sub(amount1, amount2) \uf0c1 Subtract the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def sub(amount1, amount2): \"\"\"Subtract the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number - amount2.number, amount1.currency) beancount.core.compare \uf0c1 Comparison helpers for data objects. beancount.core.compare.CompareError ( tuple ) \uf0c1 CompareError(source, message, entry) beancount.core.compare.CompareError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/compare.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.compare.CompareError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CompareError(source, message, entry) beancount.core.compare.CompareError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/compare.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.compare.compare_entries(entries1, entries2) \uf0c1 Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Parameters: entries1 \u2013 A list of directives of any type. entries2 \u2013 Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are \u2013 success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def compare_entries(entries1, entries2): \"\"\"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Args: entries1: A list of directives of any type. entries2: Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are: success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Raises: ValueError: If a duplicate entry is found. \"\"\" hashes1, errors1 = hash_entries(entries1, exclude_meta=True) hashes2, errors2 = hash_entries(entries2, exclude_meta=True) keys1 = set(hashes1.keys()) keys2 = set(hashes2.keys()) if errors1 or errors2: error = (errors1 + errors2)[0] raise ValueError(str(error)) same = keys1 == keys2 missing1 = data.sorted([hashes1[key] for key in keys1 - keys2]) missing2 = data.sorted([hashes2[key] for key in keys2 - keys1]) return (same, missing1, missing2) beancount.core.compare.excludes_entries(subset_entries, entries) \uf0c1 Check that a list of entries does not appear in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def excludes_entries(subset_entries, entries): \"\"\"Check that a list of entries does not appear in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) intersection = keys.intersection(subset_keys) excludes = not bool(intersection) extra = data.sorted([subset_hashes[key] for key in intersection]) return (excludes, extra) beancount.core.compare.hash_entries(entries, exclude_meta=False) \uf0c1 Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Parameters: entries \u2013 A list of directives. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. Source code in beancount/core/compare.py def hash_entries(entries, exclude_meta=False): \"\"\"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Args: entries: A list of directives. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. \"\"\" entry_hash_dict = {} errors = [] num_legal_duplicates = 0 for entry in entries: hash_ = hash_entry(entry, exclude_meta) if hash_ in entry_hash_dict: if isinstance(entry, Price): # Note: Allow duplicate Price entries, they should be common # because of the nature of stock markets (if they're closed, the # data source is likely to return an entry for the previously # available date, which may already have been fetched). num_legal_duplicates += 1 else: other_entry = entry_hash_dict[hash_] errors.append( CompareError(entry.meta, \"Duplicate entry: {} == {}\".format(entry, other_entry), entry)) entry_hash_dict[hash_] = entry if not errors: assert len(entry_hash_dict) + num_legal_duplicates == len(entries), ( len(entry_hash_dict), len(entries), num_legal_duplicates) return entry_hash_dict, errors beancount.core.compare.hash_entry(entry, exclude_meta=False) \uf0c1 Compute the stable hash of a single entry. Parameters: entry \u2013 A directive instance. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. Source code in beancount/core/compare.py def hash_entry(entry, exclude_meta=False): \"\"\"Compute the stable hash of a single entry. Args: entry: A directive instance. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. \"\"\" return stable_hash_namedtuple(entry, IGNORED_FIELD_NAMES if exclude_meta else frozenset()) beancount.core.compare.includes_entries(subset_entries, entries) \uf0c1 Check if a list of entries is included in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def includes_entries(subset_entries, entries): \"\"\"Check if a list of entries is included in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) includes = subset_keys.issubset(keys) missing = data.sorted([subset_hashes[key] for key in subset_keys - keys]) return (includes, missing) beancount.core.compare.stable_hash_namedtuple(objtuple, ignore=frozenset()) \uf0c1 Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Parameters: objtuple \u2013 A tuple object or other. ignore \u2013 A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. Source code in beancount/core/compare.py def stable_hash_namedtuple(objtuple, ignore=frozenset()): \"\"\"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Args: objtuple: A tuple object or other. ignore: A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. \"\"\" # Note: this routine is slow and would stand to be implemented in C. hashobj = hashlib.md5() for attr_name, attr_value in zip(objtuple._fields, objtuple): if attr_name in ignore: continue if isinstance(attr_value, (list, set, frozenset)): subhashes = set() for element in attr_value: if isinstance(element, tuple): subhashes.add(stable_hash_namedtuple(element, ignore)) else: md5 = hashlib.md5() md5.update(str(element).encode()) subhashes.add(md5.hexdigest()) for subhash in sorted(subhashes): hashobj.update(subhash.encode()) else: hashobj.update(str(attr_value).encode()) return hashobj.hexdigest() beancount.core.convert \uf0c1 Conversions from Position (or Posting) to units, cost, weight, market value. Units: Just the primary amount of the position. Cost: The cost basis of the position, if available. Weight: The cost basis or price of the position. Market Value: The units converted to a value via a price map. To convert an inventory's contents, simply use these functions in conjunction with Inventory.reduce() , like cost_inv = inv.reduce(convert.get_cost) This module equivalently converts Position and Posting instances. Note that we're specifically avoiding to create an import dependency on beancount.core.data in order to keep this module isolatable, but it works on postings due to duck-typing. Function named get_*() are used to compute values from postings to their price currency. Functions named convert_*() are used to convert postings and amounts to any currency. beancount.core.convert.convert_amount(amt, target_currency, price_map, date=None, via=None) \uf0c1 Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Parameters: amt \u2013 An instance of Amount. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. via \u2013 A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. Source code in beancount/core/convert.py def convert_amount(amt, target_currency, price_map, date=None, via=None): \"\"\"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Args: amt: An instance of Amount. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. via: A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. \"\"\" # First, attempt to convert directly. This should be the most # straightforward conversion. base_quote = (amt.currency, target_currency) _, rate = prices.get_price(price_map, base_quote, date) if rate is not None: # On success, just make the conversion directly. return Amount(amt.number * rate, target_currency) elif via: assert isinstance(via, (tuple, list)) # A price is unavailable, attempt to convert via cost/price currency # hop, if the value currency isn't the target currency. for implied_currency in via: if implied_currency == target_currency: continue base_quote1 = (amt.currency, implied_currency) _, rate1 = prices.get_price(price_map, base_quote1, date) if rate1 is not None: base_quote2 = (implied_currency, target_currency) _, rate2 = prices.get_price(price_map, base_quote2, date) if rate2 is not None: return Amount(amt.number * rate1 * rate2, target_currency) # We failed to infer a conversion rate; return the amt. return amt beancount.core.convert.convert_position(pos, target_currency, price_map, date=None) \uf0c1 Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Parameters: pos \u2013 An instance of Position or Posting, equivalently. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) Source code in beancount/core/convert.py def convert_position(pos, target_currency, price_map, date=None): \"\"\"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Args: pos: An instance of Position or Posting, equivalently. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) \"\"\" cost = pos.cost value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) return convert_amount(pos.units, target_currency, price_map, date=date, via=(value_currency,)) beancount.core.convert.get_cost(pos) \uf0c1 Return the total cost of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_cost(pos): \"\"\"Return the total cost of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' cost = pos.cost return (Amount(cost.number * pos.units.number, cost.currency) if (isinstance(cost, Cost) and isinstance(cost.number, Decimal)) else pos.units) beancount.core.convert.get_units(pos) \uf0c1 Return the units of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_units(pos): \"\"\"Return the units of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' return pos.units beancount.core.convert.get_value(pos, price_map, date=None) \uf0c1 Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see convert_*() functions below. Parameters: pos \u2013 An instance of Position or Posting, equivalently. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. Source code in beancount/core/convert.py def get_value(pos, price_map, date=None): \"\"\"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see ``convert_*()`` functions below. Args: pos: An instance of Position or Posting, equivalently. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # Try to infer what the cost/price currency should be. value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) if isinstance(value_currency, str): # We have a value currency; hit the price database. base_quote = (units.currency, value_currency) _, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: return Amount(units.number * price_number, value_currency) # We failed to infer a conversion rate; return the units. return units beancount.core.convert.get_weight(pos) \uf0c1 Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a key element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_weight(pos): \"\"\"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a *key* element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # It the object has a cost, use that as the weight, to balance. if isinstance(cost, Cost) and isinstance(cost.number, Decimal): weight = Amount(cost.number * pos.units.number, cost.currency) else: # Otherwise use the postings. weight = units # Unless there is a price available; use that if present. if not isinstance(pos, Position): price = pos.price if price is not None: # Note: Here we could assert that price.currency == units.currency. if price.number is MISSING or units.number is MISSING: converted_number = MISSING else: converted_number = price.number * units.number weight = Amount(converted_number, price.currency) return weight beancount.core.data \uf0c1 Basic data structures used to represent the Ledger entries. beancount.core.data.Balance ( tuple ) \uf0c1 Balance(meta, date, account, amount, tolerance, diff_amount) beancount.core.data.Balance.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Balance.__new__(_cls, meta, date, account, amount, tolerance, diff_amount) special staticmethod \uf0c1 Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount) beancount.core.data.Balance.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Close ( tuple ) \uf0c1 Close(meta, date, account) beancount.core.data.Close.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Close.__new__(_cls, meta, date, account) special staticmethod \uf0c1 Create new instance of Close(meta, date, account) beancount.core.data.Close.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Commodity ( tuple ) \uf0c1 Commodity(meta, date, currency) beancount.core.data.Commodity.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Commodity.__new__(_cls, meta, date, currency) special staticmethod \uf0c1 Create new instance of Commodity(meta, date, currency) beancount.core.data.Commodity.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Custom ( tuple ) \uf0c1 Custom(meta, date, type, values) beancount.core.data.Custom.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Custom.__new__(_cls, meta, date, type, values) special staticmethod \uf0c1 Create new instance of Custom(meta, date, type, values) beancount.core.data.Custom.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Document ( tuple ) \uf0c1 Document(meta, date, account, filename, tags, links) beancount.core.data.Document.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Document.__new__(_cls, meta, date, account, filename, tags, links) special staticmethod \uf0c1 Create new instance of Document(meta, date, account, filename, tags, links) beancount.core.data.Document.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Event ( tuple ) \uf0c1 Event(meta, date, type, description) beancount.core.data.Event.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Event.__new__(_cls, meta, date, type, description) special staticmethod \uf0c1 Create new instance of Event(meta, date, type, description) beancount.core.data.Event.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Note ( tuple ) \uf0c1 Note(meta, date, account, comment) beancount.core.data.Note.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Note.__new__(_cls, meta, date, account, comment) special staticmethod \uf0c1 Create new instance of Note(meta, date, account, comment) beancount.core.data.Note.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Open ( tuple ) \uf0c1 Open(meta, date, account, currencies, booking) beancount.core.data.Open.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Open.__new__(_cls, meta, date, account, currencies, booking) special staticmethod \uf0c1 Create new instance of Open(meta, date, account, currencies, booking) beancount.core.data.Open.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Pad ( tuple ) \uf0c1 Pad(meta, date, account, source_account) beancount.core.data.Pad.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Pad.__new__(_cls, meta, date, account, source_account) special staticmethod \uf0c1 Create new instance of Pad(meta, date, account, source_account) beancount.core.data.Pad.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Posting ( tuple ) \uf0c1 Posting(account, units, cost, price, flag, meta) beancount.core.data.Posting.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Posting.__new__(_cls, account, units, cost, price, flag, meta) special staticmethod \uf0c1 Create new instance of Posting(account, units, cost, price, flag, meta) beancount.core.data.Posting.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Price ( tuple ) \uf0c1 Price(meta, date, currency, amount) beancount.core.data.Price.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Price.__new__(_cls, meta, date, currency, amount) special staticmethod \uf0c1 Create new instance of Price(meta, date, currency, amount) beancount.core.data.Price.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Query ( tuple ) \uf0c1 Query(meta, date, name, query_string) beancount.core.data.Query.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Query.__new__(_cls, meta, date, name, query_string) special staticmethod \uf0c1 Create new instance of Query(meta, date, name, query_string) beancount.core.data.Query.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.Transaction ( tuple ) \uf0c1 Transaction(meta, date, flag, payee, narration, tags, links, postings) beancount.core.data.Transaction.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.Transaction.__new__(_cls, meta, date, flag, payee, narration, tags, links, postings) special staticmethod \uf0c1 Create new instance of Transaction(meta, date, flag, payee, narration, tags, links, postings) beancount.core.data.Transaction.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.TxnPosting ( tuple ) \uf0c1 TxnPosting(txn, posting) beancount.core.data.TxnPosting.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.data.TxnPosting.__new__(_cls, txn, posting) special staticmethod \uf0c1 Create new instance of TxnPosting(txn, posting) beancount.core.data.TxnPosting.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.data.create_simple_posting(entry, account, number, currency) \uf0c1 Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting(entry, account, number, currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if number is None: units = None else: if not isinstance(number, Decimal): number = D(number) units = Amount(number, currency) posting = Posting(account, units, None, None, None, None) if entry is not None: entry.postings.append(posting) return posting beancount.core.data.create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency) \uf0c1 Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. cost_number \u2013 A Decimal number or string to use for the posting's cost Amount. cost_currency \u2013 a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. cost_number: A Decimal number or string to use for the posting's cost Amount. cost_currency: a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if not isinstance(number, Decimal): number = D(number) if cost_number and not isinstance(cost_number, Decimal): cost_number = D(cost_number) units = Amount(number, currency) cost = Cost(cost_number, cost_currency, None, None) posting = Posting(account, units, cost, None, None, None) if entry is not None: entry.postings.append(posting) return posting beancount.core.data.entry_sortkey(entry) \uf0c1 Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. Source code in beancount/core/data.py def entry_sortkey(entry): \"\"\"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. \"\"\" return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"]) beancount.core.data.filter_txns(entries) \uf0c1 A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Parameters: entries \u2013 A list of directives. Yields: A sorted list of only the Transaction directives. Source code in beancount/core/data.py def filter_txns(entries): \"\"\"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Args: entries: A list of directives. Yields: A sorted list of only the Transaction directives. \"\"\" for entry in entries: if isinstance(entry, Transaction): yield entry beancount.core.data.find_closest(entries, filename, lineno) \uf0c1 Find the closest entry from entries to (filename, lineno). Parameters: entries \u2013 A list of directives. filename \u2013 A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno \u2013 An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. Source code in beancount/core/data.py def find_closest(entries, filename, lineno): \"\"\"Find the closest entry from entries to (filename, lineno). Args: entries: A list of directives. filename: A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno: An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. \"\"\" min_diffline = sys.maxsize closest_entry = None for entry in entries: emeta = entry.meta if emeta[\"filename\"] == filename and emeta[\"lineno\"] > 0: diffline = lineno - emeta[\"lineno\"] if 0 <= diffline < min_diffline: min_diffline = diffline closest_entry = entry return closest_entry beancount.core.data.get_entry(posting_or_entry) \uf0c1 Return the entry associated with the posting or entry. Parameters: entry \u2013 A TxnPosting or entry instance Returns: A datetime instance. Source code in beancount/core/data.py def get_entry(posting_or_entry): \"\"\"Return the entry associated with the posting or entry. Args: entry: A TxnPosting or entry instance Returns: A datetime instance. \"\"\" return (posting_or_entry.txn if isinstance(posting_or_entry, TxnPosting) else posting_or_entry) beancount.core.data.has_entry_account_component(entry, component) \uf0c1 Return true if one of the entry's postings has an account component. Parameters: entry \u2013 A Transaction entry. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/data.py def has_entry_account_component(entry, component): \"\"\"Return true if one of the entry's postings has an account component. Args: entry: A Transaction entry. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return (isinstance(entry, Transaction) and any(has_component(posting.account, component) for posting in entry.postings)) beancount.core.data.iter_entry_dates(entries, date_begin, date_end) \uf0c1 Iterate over the entries in a date window. Parameters: entries \u2013 A date-sorted list of dated directives. date_begin \u2013 A datetime.date instance, the first date to include. date_end \u2013 A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. Source code in beancount/core/data.py def iter_entry_dates(entries, date_begin, date_end): \"\"\"Iterate over the entries in a date window. Args: entries: A date-sorted list of dated directives. date_begin: A datetime.date instance, the first date to include. date_end: A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. \"\"\" getdate = lambda entry: entry.date index_begin = bisect_left_with_key(entries, date_begin, key=getdate) index_end = bisect_left_with_key(entries, date_end, key=getdate) for index in range(index_begin, index_end): yield entries[index] beancount.core.data.new_directive(clsname, fields) \uf0c1 Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Parameters: name \u2013 A string, the capitalized name of the directive. fields ( List[Tuple] ) \u2013 A string or the list of strings, names for the fields to add to the base tuple. Returns: \u2013 A type object for the new directive type. Source code in beancount/core/data.py def new_directive(clsname, fields: List[Tuple]) -> NamedTuple: \"\"\"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Args: name: A string, the capitalized name of the directive. fields: A string or the list of strings, names for the fields to add to the base tuple. Returns: A type object for the new directive type. \"\"\" return NamedTuple( clsname, [('meta', Meta), ('date', datetime.date)] + fields) beancount.core.data.new_metadata(filename, lineno, kvlist=None) \uf0c1 Create a new metadata container from the filename and line number. Parameters: filename \u2013 A string, the filename for the creator of this directive. lineno \u2013 An integer, the line number where the directive has been created. kvlist \u2013 An optional container of key-values. Returns: A metadata dict. Source code in beancount/core/data.py def new_metadata(filename, lineno, kvlist=None): \"\"\"Create a new metadata container from the filename and line number. Args: filename: A string, the filename for the creator of this directive. lineno: An integer, the line number where the directive has been created. kvlist: An optional container of key-values. Returns: A metadata dict. \"\"\" meta = {'filename': filename, 'lineno': lineno} if kvlist: meta.update(kvlist) return meta beancount.core.data.posting_has_conversion(posting) \uf0c1 Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Parameters: posting \u2013 an instance of Posting Returns: A boolean, true if this posting has a price conversion. Source code in beancount/core/data.py def posting_has_conversion(posting): \"\"\"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Args: posting: an instance of Posting Return: A boolean, true if this posting has a price conversion. \"\"\" return (posting.cost is None and posting.price is not None) beancount.core.data.posting_sortkey(entry) \uf0c1 Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. Source code in beancount/core/data.py def posting_sortkey(entry): \"\"\"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. \"\"\" if isinstance(entry, TxnPosting): entry = entry.txn return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"]) beancount.core.data.remove_account_postings(account, entries) \uf0c1 Remove all postings with the given account. Parameters: account \u2013 A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. Source code in beancount/core/data.py def remove_account_postings(account, entries): \"\"\"Remove all postings with the given account. Args: account: A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, Transaction) and ( any(posting.account == account for posting in entry.postings)): entry = entry._replace(postings=[posting for posting in entry.postings if posting.account != account]) new_entries.append(entry) return new_entries beancount.core.data.sanity_check_types(entry, allow_none_for_tags_and_links=False) \uf0c1 Check that the entry and its postings has all correct data types. Parameters: entry \u2013 An instance of one of the entries to be checked. allow_none_for_tags_and_links \u2013 A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Exceptions: AssertionError \u2013 If there is anything that is unexpected, raises an exception. Source code in beancount/core/data.py def sanity_check_types(entry, allow_none_for_tags_and_links=False): \"\"\"Check that the entry and its postings has all correct data types. Args: entry: An instance of one of the entries to be checked. allow_none_for_tags_and_links: A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Raises: AssertionError: If there is anything that is unexpected, raises an exception. \"\"\" assert isinstance(entry, ALL_DIRECTIVES), \"Invalid directive type\" assert isinstance(entry.meta, dict), \"Invalid type for meta\" assert 'filename' in entry.meta, \"Missing filename in metadata\" assert 'lineno' in entry.meta, \"Missing line number in metadata\" assert isinstance(entry.date, datetime.date), \"Invalid date type\" if isinstance(entry, Transaction): assert isinstance(entry.flag, (NoneType, str)), \"Invalid flag type\" assert isinstance(entry.payee, (NoneType, str)), \"Invalid payee type\" assert isinstance(entry.narration, (NoneType, str)), \"Invalid narration type\" set_types = ((NoneType, set, frozenset) if allow_none_for_tags_and_links else (set, frozenset)) assert isinstance(entry.tags, set_types), ( \"Invalid tags type: {}\".format(type(entry.tags))) assert isinstance(entry.links, set_types), ( \"Invalid links type: {}\".format(type(entry.links))) assert isinstance(entry.postings, list), \"Invalid postings list type\" for posting in entry.postings: assert isinstance(posting, Posting), \"Invalid posting type\" assert isinstance(posting.account, str), \"Invalid account type\" assert isinstance(posting.units, (Amount, NoneType)), \"Invalid units type\" assert isinstance(posting.cost, (Cost, CostSpec, NoneType)), \"Invalid cost type\" assert isinstance(posting.price, (Amount, NoneType)), \"Invalid price type\" assert isinstance(posting.flag, (str, NoneType)), \"Invalid flag type\" beancount.core.data.sorted(entries) \uf0c1 A convenience to sort a list of entries, using entry_sortkey(). Parameters: entries \u2013 A list of directives. Returns: A sorted list of directives. Source code in beancount/core/data.py def sorted(entries): \"\"\"A convenience to sort a list of entries, using entry_sortkey(). Args: entries: A list of directives. Returns: A sorted list of directives. \"\"\" return builtins.sorted(entries, key=entry_sortkey) beancount.core.data.transaction_has_conversion(transaction) \uf0c1 Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Parameters: transaction \u2013 an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. Source code in beancount/core/data.py def transaction_has_conversion(transaction): \"\"\"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Args: transaction: an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. \"\"\" assert isinstance(transaction, Transaction), ( \"Invalid type of entry for transaction: {}\".format(transaction)) for posting in transaction.postings: if posting_has_conversion(posting): return True return False beancount.core.display_context \uf0c1 A settings class to offer control over the number of digits rendered. This module contains routines that can accumulate information on the width and precision of numbers to be rendered and derive the precision required to render all of them consistently and under certain common alignment requirements. This is required in order to output neatly lined up columns of numbers in various styles. A common case is that the precision can be observed for numbers present in the input file. This display precision can be used as the \"precision by default\" if we write a routine for which it is inconvenient to feed all the numbers to build such an accumulator. Here are all the aspects supported by this module: PRECISION: Numbers for a particular currency are always rendered to the same precision, and they can be rendered to one of two precisions; either the most common number of fractional digits, or the maximum number of digits seen (this is useful for rendering prices). ALIGNMENT: Several alignment methods are supported. \"natural\": Render the strings as small as possible with no padding, but to their currency's precision. Like this: '1.2345' '764' '-7,409.01' '0.00000125' \"dot-aligned\": The periods will align vertically, the left and right sides are padded so that the column of numbers has the same width: ' 1.2345 ' ' 764 ' '-7,409.01 ' ' 0.00000125' \"right\": The strings are all flushed right, the left side is padded so that the column of numbers has the same width: ' 1.2345' ' 764' ' -7,409.01' ' 0.00000125' SIGN: If a negative sign is present in the input numbers, the rendered numbers reserve a space for it. If not, then we save the space. COMMAS: If the user requests to render commas, commas are rendered in the output. RESERVED: A number of extra integral digits reserved on the left in order to allow rendering novel numbers that haven't yet been seen. For example, balances may contains much larger numbers than the numbers seen in input files, and these need to be accommodated when aligning to the right. beancount.core.display_context.Align ( Enum ) \uf0c1 Alignment style for numbers. beancount.core.display_context.DisplayContext \uf0c1 A builder object used to construct a DisplayContext from a series of numbers. Attributes: Name Type Description ccontexts A dict of currency string to CurrencyContext instance. commas A bool, true if we should render commas. This just gets propagated onwards as the default value of to build with. beancount.core.display_context.DisplayContext.build(self, alignment=, precision=, commas=None, reserved=0) \uf0c1 Build a formatter for the given display context. Parameters: alignment \u2013 The desired alignment. precision \u2013 The desired precision. commas \u2013 Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved \u2013 An integer, the number of extra digits to be allocated in the maximum width calculations. Source code in beancount/core/display_context.py def build(self, alignment=Align.NATURAL, precision=Precision.MOST_COMMON, commas=None, reserved=0): \"\"\"Build a formatter for the given display context. Args: alignment: The desired alignment. precision: The desired precision. commas: Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved: An integer, the number of extra digits to be allocated in the maximum width calculations. \"\"\" if commas is None: commas = self.commas if alignment == Align.NATURAL: build_method = self._build_natural elif alignment == Align.RIGHT: build_method = self._build_right elif alignment == Align.DOT: build_method = self._build_dot else: raise ValueError(\"Unknown alignment: {}\".format(alignment)) fmtstrings = build_method(precision, commas, reserved) return DisplayFormatter(self, precision, fmtstrings) beancount.core.display_context.DisplayContext.quantize(self, number, currency, precision=) \uf0c1 Quantize the given number to the given precision. Parameters: number \u2013 A Decimal instance, the number to be quantized. currency \u2013 A currency string. precision \u2013 Which precision to use. Returns: A Decimal instance, the quantized number. Source code in beancount/core/display_context.py def quantize(self, number, currency, precision=Precision.MOST_COMMON): \"\"\"Quantize the given number to the given precision. Args: number: A Decimal instance, the number to be quantized. currency: A currency string. precision: Which precision to use. Returns: A Decimal instance, the quantized number. \"\"\" assert isinstance(number, Decimal), \"Invalid data: {}\".format(number) ccontext = self.ccontexts[currency] num_fractional_digits = ccontext.get_fractional(precision) if num_fractional_digits is None: # Note: We could probably logging.warn() this situation here. return number qdigit = Decimal(1).scaleb(-num_fractional_digits) return number.quantize(qdigit) beancount.core.display_context.DisplayContext.set_commas(self, commas) \uf0c1 Set the default value for rendering commas. Source code in beancount/core/display_context.py def set_commas(self, commas): \"\"\"Set the default value for rendering commas.\"\"\" self.commas = commas beancount.core.display_context.DisplayContext.update(self, number, currency='__default__') \uf0c1 Update the builder with the given number for the given currency. Parameters: number \u2013 An instance of Decimal to consider for this currency. currency \u2013 An optional string, the currency this numbers applies to. Source code in beancount/core/display_context.py def update(self, number, currency='__default__'): \"\"\"Update the builder with the given number for the given currency. Args: number: An instance of Decimal to consider for this currency. currency: An optional string, the currency this numbers applies to. \"\"\" self.ccontexts[currency].update(number) beancount.core.display_context.DisplayFormatter \uf0c1 A class used to contain various settings that control how we output numbers. In particular, the precision used for each currency, and whether or not commas should be printed. This object is intended to be passed around to all functions that format numbers to strings. Attributes: Name Type Description dcontext A DisplayContext instance. precision An enum of Precision from which it was built. fmtstrings A dict of currency to pre-baked format strings for it. fmtfuncs A dict of currency to pre-baked formatting functions for it. beancount.core.display_context.Precision ( Enum ) \uf0c1 The type of precision required. beancount.core.distribution \uf0c1 A simple accumulator for data about a mathematical distribution. beancount.core.distribution.Distribution \uf0c1 A class that computes a histogram of integer values. This is used to compute a length that will cover at least some decent fraction of the samples. beancount.core.distribution.Distribution.empty(self) \uf0c1 Return true if the distribution is empty. Returns: A boolean. Source code in beancount/core/distribution.py def empty(self): \"\"\"Return true if the distribution is empty. Returns: A boolean. \"\"\" return len(self.hist) == 0 beancount.core.distribution.Distribution.max(self) \uf0c1 Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def max(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[-1] return value beancount.core.distribution.Distribution.min(self) \uf0c1 Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def min(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[0] return value beancount.core.distribution.Distribution.mode(self) \uf0c1 Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def mode(self): \"\"\"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None max_value = 0 max_count = 0 for value, count in sorted(self.hist.items()): if count >= max_count: max_count = count max_value = value return max_value beancount.core.distribution.Distribution.update(self, value) \uf0c1 Add a sample to the distribution. Parameters: value \u2013 A value of the function. Source code in beancount/core/distribution.py def update(self, value): \"\"\"Add a sample to the distribution. Args: value: A value of the function. \"\"\" self.hist[value] += 1 beancount.core.flags \uf0c1 Flag constants. beancount.core.getters \uf0c1 Getter functions that operate on lists of entries to return various lists of things that they reference, accounts, tags, links, currencies, etc. beancount.core.getters.GetAccounts \uf0c1 Accounts gatherer. beancount.core.getters.GetAccounts.Balance(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Close(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Commodity(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Custom(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Document(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Event(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Note(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Open(_, entry) \uf0c1 Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,) beancount.core.getters.GetAccounts.Pad(_, entry) \uf0c1 Process a Pad directive. Parameters: entry \u2013 An instance of Pad. Returns: The two accounts of the Pad directive. Source code in beancount/core/getters.py def Pad(_, entry): \"\"\"Process a Pad directive. Args: entry: An instance of Pad. Returns: The two accounts of the Pad directive. \"\"\" return (entry.account, entry.source_account) beancount.core.getters.GetAccounts.Price(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Query(_, entry) \uf0c1 Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return () beancount.core.getters.GetAccounts.Transaction(_, entry) \uf0c1 Process a Transaction directive. Parameters: entry \u2013 An instance of Transaction. Yields: The accounts of the legs of the transaction. Source code in beancount/core/getters.py def Transaction(_, entry): \"\"\"Process a Transaction directive. Args: entry: An instance of Transaction. Yields: The accounts of the legs of the transaction. \"\"\" for posting in entry.postings: yield posting.account beancount.core.getters.GetAccounts.get_accounts_use_map(self, entries) \uf0c1 Gather the list of accounts from the list of entries. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(self, entries): \"\"\"Gather the list of accounts from the list of entries. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" accounts_first = {} accounts_last = {} for entry in entries: method = getattr(self, entry.__class__.__name__) for account_ in method(entry): if account_ not in accounts_first: accounts_first[account_] = entry.date accounts_last[account_] = entry.date return accounts_first, accounts_last beancount.core.getters.GetAccounts.get_entry_accounts(self, entry) \uf0c1 Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entry \u2013 A directive instance. Returns: A set of account name strings. Source code in beancount/core/getters.py def get_entry_accounts(self, entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entry: A directive instance. Returns: A set of account name strings. \"\"\" method = getattr(self, entry.__class__.__name__) return set(method(entry)) beancount.core.getters.get_account_components(entries) \uf0c1 Gather all the account components available in the given directives. Parameters: entries \u2013 A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. Source code in beancount/core/getters.py def get_account_components(entries): \"\"\"Gather all the account components available in the given directives. Args: entries: A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. \"\"\" accounts = get_accounts(entries) components = set() for account_name in accounts: components.update(account.split(account_name)) return sorted(components) beancount.core.getters.get_account_open_close(entries) \uf0c1 Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Parameters: entries \u2013 A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. Source code in beancount/core/getters.py def get_account_open_close(entries): \"\"\"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Args: entries: A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. \"\"\" # A dict of account name to (open-entry, close-entry). open_close_map = defaultdict(lambda: [None, None]) for entry in entries: if not isinstance(entry, (Open, Close)): continue open_close = open_close_map[entry.account] index = 0 if isinstance(entry, Open) else 1 previous_entry = open_close[index] if previous_entry is not None: if previous_entry.date <= entry.date: entry = previous_entry open_close[index] = entry return dict(open_close_map) beancount.core.getters.get_accounts(entries) \uf0c1 Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A set of account strings. Source code in beancount/core/getters.py def get_accounts(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A set of account strings. \"\"\" _, accounts_last = _GetAccounts.get_accounts_use_map(entries) return accounts_last.keys() beancount.core.getters.get_accounts_use_map(entries) \uf0c1 Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" return _GetAccounts.get_accounts_use_map(entries) beancount.core.getters.get_active_years(entries) \uf0c1 Yield all the years that have at least one entry in them. Parameters: entries \u2013 A list of directive instances. Yields: Unique dates see in the list of directives. Source code in beancount/core/getters.py def get_active_years(entries): \"\"\"Yield all the years that have at least one entry in them. Args: entries: A list of directive instances. Yields: Unique dates see in the list of directives. \"\"\" seen = set() prev_year = None for entry in entries: year = entry.date.year if year != prev_year: prev_year = year assert year not in seen seen.add(year) yield year beancount.core.getters.get_all_links(entries) \uf0c1 Return a list of all the links seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of links strings. Source code in beancount/core/getters.py def get_all_links(entries): \"\"\"Return a list of all the links seen in the given entries. Args: entries: A list of directive instances. Returns: A set of links strings. \"\"\" all_links = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.links: all_links.update(entry.links) return sorted(all_links) beancount.core.getters.get_all_payees(entries) \uf0c1 Return a list of all the unique payees seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of payee strings. Source code in beancount/core/getters.py def get_all_payees(entries): \"\"\"Return a list of all the unique payees seen in the given entries. Args: entries: A list of directive instances. Returns: A set of payee strings. \"\"\" all_payees = set() for entry in entries: if not isinstance(entry, Transaction): continue all_payees.add(entry.payee) all_payees.discard(None) return sorted(all_payees) beancount.core.getters.get_all_tags(entries) \uf0c1 Return a list of all the tags seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of tag strings. Source code in beancount/core/getters.py def get_all_tags(entries): \"\"\"Return a list of all the tags seen in the given entries. Args: entries: A list of directive instances. Returns: A set of tag strings. \"\"\" all_tags = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.tags: all_tags.update(entry.tags) return sorted(all_tags) beancount.core.getters.get_commodity_map(entries, create_missing=True) \uf0c1 Create map of commodity names to Commodity entries. Parameters: entries \u2013 A list of directive instances. create_missing \u2013 A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. Source code in beancount/core/getters.py def get_commodity_map(entries, create_missing=True): \"\"\"Create map of commodity names to Commodity entries. Args: entries: A list of directive instances. create_missing: A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. \"\"\" if not entries: return {} commodities_map = {} for entry in entries: if isinstance(entry, Commodity): commodities_map[entry.currency] = entry elif isinstance(entry, Transaction): for posting in entry.postings: # Main currency. units = posting.units commodities_map.setdefault(units.currency, None) # Currency in cost. cost = posting.cost if cost: commodities_map.setdefault(cost.currency, None) # Currency in price. price = posting.price if price: commodities_map.setdefault(price.currency, None) elif isinstance(entry, Balance): commodities_map.setdefault(entry.amount.currency, None) elif isinstance(entry, Price): commodities_map.setdefault(entry.currency, None) if create_missing: # Create missing Commodity directives when they haven't been specified explicitly. # (I think it might be better to always do this from the loader.) date = entries[0].date meta = data.new_metadata('', 0) commodities_map = { commodity: (entry if entry is not None else Commodity(meta, date, commodity)) for commodity, entry in commodities_map.items()} return commodities_map beancount.core.getters.get_dict_accounts(account_names) \uf0c1 Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Parameters: account_names \u2013 An iterable of account names (strings) Returns: A nested OrderedDict of account leafs Source code in beancount/core/getters.py def get_dict_accounts(account_names): \"\"\"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Args: account_names: An iterable of account names (strings) Returns: A nested OrderedDict of account leafs \"\"\" leveldict = OrderedDict() for account_name in account_names: nested_dict = leveldict for component in account.split(account_name): nested_dict = nested_dict.setdefault(component, OrderedDict()) nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True return leveldict beancount.core.getters.get_entry_accounts(entry) \uf0c1 Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entries \u2013 A directive instance. Returns: A set of account strings. Source code in beancount/core/getters.py def get_entry_accounts(entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entries: A directive instance. Returns: A set of account strings. \"\"\" return _GetAccounts.get_entry_accounts(entry) beancount.core.getters.get_leveln_parent_accounts(account_names, level, nrepeats=0) \uf0c1 Return a list of all the unique leaf names at level N in an account hierarchy. Parameters: account_names \u2013 A list of account names (strings) level \u2013 The level to cross-cut. 0 is for root accounts. nrepeats \u2013 A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. Source code in beancount/core/getters.py def get_leveln_parent_accounts(account_names, level, nrepeats=0): \"\"\"Return a list of all the unique leaf names at level N in an account hierarchy. Args: account_names: A list of account names (strings) level: The level to cross-cut. 0 is for root accounts. nrepeats: A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. \"\"\" leveldict = defaultdict(int) for account_name in set(account_names): components = account.split(account_name) if level < len(components): leveldict[components[level]] += 1 levels = {level_ for level_, count in leveldict.items() if count > nrepeats} return sorted(levels) beancount.core.getters.get_min_max_dates(entries, types=None) \uf0c1 Return the minimum and maximum dates in the list of entries. Parameters: entries \u2013 A list of directive instances. types \u2013 An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. Source code in beancount/core/getters.py def get_min_max_dates(entries, types=None): \"\"\"Return the minimum and maximum dates in the list of entries. Args: entries: A list of directive instances. types: An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. \"\"\" date_first = date_last = None for entry in entries: if types and not isinstance(entry, types): continue date_first = entry.date break for entry in reversed(entries): if types and not isinstance(entry, types): continue date_last = entry.date break return (date_first, date_last) beancount.core.getters.get_values_meta(name_to_entries_map, *meta_keys, *, default=None) \uf0c1 Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Parameters: name_to_entries_map \u2013 A dict of something to an entry or None. meta_keys \u2013 A list of strings, the keys to fetch from the metadata. default \u2013 The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. Source code in beancount/core/getters.py def get_values_meta(name_to_entries_map, *meta_keys, default=None): \"\"\"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Args: name_to_entries_map: A dict of something to an entry or None. meta_keys: A list of strings, the keys to fetch from the metadata. default: The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. \"\"\" value_map = {} for key, entry in name_to_entries_map.items(): value_list = [] for meta_key in meta_keys: value_list.append(entry.meta.get(meta_key, default) if entry is not None else default) value_map[key] = (value_list[0] if len(meta_keys) == 1 else tuple(value_list)) return value_map beancount.core.interpolate \uf0c1 Code used to automatically complete postings without positions. beancount.core.interpolate.BalanceError ( tuple ) \uf0c1 BalanceError(source, message, entry) beancount.core.interpolate.BalanceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/interpolate.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.interpolate.BalanceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BalanceError(source, message, entry) beancount.core.interpolate.BalanceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/interpolate.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.interpolate.compute_entries_balance(entries, prefix=None, date=None) \uf0c1 Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Parameters: entries \u2013 A list of directives. prefix \u2013 If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date \u2013 A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. Source code in beancount/core/interpolate.py def compute_entries_balance(entries, prefix=None, date=None): \"\"\"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Args: entries: A list of directives. prefix: If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date: A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. \"\"\" total_balance = Inventory() for entry in entries: if not (date is None or entry.date < date): break if isinstance(entry, Transaction): for posting in entry.postings: if prefix is None or posting.account.startswith(prefix): total_balance.add_position(posting) return total_balance beancount.core.interpolate.compute_entry_context(entries, context_entry) \uf0c1 Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Parameters: entries \u2013 A list of directives. context_entry \u2013 The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. Source code in beancount/core/interpolate.py def compute_entry_context(entries, context_entry): \"\"\"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Args: entries: A list of directives. context_entry: The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. \"\"\" assert context_entry is not None, \"context_entry is missing.\" # Get the set of accounts for which to compute the context. context_accounts = getters.get_entry_accounts(context_entry) # Iterate over the entries until we find the target one and accumulate the # balance. context_before = collections.defaultdict(inventory.Inventory) for entry in entries: if entry is context_entry: break if isinstance(entry, Transaction): for posting in entry.postings: if not any(posting.account == account for account in context_accounts): continue balance = context_before[posting.account] balance.add_position(posting) # Compute the after context for the entry. context_after = copy.deepcopy(context_before) if isinstance(context_entry, Transaction): for posting in entry.postings: balance = context_after[posting.account] balance.add_position(posting) return context_before, context_after beancount.core.interpolate.compute_residual(postings) \uf0c1 Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Parameters: postings \u2013 A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. Source code in beancount/core/interpolate.py def compute_residual(postings): \"\"\"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Args: postings: A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. \"\"\" inventory = Inventory() for posting in postings: # Skip auto-postings inserted to absorb the residual (rounding error). if posting.meta and posting.meta.get(AUTOMATIC_RESIDUAL, False): continue # Add to total residual balance. inventory.add_amount(convert.get_weight(posting)) return inventory beancount.core.interpolate.fill_residual_posting(entry, account_rounding) \uf0c1 If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Parameters: entry \u2013 An instance of a Transaction. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. Source code in beancount/core/interpolate.py def fill_residual_posting(entry, account_rounding): \"\"\"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Args: entry: An instance of a Transaction. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. \"\"\" residual = compute_residual(entry.postings) if not residual.is_empty(): new_postings = list(entry.postings) new_postings.extend(get_residual_postings(residual, account_rounding)) entry = entry._replace(postings=new_postings) return entry beancount.core.interpolate.get_residual_postings(residual, account_rounding) \uf0c1 Create postings to book the given residuals. Parameters: residual \u2013 An Inventory, the residual positions. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. Source code in beancount/core/interpolate.py def get_residual_postings(residual, account_rounding): \"\"\"Create postings to book the given residuals. Args: residual: An Inventory, the residual positions. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. \"\"\" meta = {AUTOMATIC_META: True, AUTOMATIC_RESIDUAL: True} return [Posting(account_rounding, -position.units, position.cost, None, None, meta.copy()) for position in residual.get_positions()] beancount.core.interpolate.has_nontrivial_balance(posting) \uf0c1 Return True if a Posting has a balance amount that would have to be calculated. Parameters: posting \u2013 A Posting instance. Returns: A boolean. Source code in beancount/core/interpolate.py def has_nontrivial_balance(posting): \"\"\"Return True if a Posting has a balance amount that would have to be calculated. Args: posting: A Posting instance. Returns: A boolean. \"\"\" return posting.cost or posting.price beancount.core.interpolate.infer_tolerances(postings, options_map, use_cost=None) \uf0c1 Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Parameters: postings \u2013 A list of Posting instances. options_map \u2013 A dict of options. use_cost \u2013 A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. Source code in beancount/core/interpolate.py def infer_tolerances(postings, options_map, use_cost=None): \"\"\"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Args: postings: A list of Posting instances. options_map: A dict of options. use_cost: A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. \"\"\" if use_cost is None: use_cost = options_map[\"infer_tolerance_from_cost\"] inferred_tolerance_multiplier = options_map[\"inferred_tolerance_multiplier\"] default_tolerances = options_map[\"inferred_tolerance_default\"] tolerances = default_tolerances.copy() cost_tolerances = collections.defaultdict(D) for posting in postings: # Skip the precision on automatically inferred postings. if posting.meta and AUTOMATIC_META in posting.meta: continue units = posting.units if not (isinstance(units, Amount) and isinstance(units.number, Decimal)): continue # Compute bounds on the number. currency = units.currency expo = units.number.as_tuple().exponent if expo < 0: # Note: the exponent is a negative value. tolerance = ONE.scaleb(expo) * inferred_tolerance_multiplier tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) if not use_cost: continue # Compute bounds on the smallest digit of the number implied as cost. cost = posting.cost if cost is not None: cost_currency = cost.currency if isinstance(cost, Cost): cost_tolerance = min(tolerance * cost.number, MAXIMUM_TOLERANCE) else: assert isinstance(cost, CostSpec) cost_tolerance = MAXIMUM_TOLERANCE for cost_number in cost.number_total, cost.number_per: if cost_number is None or cost_number is MISSING: continue cost_tolerance = min(tolerance * cost_number, cost_tolerance) cost_tolerances[cost_currency] += cost_tolerance # Compute bounds on the smallest digit of the number implied as cost. price = posting.price if isinstance(price, Amount) and isinstance(price.number, Decimal): price_currency = price.currency price_tolerance = min(tolerance * price.number, MAXIMUM_TOLERANCE) cost_tolerances[price_currency] += price_tolerance for currency, tolerance in cost_tolerances.items(): tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) default = tolerances.pop('*', ZERO) return defdict.ImmutableDictWithDefault(tolerances, default=default) beancount.core.interpolate.is_tolerance_user_specified(tolerance) \uf0c1 Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Parameters: tolerance \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/interpolate.py def is_tolerance_user_specified(tolerance): \"\"\"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Args: tolerance: An instance of Decimal. Returns: A boolean. \"\"\" return len(tolerance.as_tuple().digits) < MAX_TOLERANCE_DIGITS beancount.core.interpolate.quantize_with_tolerance(tolerances, currency, number) \uf0c1 Quantize the units using the tolerance dict. Parameters: tolerances \u2013 A dict of currency to tolerance Decimalvalues. number \u2013 A number to quantize. currency \u2013 A string currency. Returns: A Decimal, the number possibly quantized. Source code in beancount/core/interpolate.py def quantize_with_tolerance(tolerances, currency, number): \"\"\"Quantize the units using the tolerance dict. Args: tolerances: A dict of currency to tolerance Decimalvalues. number: A number to quantize. currency: A string currency. Returns: A Decimal, the number possibly quantized. \"\"\" # Applying rounding to the default tolerance, if there is one. tolerance = tolerances.get(currency) if tolerance: quantum = (tolerance * 2).normalize() # If the tolerance is a neat number provided by the user, # quantize the inferred numbers. See doc on quantize(): # # Unlike other operations, if the length of the coefficient # after the quantize operation would be greater than # precision, then an InvalidOperation is signaled. This # guarantees that, unless there is an error condition, the # quantized exponent is always equal to that of the # right-hand operand. if is_tolerance_user_specified(quantum): number = number.quantize(quantum) return number beancount.core.inventory \uf0c1 A container for an inventory of positions. This module provides a container class that can hold positions. An inventory is a mapping of positions, where each position is keyed by (currency: str, cost: Cost) -> position: Position where 'currency': The commodity under consideration, USD, CAD, or stock units such as HOOL, MSFT, AAPL, etc.; 'cost': None or a Cost instance existing of cost currency, number, date, and label; 'position': A Position object, whose 'units' attribute is guaranteed to have the same currency as 'currency' and whose 'cost' attribute is equal to the 'cost' key. It basically stores the number of units. This is meant to accommodate both booked and non-booked amounts. The clever trick that we pull to do this is that for positions which aren't booked, we simply leave the 'cost' as None. This is the case for most of the transactions. beancount.core.inventory.Booking ( Enum ) \uf0c1 Result of booking a new lot to an existing inventory. beancount.core.inventory.Inventory ( dict ) \uf0c1 An Inventory is a set of positions. Attributes: Name Type Description positions A list of Position instances, held in this Inventory object. beancount.core.inventory.Inventory.__abs__(self) special \uf0c1 Return an inventory with the absolute value of each position. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __abs__(self): \"\"\"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. \"\"\" return Inventory({key: abs(pos) for key, pos in self.items()}) beancount.core.inventory.Inventory.__add__(self, other) special \uf0c1 Add another inventory to this one. This inventory is not modified. Parameters: other \u2013 An instance of Inventory. Returns: A new instance of Inventory. Source code in beancount/core/inventory.py def __add__(self, other): \"\"\"Add another inventory to this one. This inventory is not modified. Args: other: An instance of Inventory. Returns: A new instance of Inventory. \"\"\" new_inventory = self.__copy__() new_inventory.add_inventory(other) return new_inventory beancount.core.inventory.Inventory.__copy__(self) special \uf0c1 A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. Source code in beancount/core/inventory.py def __copy__(self): \"\"\"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. \"\"\" return Inventory(self) beancount.core.inventory.Inventory.__iadd__(self, other) special \uf0c1 Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self beancount.core.inventory.Inventory.__init__(self, positions=None) special \uf0c1 Create a new inventory using a list of existing positions. Parameters: positions \u2013 A list of Position instances or an existing dict or Inventory instance. Source code in beancount/core/inventory.py def __init__(self, positions=None): \"\"\"Create a new inventory using a list of existing positions. Args: positions: A list of Position instances or an existing dict or Inventory instance. \"\"\" if isinstance(positions, (dict, Inventory)): dict.__init__(self, positions) else: dict.__init__(self) if positions: assert isinstance(positions, Iterable) for position in positions: self.add_position(position) beancount.core.inventory.Inventory.__iter__(self) special \uf0c1 Iterate over the positions. Note that there is no guaranteed order. Source code in beancount/core/inventory.py def __iter__(self): \"\"\"Iterate over the positions. Note that there is no guaranteed order.\"\"\" return iter(self.values()) beancount.core.inventory.Inventory.__lt__(self, other) special \uf0c1 Inequality comparison operator. Source code in beancount/core/inventory.py def __lt__(self, other): \"\"\"Inequality comparison operator.\"\"\" return sorted(self) < sorted(other) beancount.core.inventory.Inventory.__mul__(self, scalar) special \uf0c1 Scale/multiply the contents of the inventory. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the inventory. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Inventory({key: pos * scalar for key, pos in self.items()}) beancount.core.inventory.Inventory.__neg__(self) special \uf0c1 Return an inventory with the negative of values of this one. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __neg__(self): \"\"\"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. \"\"\" return Inventory({key: -pos for key, pos in self.items()}) beancount.core.inventory.Inventory.__repr__(self) special \uf0c1 Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string() beancount.core.inventory.Inventory.__str__(self) special \uf0c1 Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string() beancount.core.inventory.Inventory.add_amount(self, units, cost=None) \uf0c1 Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Parameters: units \u2013 An Amount instance to add. cost \u2013 An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. Source code in beancount/core/inventory.py def add_amount(self, units, cost=None): \"\"\"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Args: units: An Amount instance to add. cost: An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. \"\"\" if ASSERTS_TYPES: assert isinstance(units, Amount), ( \"Internal error: {!r} (type: {})\".format(units, type(units).__name__)) assert cost is None or isinstance(cost, Cost), ( \"Internal error: {!r} (type: {})\".format(cost, type(cost).__name__)) # Find the position. key = (units.currency, cost) pos = self.get(key, None) if pos is not None: # Note: In order to augment or reduce, all the fields have to match. # Check if reducing. booking = (Booking.REDUCED if not same_sign(pos.units.number, units.number) else Booking.AUGMENTED) # Compute the new number of units. number = pos.units.number + units.number if number == ZERO: # If empty, delete the position. del self[key] else: # Otherwise update it. self[key] = Position(Amount(number, units.currency), cost) else: # If not found, create a new one. if units.number == ZERO: booking = Booking.IGNORED else: self[key] = Position(units, cost) booking = Booking.CREATED return pos, booking beancount.core.inventory.Inventory.add_inventory(self, other) \uf0c1 Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self beancount.core.inventory.Inventory.add_position(self, position) \uf0c1 Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Parameters: position \u2013 The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Source code in beancount/core/inventory.py def add_position(self, position): \"\"\"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Args: position: The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. \"\"\" if ASSERTS_TYPES: assert hasattr(position, 'units') and hasattr(position, 'cost'), ( \"Invalid type for position: {}\".format(position)) assert isinstance(position.cost, (type(None), Cost)), ( \"Invalid type for cost: {}\".format(position.cost)) return self.add_amount(position.units, position.cost) beancount.core.inventory.Inventory.average(self) \uf0c1 Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def average(self): \"\"\"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. \"\"\" groups = collections.defaultdict(list) for position in self: key = (position.units.currency, position.cost.currency if position.cost else None) groups[key].append(position) average_inventory = Inventory() for (currency, cost_currency), positions in groups.items(): total_units = sum(position.units.number for position in positions) # Explicitly skip aggregates when resulting in zero units. if total_units == ZERO: continue units_amount = Amount(total_units, currency) if cost_currency: total_cost = sum(convert.get_cost(position).number for position in positions) cost_number = (Decimal('Infinity') if total_units == ZERO else (total_cost / total_units)) min_date = None for pos in positions: pos_date = pos.cost.date if pos.cost else None if pos_date is not None: min_date = (pos_date if min_date is None else min(min_date, pos_date)) cost = Cost(cost_number, cost_currency, min_date, None) else: cost = None average_inventory.add_amount(units_amount, cost) return average_inventory beancount.core.inventory.Inventory.cost_currencies(self) \uf0c1 Return the list of unit currencies held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def cost_currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. \"\"\" return set(cost.currency for _, cost in self.keys() if cost is not None) beancount.core.inventory.Inventory.currencies(self) \uf0c1 Return the list of unit currencies held in this inventory. Returns: A list of currency strings. Source code in beancount/core/inventory.py def currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. \"\"\" return set(currency for currency, _ in self.keys()) beancount.core.inventory.Inventory.currency_pairs(self) \uf0c1 Return the commodities held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def currency_pairs(self): \"\"\"Return the commodities held in this inventory. Returns: A set of currency strings. \"\"\" return set(position.currency_pair() for position in self) beancount.core.inventory.Inventory.from_string(string) staticmethod \uf0c1 Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory beancount.core.inventory.Inventory.get_currency_units(self, currency) \uf0c1 Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Parameters: currency \u2013 A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. Source code in beancount/core/inventory.py def get_currency_units(self, currency): \"\"\"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Args: currency: A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. \"\"\" total_units = ZERO for position in self: if position.units.currency == currency: total_units += position.units.number return Amount(total_units, currency) beancount.core.inventory.Inventory.get_only_position(self) \uf0c1 Return the first position and assert there are no more. If the inventory is empty, return None. Source code in beancount/core/inventory.py def get_only_position(self): \"\"\"Return the first position and assert there are no more. If the inventory is empty, return None. \"\"\" if len(self) > 0: if len(self) > 1: raise AssertionError(\"Inventory has more than one expected \" \"position: {}\".format(self)) return next(iter(self)) beancount.core.inventory.Inventory.get_positions(self) \uf0c1 Return the positions in this inventory. Returns: A shallow copy of the list of positions. Source code in beancount/core/inventory.py def get_positions(self): \"\"\"Return the positions in this inventory. Returns: A shallow copy of the list of positions. \"\"\" return list(iter(self)) beancount.core.inventory.Inventory.is_empty(self) \uf0c1 Return true if the inventory is empty, that is, has no positions. Returns: A boolean. Source code in beancount/core/inventory.py def is_empty(self): \"\"\"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. \"\"\" return len(self) == 0 beancount.core.inventory.Inventory.is_mixed(self) \uf0c1 Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. Source code in beancount/core/inventory.py def is_mixed(self): \"\"\"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. \"\"\" signs_map = {} for position in self: sign = position.units.number >= 0 prev_sign = signs_map.setdefault(position.units.currency, sign) if sign != prev_sign: return True return False beancount.core.inventory.Inventory.is_reduced_by(self, ramount) \uf0c1 Return true if the amount could reduce this inventory. Parameters: ramount \u2013 An instance of Amount. Returns: A boolean. Source code in beancount/core/inventory.py def is_reduced_by(self, ramount): \"\"\"Return true if the amount could reduce this inventory. Args: ramount: An instance of Amount. Returns: A boolean. \"\"\" if ramount.number == ZERO: return False for position in self: units = position.units if (ramount.currency == units.currency and not same_sign(ramount.number, units.number)): return True return False beancount.core.inventory.Inventory.is_small(self, tolerances) \uf0c1 Return true if all the positions in the inventory are small. Parameters: tolerances \u2013 A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. Source code in beancount/core/inventory.py def is_small(self, tolerances): \"\"\"Return true if all the positions in the inventory are small. Args: tolerances: A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. \"\"\" if isinstance(tolerances, dict): for position in self: tolerance = tolerances.get(position.units.currency, ZERO) if abs(position.units.number) > tolerance: return False small = True else: small = not any(abs(position.units.number) > tolerances for position in self) return small beancount.core.inventory.Inventory.reduce(self, reducer, *args) \uf0c1 Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def reduce(self, reducer, *args): \"\"\"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. \"\"\" inventory = Inventory() for position in self: inventory.add_amount(reducer(position, *args)) return inventory beancount.core.inventory.Inventory.segregate_units(self, currencies) \uf0c1 Split up the list of positions to the given currencies. Parameters: currencies \u2013 A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. Source code in beancount/core/inventory.py def segregate_units(self, currencies): \"\"\"Split up the list of positions to the given currencies. Args: currencies: A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. \"\"\" per_currency_dict = {currency: Inventory() for currency in currencies} per_currency_dict[None] = Inventory() for position in self: currency = position.units.currency key = (currency if currency in currencies else None) per_currency_dict[key].add_position(position) return per_currency_dict beancount.core.inventory.Inventory.to_string(self, dformat=, parens=True) \uf0c1 Convert an Inventory instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. parents \u2013 A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/inventory.py def to_string(self, dformat=DEFAULT_FORMATTER, parens=True): \"\"\"Convert an Inventory instance to a printable string. Args: dformat: An instance of DisplayFormatter. parents: A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. \"\"\" fmt = '({})' if parens else '{}' return fmt.format( ', '.join(pos.to_string(dformat) for pos in sorted(self))) beancount.core.inventory.check_invariants(inv) \uf0c1 Check the invariants of the Inventory. Parameters: inventory \u2013 An instance of Inventory. Returns: True if the invariants are respected. Source code in beancount/core/inventory.py def check_invariants(inv): \"\"\"Check the invariants of the Inventory. Args: inventory: An instance of Inventory. Returns: True if the invariants are respected. \"\"\" # Check that all the keys are unique. lots = set((pos.units.currency, pos.cost) for pos in inv) assert len(lots) == len(inv), \"Invalid inventory: {}\".format(inv) # Check that none of the amounts is zero. for pos in inv: assert pos.units.number != ZERO, \"Invalid position size: {}\".format(pos) beancount.core.inventory.from_string(string) \uf0c1 Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory beancount.core.number \uf0c1 The module contains the basic Decimal type import. About Decimal usage: Do not import Decimal from the 'decimal' or 'cdecimal' modules; always import your Decimal class from beancount.core.amount. Prefer to use D() to create new instances of Decimal objects, which handles more syntax, e.g., handles None, and numbers with commas. beancount.core.number.D(strord=None) \uf0c1 Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Parameters: strord \u2013 A string or Decimal instance. Returns: A Decimal instance. Source code in beancount/core/number.py def D(strord=None): \"\"\"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Args: strord: A string or Decimal instance. Returns: A Decimal instance. \"\"\" try: # Note: try a map lookup and optimize performance here. if strord is None or strord == '': return Decimal() elif isinstance(strord, str): return Decimal(_CLEAN_NUMBER_RE.sub('', strord)) elif isinstance(strord, Decimal): return strord elif isinstance(strord, (int, float)): return Decimal(strord) else: assert strord is None, \"Invalid value to convert: {}\".format(strord) except Exception as exc: raise ValueError(\"Impossible to create Decimal instance from {!s}: {}\".format( strord, exc)) beancount.core.number.is_fast_decimal(decimal_module) \uf0c1 Return true if a fast C decimal implementation is installed. Source code in beancount/core/number.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType) beancount.core.number.round_to(number, increment) \uf0c1 Round a number down to a particular increment. Parameters: number \u2013 A Decimal, the number to be rounded. increment \u2013 A Decimal, the size of the increment. Returns: A Decimal, the rounded number. Source code in beancount/core/number.py def round_to(number, increment): \"\"\"Round a number *down* to a particular increment. Args: number: A Decimal, the number to be rounded. increment: A Decimal, the size of the increment. Returns: A Decimal, the rounded number. \"\"\" return int((number / increment)) * increment beancount.core.number.same_sign(number1, number2) \uf0c1 Return true if both numbers have the same sign. Parameters: number1 \u2013 An instance of Decimal. number2 \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/number.py def same_sign(number1, number2): \"\"\"Return true if both numbers have the same sign. Args: number1: An instance of Decimal. number2: An instance of Decimal. Returns: A boolean. \"\"\" return (number1 >= 0) == (number2 >= 0) beancount.core.position \uf0c1 A position object, which consists of units Amount and cost Cost. See types below for details. beancount.core.position.Cost ( tuple ) \uf0c1 Cost(number, currency, date, label) beancount.core.position.Cost.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.position.Cost.__new__(_cls, number, currency, date, label) special staticmethod \uf0c1 Create new instance of Cost(number, currency, date, label) beancount.core.position.Cost.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.position.CostSpec ( tuple ) \uf0c1 CostSpec(number_per, number_total, currency, date, label, merge) beancount.core.position.CostSpec.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.core.position.CostSpec.__new__(_cls, number_per, number_total, currency, date, label, merge) special staticmethod \uf0c1 Create new instance of CostSpec(number_per, number_total, currency, date, label, merge) beancount.core.position.CostSpec.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.core.position.Position ( _Position ) \uf0c1 A 'Position' is a pair of units and optional cost. This is used to track inventories. Attributes: Name Type Description units Amount An Amount, the number of units and its currency. cost Cost A Cost that represents the lot, or None. beancount.core.position.Position.__abs__(self) special \uf0c1 Return the absolute value of the position. Returns: An instance of Position with the absolute units. Source code in beancount/core/position.py def __abs__(self): \"\"\"Return the absolute value of the position. Returns: An instance of Position with the absolute units. \"\"\" return Position(amount_abs(self.units), self.cost) beancount.core.position.Position.__copy__(self) special \uf0c1 Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. Source code in beancount/core/position.py def __copy__(self): \"\"\"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. \"\"\" # Note: We use Decimal() for efficiency. return Position(copy.copy(self.units), copy.copy(self.cost)) beancount.core.position.Position.__eq__(self, other) special \uf0c1 Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Parameters: other \u2013 An instance of Position, or None. Returns: A boolean, true if the positions are equal. Source code in beancount/core/position.py def __eq__(self, other): \"\"\"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Args: other: An instance of Position, or None. Returns: A boolean, true if the positions are equal. \"\"\" return (self.units.number == ZERO if other is None else (self.units == other.units and self.cost == other.cost)) beancount.core.position.Position.__hash__(self) special \uf0c1 Compute a hash for this position. Returns: A hash of this position object. Source code in beancount/core/position.py def __hash__(self): \"\"\"Compute a hash for this position. Returns: A hash of this position object. \"\"\" return hash((self.units, self.cost)) beancount.core.position.Position.__lt__(self, other) special \uf0c1 A less-than comparison operator for positions. Parameters: other \u2013 Another instance of Position. Returns: True if this positions is smaller than the other position. Source code in beancount/core/position.py def __lt__(self, other): \"\"\"A less-than comparison operator for positions. Args: other: Another instance of Position. Returns: True if this positions is smaller than the other position. \"\"\" return self.sortkey() < other.sortkey() beancount.core.position.Position.__mul__(self, scalar) special \uf0c1 Scale/multiply the contents of the position. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/position.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the position. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Position(amount_mul(self.units, scalar), self.cost) beancount.core.position.Position.__neg__(self) special \uf0c1 Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost) beancount.core.position.Position.__new__(cls, units, cost=None) special staticmethod \uf0c1 Create new instance of _Position(units, cost) Source code in beancount/core/position.py def __new__(cls, units, cost=None): assert isinstance(units, Amount), ( \"Expected an Amount for units; received '{}'\".format(units)) assert cost is None or isinstance(cost, Position.cost_types), ( \"Expected a Cost for cost; received '{}'\".format(cost)) return _Position.__new__(cls, units, cost) beancount.core.position.Position.__repr__(self) special \uf0c1 Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string() beancount.core.position.Position.__str__(self) special \uf0c1 Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string() beancount.core.position.Position.currency_pair(self) \uf0c1 Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. Source code in beancount/core/position.py def currency_pair(self): \"\"\"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. \"\"\" return (self.units.currency, self.cost.currency if self.cost else None) beancount.core.position.Position.from_amounts(units, cost_amount=None) staticmethod \uf0c1 Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost) beancount.core.position.Position.from_string(string) staticmethod \uf0c1 Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost) beancount.core.position.Position.get_negative(self) \uf0c1 Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost) beancount.core.position.Position.is_negative_at_cost(self) \uf0c1 Return true if the position is held at cost and negative. Returns: A boolean. Source code in beancount/core/position.py def is_negative_at_cost(self): \"\"\"Return true if the position is held at cost and negative. Returns: A boolean. \"\"\" return (self.units.number < ZERO and self.cost is not None) beancount.core.position.Position.sortkey(self) \uf0c1 Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. Source code in beancount/core/position.py def sortkey(self): \"\"\"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. \"\"\" currency = self.units.currency order_units = CURRENCY_ORDER.get(currency, NCURRENCIES + len(currency)) if self.cost is not None: cost_number = self.cost.number cost_currency = self.cost.currency else: cost_number = ZERO cost_currency = '' return (order_units, cost_number, cost_currency, self.units.number) beancount.core.position.Position.to_string(self, dformat=, detail=True) \uf0c1 Render the position to a string.See to_string() for details. Source code in beancount/core/position.py def to_string(self, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the position to a string.See to_string() for details. \"\"\" return to_string(self, dformat, detail) beancount.core.position.cost_to_str(cost, dformat, detail=True) \uf0c1 Format an instance of Cost or a CostSpec to a string. Parameters: cost \u2013 An instance of Cost or CostSpec. dformat \u2013 A DisplayFormatter object. detail \u2013 A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. Source code in beancount/core/position.py def cost_to_str(cost, dformat, detail=True): \"\"\"Format an instance of Cost or a CostSpec to a string. Args: cost: An instance of Cost or CostSpec. dformat: A DisplayFormatter object. detail: A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. \"\"\" strlist = [] if isinstance(cost, Cost): if isinstance(cost.number, Decimal): strlist.append(Amount(cost.number, cost.currency).to_string(dformat)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) elif isinstance(cost, CostSpec): if isinstance(cost.number_per, Decimal) or isinstance(cost.number_total, Decimal): amountlist = [] if isinstance(cost.number_per, Decimal): amountlist.append(dformat.format(cost.number_per)) if isinstance(cost.number_total, Decimal): amountlist.append('#') amountlist.append(dformat.format(cost.number_total)) if isinstance(cost.currency, str): amountlist.append(cost.currency) strlist.append(' '.join(amountlist)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) if cost.merge: strlist.append('*') return ', '.join(strlist) beancount.core.position.from_amounts(units, cost_amount=None) \uf0c1 Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost) beancount.core.position.from_string(string) \uf0c1 Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost) beancount.core.position.get_position(posting) \uf0c1 Build a Position instance from a Posting instance. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Position. Source code in beancount/core/position.py def get_position(posting): \"\"\"Build a Position instance from a Posting instance. Args: posting: An instance of Posting. Returns: An instance of Position. \"\"\" return Position(posting.units, posting.cost) beancount.core.position.to_string(pos, dformat=, detail=True) \uf0c1 Render the Position or Posting instance to a string. Parameters: pos \u2013 An instance of Position or Posting. dformat \u2013 An instance of DisplayFormatter. detail \u2013 A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. Source code in beancount/core/position.py def to_string(pos, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the Position or Posting instance to a string. Args: pos: An instance of Position or Posting. dformat: An instance of DisplayFormatter. detail: A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. \"\"\" pos_str = pos.units.to_string(dformat) if pos.cost is not None: pos_str = '{} {{{}}}'.format(pos_str, cost_to_str(pos.cost, dformat, detail)) return pos_str beancount.core.prices \uf0c1 This module has code that can build a database of historical prices at various times, from which unrealized capital gains and market value can be deduced. Prices are deduced from Price entries found in the file, or perhaps created by scripts (for example you could build a script that will fetch live prices online and create entries on-the-fly). beancount.core.prices.PriceMap ( dict ) \uf0c1 A price map dictionary. The keys include both the set of forward (base, quote) pairs and their inverse. In order to determine which are the forward pairs, access the 'forward_pairs' attribute Attributes: Name Type Description forward_pairs A list of (base, quote) keys for the forward pairs. beancount.core.prices.build_price_map(entries) \uf0c1 Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Parameters: entries \u2013 A list of directives, hopefully including some Price and/or Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. Source code in beancount/core/prices.py def build_price_map(entries): \"\"\"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Args: entries: A list of directives, hopefully including some Price and/or Transaction entries. Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. \"\"\" # Fetch a list of all the price entries seen in the ledger. price_entries = [entry for entry in entries if isinstance(entry, Price)] # Build a map of exchange rates between these units. # (base-currency, quote-currency) -> List of (date, rate). price_map = collections.defaultdict(list) for price in price_entries: base_quote = (price.currency, price.amount.currency) price_map[base_quote].append((price.date, price.amount.number)) # Find pairs of inversed units. inversed_units = [] for base_quote, values in price_map.items(): base, quote = base_quote if (quote, base) in price_map: inversed_units.append(base_quote) # Find pairs of inversed units, and swallow the conversion with the smaller # number of rates into the other one. for base, quote in inversed_units: bq_prices = price_map[(base, quote)] qb_prices = price_map[(quote, base)] remove = ((base, quote) if len(bq_prices) < len(qb_prices) else (quote, base)) base, quote = remove remove_list = price_map[remove] insert_list = price_map[(quote, base)] del price_map[remove] inverted_list = [(date, ONE/rate) for (date, rate) in remove_list if rate != ZERO] insert_list.extend(inverted_list) # Unzip and sort each of the entries and eliminate duplicates on the date. sorted_price_map = PriceMap({ base_quote: list(misc_utils.sorted_uniquify(date_rates, lambda x: x[0], last=True)) for (base_quote, date_rates) in price_map.items()}) # Compute and insert all the inverted rates. forward_pairs = list(sorted_price_map.keys()) for (base, quote), price_list in list(sorted_price_map.items()): # Note: You have to filter out zero prices for zero-cost postings, like # gifted options. sorted_price_map[(quote, base)] = [ (date, ONE/price) for date, price in price_list if price != ZERO] sorted_price_map.forward_pairs = forward_pairs return sorted_price_map beancount.core.prices.get_all_prices(price_map, base_quote) \uf0c1 Return a sorted list of all (date, number) price pairs. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Exceptions: KeyError \u2013 If the base/quote could not be found. Source code in beancount/core/prices.py def get_all_prices(price_map, base_quote): \"\"\"Return a sorted list of all (date, number) price pairs. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Raises: KeyError: If the base/quote could not be found. \"\"\" base_quote = normalize_base_quote(base_quote) return _lookup_price_and_inverse(price_map, base_quote) beancount.core.prices.get_last_price_entries(entries, date) \uf0c1 Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Parameters: entries \u2013 A list of directives. date \u2013 An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. Source code in beancount/core/prices.py def get_last_price_entries(entries, date): \"\"\"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Args: entries: A list of directives. date: An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. \"\"\" price_entry_map = {} for entry in entries: if date is not None and entry.date >= date: break if isinstance(entry, Price): base_quote = (entry.currency, entry.amount.currency) price_entry_map[base_quote] = entry return sorted(price_entry_map.values(), key=data.entry_sortkey) beancount.core.prices.get_latest_price(price_map, base_quote) \uf0c1 Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. Source code in beancount/core/prices.py def get_latest_price(price_map, base_quote): \"\"\"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. \"\"\" base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) # Look up the list and return the latest element. The lists are assumed to # be sorted. try: price_list = _lookup_price_and_inverse(price_map, base_quote) except KeyError: price_list = None if price_list: return price_list[-1] else: return None, None beancount.core.prices.get_price(price_map, base_quote, date=None) \uf0c1 Return the price as of the given date. If the date is unspecified, return the latest price. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date \u2013 A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). Source code in beancount/core/prices.py def get_price(price_map, base_quote, date=None): \"\"\"Return the price as of the given date. If the date is unspecified, return the latest price. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date: A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). \"\"\" if date is None: return get_latest_price(price_map, base_quote) base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) try: price_list = _lookup_price_and_inverse(price_map, base_quote) index = bisect_key.bisect_right_with_key(price_list, date, key=lambda x: x[0]) if index == 0: return None, None else: return price_list[index-1] except KeyError: return None, None beancount.core.prices.normalize_base_quote(base_quote) \uf0c1 Convert a slash-separated string to a pair of strings. Parameters: base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. Source code in beancount/core/prices.py def normalize_base_quote(base_quote): \"\"\"Convert a slash-separated string to a pair of strings. Args: base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. \"\"\" if isinstance(base_quote, str): base_quote_norm = tuple(base_quote.split('/')) assert len(base_quote_norm) == 2, base_quote base_quote = base_quote_norm assert isinstance(base_quote, tuple), base_quote return base_quote beancount.core.realization \uf0c1 Realization of specific lists of account postings into reports. This code converts a list of entries into a tree of RealAccount nodes (which stands for \"realized accounts\"). The RealAccount objects contain lists of Posting instances instead of Transactions, or other entry types that are attached to an account, such as a Balance or Note entry. The interface of RealAccount corresponds to that of a regular Python dict, where the keys are the names of the individual components of an account's name, and the values are always other RealAccount instances. If you want to get an account by long account name, there are helper functions in this module for this purpose (see realization.get(), for instance). RealAccount instances also contain the final balance of that account, resulting from its list of postings. You should not build RealAccount trees yourself; instead, you should filter the list of desired directives to display and call the realize() function with them. beancount.core.realization.RealAccount ( dict ) \uf0c1 A realized account, inserted in a tree, that contains the list of realized entries. Attributes: Name Type Description account A string, the full name of the corresponding account. postings A list of postings associated with this accounting (does not include the postings of children accounts). balance The final balance of the list of postings associated with this account. beancount.core.realization.RealAccount.__eq__(self, other) special \uf0c1 Equality predicate. All attributes are compared. Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. Source code in beancount/core/realization.py def __eq__(self, other): \"\"\"Equality predicate. All attributes are compared. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. \"\"\" return (dict.__eq__(self, other) and self.account == other.account and self.balance == other.balance and self.txn_postings == other.txn_postings) beancount.core.realization.RealAccount.__init__(self, account_name, *args, **kwargs) special \uf0c1 Create a RealAccount instance. Parameters: account_name \u2013 a string, the name of the account. Maybe not be None. Source code in beancount/core/realization.py def __init__(self, account_name, *args, **kwargs): \"\"\"Create a RealAccount instance. Args: account_name: a string, the name of the account. Maybe not be None. \"\"\" super().__init__(*args, **kwargs) assert isinstance(account_name, str) self.account = account_name self.txn_postings = [] self.balance = inventory.Inventory() beancount.core.realization.RealAccount.__ne__(self, other) special \uf0c1 Not-equality predicate. See eq . Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. Source code in beancount/core/realization.py def __ne__(self, other): \"\"\"Not-equality predicate. See __eq__. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. \"\"\" return not self.__eq__(other) beancount.core.realization.RealAccount.__setitem__(self, key, value) special \uf0c1 Prevent the setting of non-string or non-empty keys on this dict. Parameters: key \u2013 The dictionary key. Must be a string. value \u2013 The value, must be a RealAccount instance. Exceptions: KeyError \u2013 If the key is not a string, or is invalid. ValueError \u2013 If the value is not a RealAccount instance. Source code in beancount/core/realization.py def __setitem__(self, key, value): \"\"\"Prevent the setting of non-string or non-empty keys on this dict. Args: key: The dictionary key. Must be a string. value: The value, must be a RealAccount instance. Raises: KeyError: If the key is not a string, or is invalid. ValueError: If the value is not a RealAccount instance. \"\"\" if not isinstance(key, str) or not key: raise KeyError(\"Invalid RealAccount key: '{}'\".format(key)) if not isinstance(value, RealAccount): raise ValueError(\"Invalid RealAccount value: '{}'\".format(value)) if not value.account.endswith(key): raise ValueError(\"RealAccount name '{}' inconsistent with key: '{}'\".format( value.account, key)) return super().__setitem__(key, value) beancount.core.realization.RealAccount.copy(self) \uf0c1 Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. Source code in beancount/core/realization.py def copy(self): \"\"\"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. \"\"\" return copy.copy(self) beancount.core.realization.compute_balance(real_account, leaf_only=False) \uf0c1 Compute the total balance of this account and all its subaccounts. Parameters: real_account \u2013 A RealAccount instance. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Returns: An Inventory. Source code in beancount/core/realization.py def compute_balance(real_account, leaf_only=False): \"\"\"Compute the total balance of this account and all its subaccounts. Args: real_account: A RealAccount instance. leaf_only: A boolean flag, true if we should yield only leaves. Returns: An Inventory. \"\"\" return functools.reduce(operator.add, [ ra.balance for ra in iter_children(real_account, leaf_only)]) beancount.core.realization.compute_postings_balance(txn_postings) \uf0c1 Compute the balance of a list of Postings's or TxnPosting's positions. Parameters: postings \u2013 A list of Posting instances and other directives (which are skipped). Returns: An Inventory. Source code in beancount/core/realization.py def compute_postings_balance(txn_postings): \"\"\"Compute the balance of a list of Postings's or TxnPosting's positions. Args: postings: A list of Posting instances and other directives (which are skipped). Returns: An Inventory. \"\"\" final_balance = inventory.Inventory() for txn_posting in txn_postings: if isinstance(txn_posting, Posting): final_balance.add_position(txn_posting) elif isinstance(txn_posting, TxnPosting): final_balance.add_position(txn_posting.posting) return final_balance beancount.core.realization.contains(real_account, account_name) \uf0c1 True if the given account node contains the subaccount name. Parameters: account_name \u2013 A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. Source code in beancount/core/realization.py def contains(real_account, account_name): \"\"\"True if the given account node contains the subaccount name. Args: account_name: A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. \"\"\" return get(real_account, account_name) is not None beancount.core.realization.dump(root_account) \uf0c1 Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Parameters: root_account \u2013 A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. Source code in beancount/core/realization.py def dump(root_account): \"\"\"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Args: root_account: A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root_account.account, root_account, True)] while stack: prefix, name, real_account, is_last = stack.pop(-1) if real_account is root_account: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(real_account) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (real_account is root_account and not name): lines.append((first + name, cont + cont_name, real_account)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. child_items = sorted(real_account.items(), reverse=True) if child_items: child_iter = iter(child_items) child_name, child_account = next(child_iter) stack.append((cont, child_name, child_account, True)) for child_name, child_account in child_iter: stack.append((cont, child_name, child_account, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), real_node) for (first_line, cont_line, real_node) in lines] beancount.core.realization.dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None) \uf0c1 Dump a realization tree with balances. Parameters: real_root \u2013 An instance of RealAccount. dformat \u2013 An instance of DisplayFormatter to format the numbers with. at_cost \u2013 A boolean, if true, render the values at cost. fullnames \u2013 A boolean, if true, don't render a tree of accounts and render the full account names. file \u2013 A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. Source code in beancount/core/realization.py def dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None): \"\"\"Dump a realization tree with balances. Args: real_root: An instance of RealAccount. dformat: An instance of DisplayFormatter to format the numbers with. at_cost: A boolean, if true, render the values at cost. fullnames: A boolean, if true, don't render a tree of accounts and render the full account names. file: A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. \"\"\" if fullnames: # Compute the maximum account name length; maxlen = max(len(real_child.account) for real_child in iter_children(real_root, leaf_only=True)) line_format = '{{:{width}}} {{}}\\n'.format(width=maxlen) else: line_format = '{} {}\\n' output = file or io.StringIO() for first_line, cont_line, real_account in dump(real_root): if not real_account.balance.is_empty(): if at_cost: rinv = real_account.balance.reduce(convert.get_cost) else: rinv = real_account.balance.reduce(convert.get_units) amounts = [position.units for position in rinv.get_positions()] positions = [amount_.to_string(dformat) for amount_ in sorted(amounts, key=amount.sortkey)] else: positions = [''] if fullnames: for position in positions: if not position and len(real_account) > 0: continue # Skip parent accounts with no position to render. output.write(line_format.format(real_account.account, position)) else: line = first_line for position in positions: output.write(line_format.format(line, position)) line = cont_line if file is None: return output.getvalue() beancount.core.realization.filter(real_account, predicate) \uf0c1 Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Parameters: real_account \u2013 An instance of RealAccount. predicate \u2013 A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. Source code in beancount/core/realization.py def filter(real_account, predicate): \"\"\"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Args: real_account: An instance of RealAccount. predicate: A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. \"\"\" assert isinstance(real_account, RealAccount) real_copy = RealAccount(real_account.account) real_copy.balance = real_account.balance real_copy.txn_postings = real_account.txn_postings for child_name, real_child in real_account.items(): real_child_copy = filter(real_child, predicate) if real_child_copy is not None: real_copy[child_name] = real_child_copy if len(real_copy) > 0 or predicate(real_account): return real_copy beancount.core.realization.find_last_active_posting(txn_postings) \uf0c1 Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Parameters: txn_postings \u2013 a list of postings or entries. Returns: An entry, or None, if the input list was empty. Source code in beancount/core/realization.py def find_last_active_posting(txn_postings): \"\"\"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Args: txn_postings: a list of postings or entries. Returns: An entry, or None, if the input list was empty. \"\"\" for txn_posting in reversed(txn_postings): assert not isinstance(txn_posting, Posting) if not isinstance(txn_posting, (TxnPosting, Open, Close, Pad, Balance, Note)): continue # pylint: disable=bad-continuation if (isinstance(txn_posting, TxnPosting) and txn_posting.txn.flag == flags.FLAG_UNREALIZED): continue return txn_posting beancount.core.realization.get(real_account, account_name, default=None) \uf0c1 Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of. account_name \u2013 A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default \u2013 The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get(real_account, account_name, default=None): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of. account_name: A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default: The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) for component in components: real_child = real_account.get(component, default) if real_child is default: return default real_account = real_child return real_account beancount.core.realization.get_or_create(real_account, account_name) \uf0c1 Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of, or create under. account_name \u2013 A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get_or_create(real_account, account_name): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of, or create under. account_name: A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) path = [] for component in components: path.append(component) real_child = real_account.get(component, None) if real_child is None: real_child = RealAccount(account.join(*path)) real_account[component] = real_child real_account = real_child return real_account beancount.core.realization.get_postings(real_account) \uf0c1 Return a sorted list a RealAccount's postings and children. Parameters: real_account \u2013 An instance of RealAccount. Returns: A list of Posting or directories. Source code in beancount/core/realization.py def get_postings(real_account): \"\"\"Return a sorted list a RealAccount's postings and children. Args: real_account: An instance of RealAccount. Returns: A list of Posting or directories. \"\"\" # We accumulate all the postings at once here instead of incrementally # because we need to return them sorted. accumulator = [] for real_child in iter_children(real_account): accumulator.extend(real_child.txn_postings) accumulator.sort(key=data.posting_sortkey) return accumulator beancount.core.realization.index_key(sequence, value, key, cmp) \uf0c1 Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Parameters: sequence \u2013 The sequence to search. value \u2013 The value to search for. key \u2013 A predicate to call to obtain the value to compare against. cmp \u2013 A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. Source code in beancount/core/realization.py def index_key(sequence, value, key, cmp): \"\"\"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Args: sequence: The sequence to search. value: The value to search for. key: A predicate to call to obtain the value to compare against. cmp: A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. \"\"\" for index, element in enumerate(sequence): if cmp(key(element), value): return index return beancount.core.realization.iter_children(real_account, leaf_only=False) \uf0c1 Iterate this account node and all its children, depth-first. Parameters: real_account \u2013 An instance of RealAccount. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. Source code in beancount/core/realization.py def iter_children(real_account, leaf_only=False): \"\"\"Iterate this account node and all its children, depth-first. Args: real_account: An instance of RealAccount. leaf_only: A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. \"\"\" if leaf_only: if len(real_account) == 0: yield real_account else: for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child, leaf_only): yield real_subchild else: yield real_account for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child): yield real_subchild beancount.core.realization.iterate_with_balance(txn_postings) \uf0c1 Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance after adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Parameters: txn_postings \u2013 A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. Source code in beancount/core/realization.py def iterate_with_balance(txn_postings): \"\"\"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance *after* adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Args: txn_postings: A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. \"\"\" # The running balance. running_balance = inventory.Inventory() # Previous date. prev_date = None # A list of entries at the current date. date_entries = [] first = lambda pair: pair[0] for txn_posting in txn_postings: # Get the posting if we are dealing with one. assert not isinstance(txn_posting, Posting) if isinstance(txn_posting, TxnPosting): posting = txn_posting.posting entry = txn_posting.txn else: posting = None entry = txn_posting if entry.date != prev_date: assert prev_date is None or entry.date > prev_date, ( \"Invalid date order for postings: {} > {}\".format(prev_date, entry.date)) prev_date = entry.date # Flush the dated entries. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: # Compute the change due to this transaction and update the # total balance at the same time. for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() assert not date_entries if posting is not None: # De-dup multiple postings on the same transaction entry by # grouping their positions together. index = index_key(date_entries, entry, first, operator.is_) if index is None: date_entries.append((entry, [posting])) else: # We are indeed de-duping! postings = date_entries[index][1] postings.append(posting) else: # This is a regular entry; nothing to add/remove. date_entries.append((entry, [])) # Flush the final dated entries if any, same as above. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() beancount.core.realization.postings_by_account(entries) \uf0c1 Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Parameters: entries \u2013 A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. Source code in beancount/core/realization.py def postings_by_account(entries): \"\"\"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Args: entries: A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. \"\"\" txn_postings_map = collections.defaultdict(list) for entry in entries: if isinstance(entry, Transaction): # Insert an entry for each of the postings. for posting in entry.postings: txn_postings_map[posting.account].append( TxnPosting(entry, posting)) elif isinstance(entry, (Open, Close, Balance, Note, Document)): # Append some other entries in the realized list. txn_postings_map[entry.account].append(entry) elif isinstance(entry, Pad): # Insert the pad entry in both realized accounts. txn_postings_map[entry.account].append(entry) txn_postings_map[entry.source_account].append(entry) elif isinstance(entry, Custom): # Insert custom entry for each account in its values. for custom_value in entry.values: if custom_value.dtype == account.TYPE: txn_postings_map[custom_value.value].append(entry) return txn_postings_map beancount.core.realization.realize(entries, min_accounts=None, compute_balance=True) \uf0c1 Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\ v \\.__ +---------+ +-----+ -------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Parameters: entries \u2013 A list of directives. min_accounts \u2013 A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance \u2013 A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. Source code in beancount/core/realization.py def realize(entries, min_accounts=None, compute_balance=True): r\"\"\"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\\\ v `\\.__ +---------+ +-----+ `-------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Args: entries: A list of directives. min_accounts: A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance: A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. \"\"\" # Create lists of the entries by account. txn_postings_map = postings_by_account(entries) # Create a RealAccount tree and compute the balance for each. real_root = RealAccount('') for account_name, txn_postings in txn_postings_map.items(): real_account = get_or_create(real_root, account_name) real_account.txn_postings = txn_postings if compute_balance: real_account.balance = compute_postings_balance(txn_postings) # Ensure a minimum set of accounts that should exist. This is typically # called with an instance of AccountTypes to make sure that those exist. if min_accounts: for account_name in min_accounts: get_or_create(real_root, account_name) return real_root","title":"beancount.core"},{"location":"api_reference/beancount.core.html#beancountcore","text":"Core basic objects and data structures to represent a list of entries.","title":"beancount.core"},{"location":"api_reference/beancount.core.html#beancount.core.account","text":"Functions that operate on account strings. These account objects are rather simple and dumb; they do not contain the list of their associated postings. This is achieved by building a realization; see realization.py for details.","title":"account"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer","text":"Account name transformer. This is used to support Win... huh, filesystems and platforms which do not support colon characters. Attributes: Name Type Description rsep A character string, the new separator to use in link names.","title":"AccountTransformer"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer.parse","text":"Convert the transform account name to an account name. Source code in beancount/core/account.py def parse(self, transformed_name): \"Convert the transform account name to an account name.\" return (transformed_name if self.rsep is None else transformed_name.replace(self.rsep, sep))","title":"parse()"},{"location":"api_reference/beancount.core.html#beancount.core.account.AccountTransformer.render","text":"Convert the account name to a transformed account name. Source code in beancount/core/account.py def render(self, account_name): \"Convert the account name to a transformed account name.\" return (account_name if self.rsep is None else account_name.replace(sep, self.rsep))","title":"render()"},{"location":"api_reference/beancount.core.html#beancount.core.account.commonprefix","text":"Return the common prefix of a list of account names. Parameters: accounts \u2013 A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. Source code in beancount/core/account.py def commonprefix(accounts): \"\"\"Return the common prefix of a list of account names. Args: accounts: A sequence of account name strings. Returns: A string, the common parent account. If none, returns an empty string. \"\"\" accounts_lists = [account_.split(sep) for account_ in accounts] # Note: the os.path.commonprefix() function just happens to work here. # Inspect its code, and even the special case of no common prefix # works well with str.join() below. common_list = path.commonprefix(accounts_lists) return sep.join(common_list)","title":"commonprefix()"},{"location":"api_reference/beancount.core.html#beancount.core.account.has_component","text":"Return true if one of the account contains a given component. Parameters: account_name \u2013 A string, an account name. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/account.py def has_component(account_name, component): \"\"\"Return true if one of the account contains a given component. Args: account_name: A string, an account name. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return bool(re.search('(^|:){}(:|$)'.format(component), account_name))","title":"has_component()"},{"location":"api_reference/beancount.core.html#beancount.core.account.is_valid","text":"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Parameters: string \u2013 A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. Source code in beancount/core/account.py def is_valid(string): \"\"\"Return true if the given string is a valid account name. This does not check for the root account types, just the general syntax. Args: string: A string, to be checked for account name pattern. Returns: A boolean, true if the string has the form of an account's name. \"\"\" return (isinstance(string, str) and bool(re.match('{}$'.format(ACCOUNT_RE), string)))","title":"is_valid()"},{"location":"api_reference/beancount.core.html#beancount.core.account.join","text":"Join the names with the account separator. Parameters: *components \u2013 Strings, the components of an account name. Returns: A string, joined in a single account name. Source code in beancount/core/account.py def join(*components): \"\"\"Join the names with the account separator. Args: *components: Strings, the components of an account name. Returns: A string, joined in a single account name. \"\"\" return sep.join(components)","title":"join()"},{"location":"api_reference/beancount.core.html#beancount.core.account.leaf","text":"Get the name of the leaf of this account. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. Source code in beancount/core/account.py def leaf(account_name): \"\"\"Get the name of the leaf of this account. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the leaf of the account. \"\"\" assert isinstance(account_name, str) return account_name.split(sep)[-1] if account_name else None","title":"leaf()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parent","text":"Return the name of the parent account of the given account. Parameters: account_name \u2013 A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. Source code in beancount/core/account.py def parent(account_name): \"\"\"Return the name of the parent account of the given account. Args: account_name: A string, the name of the account whose parent to return. Returns: A string, the name of the parent account of this account. \"\"\" assert isinstance(account_name, str), account_name if not account_name: return None components = account_name.split(sep) components.pop(-1) return sep.join(components)","title":"parent()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parent_matcher","text":"Build a predicate that returns whether an account is under the given one. Parameters: account_name \u2013 The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of account_name . Source code in beancount/core/account.py def parent_matcher(account_name): \"\"\"Build a predicate that returns whether an account is under the given one. Args: account_name: The name of the parent account we want to check for. Returns: A callable, which, when called, will return true if the given account is a child of ``account_name``. \"\"\" return re.compile(r'{}($|{})'.format(re.escape(account_name), sep)).match","title":"parent_matcher()"},{"location":"api_reference/beancount.core.html#beancount.core.account.parents","text":"A generator of the names of the parents of this account, including this account. Parameters: account_name \u2013 The name of the account we want to start iterating from. Returns: A generator of account name strings. Source code in beancount/core/account.py def parents(account_name): \"\"\"A generator of the names of the parents of this account, including this account. Args: account_name: The name of the account we want to start iterating from. Returns: A generator of account name strings. \"\"\" while account_name: yield account_name account_name = parent(account_name)","title":"parents()"},{"location":"api_reference/beancount.core.html#beancount.core.account.root","text":"Return the first few components of an account's name. Parameters: num_components \u2013 An integer, the number of components to return. account_name \u2013 A string, an account name. Returns: A string, the account root up to 'num_components' components. Source code in beancount/core/account.py def root(num_components, account_name): \"\"\"Return the first few components of an account's name. Args: num_components: An integer, the number of components to return. account_name: A string, an account name. Returns: A string, the account root up to 'num_components' components. \"\"\" return join(*(split(account_name)[:num_components]))","title":"root()"},{"location":"api_reference/beancount.core.html#beancount.core.account.sans_root","text":"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Parameters: account_name \u2013 A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. Source code in beancount/core/account.py def sans_root(account_name): \"\"\"Get the name of the account without the root. For example, an input of 'Assets:BofA:Checking' will produce 'BofA:Checking'. Args: account_name: A string, the name of the account whose leaf name to return. Returns: A string, the name of the non-root portion of this account name. \"\"\" assert isinstance(account_name, str) components = account_name.split(sep)[1:] return join(*components) if account_name else None","title":"sans_root()"},{"location":"api_reference/beancount.core.html#beancount.core.account.split","text":"Split an account's name into its components. Parameters: account_name \u2013 A string, an account name. Returns: A list of strings, the components of the account name (without the separators). Source code in beancount/core/account.py def split(account_name): \"\"\"Split an account's name into its components. Args: account_name: A string, an account name. Returns: A list of strings, the components of the account name (without the separators). \"\"\" return account_name.split(sep)","title":"split()"},{"location":"api_reference/beancount.core.html#beancount.core.account.walk","text":"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Parameters: root_directory \u2013 A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). Source code in beancount/core/account.py def walk(root_directory): \"\"\"A version of os.walk() which yields directories that are valid account names. This only yields directories that are accounts... it skips the other ones. For convenience, it also yields you the account's name. Args: root_directory: A string, the name of the root of the hierarchy to be walked. Yields: Tuples of (root, account-name, dirs, files), similar to os.walk(). \"\"\" for root, dirs, files in os.walk(root_directory): dirs.sort() files.sort() relroot = root[len(root_directory)+1:] account_name = relroot.replace(os.sep, sep) if is_valid(account_name): yield (root, account_name, dirs, files)","title":"walk()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types","text":"Definition for global account types. This is where we keep the global account types value and definition. Note that it's unfortunate that we're using globals and side-effect here, but this is the best solution in the short-term, the account types are used in too many places to pass around that state everywhere. Maybe we change this later on.","title":"account_types"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes","text":"AccountTypes(assets, liabilities, equity, income, expenses)","title":"AccountTypes"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/account_types.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__new__","text":"Create new instance of AccountTypes(assets, liabilities, equity, income, expenses)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.AccountTypes.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/account_types.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_sign","text":"Return the sign of the normal balance of a particular account. Parameters: account_name \u2013 A string, the name of the account whose sign is to return. account_types \u2013 An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. Source code in beancount/core/account_types.py def get_account_sign(account_name, account_types=None): \"\"\"Return the sign of the normal balance of a particular account. Args: account_name: A string, the name of the account whose sign is to return. account_types: An optional instance of the current account_types. Returns: +1 or -1, depending on the account's type. \"\"\" if account_types is None: account_types = DEFAULT_ACCOUNT_TYPES assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) account_type = get_account_type(account_name) return (+1 if account_type in (account_types.assets, account_types.expenses) else -1)","title":"get_account_sign()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_sort_key","text":"Return a tuple that can be used to order/sort account names. Parameters: account_types \u2013 An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. Source code in beancount/core/account_types.py def get_account_sort_key(account_types, account_name): \"\"\"Return a tuple that can be used to order/sort account names. Args: account_types: An instance of AccountTypes, a tuple of account type names. Returns: A function object to use as the optional 'key' argument to the sort function. It accepts a single argument, the account name to sort and produces a sortable key. \"\"\" return (account_types.index(get_account_type(account_name)), account_name)","title":"get_account_sort_key()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.get_account_type","text":"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_name \u2013 A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. Source code in beancount/core/account_types.py def get_account_type(account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_name: A string, the name of the account whose type is to return. Returns: A string, the type of the account in 'account_name'. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) return account.split(account_name)[0]","title":"get_account_type()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_account_type","text":"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Parameters: account_type \u2013 A string, the prefix type of the account. account_name \u2013 A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. Source code in beancount/core/account_types.py def is_account_type(account_type, account_name): \"\"\"Return the type of this account's name. Warning: No check is made on the validity of the account type. This merely returns the root account of the corresponding account name. Args: account_type: A string, the prefix type of the account. account_name: A string, the name of the account whose type is to return. Returns: A boolean, true if the account is of the given type. \"\"\" return bool(re.match('^{}{}'.format(account_type, account.sep), account_name))","title":"is_account_type()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_balance_sheet_account","text":"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. Source code in beancount/core/account_types.py def is_balance_sheet_account(account_name, account_types): \"\"\"Return true if the given account is a balance sheet account. Assets, liabilities and equity accounts are balance sheet accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is a balance sheet account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.assets, account_types.liabilities, account_types.equity)","title":"is_balance_sheet_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_equity_account","text":"Return true if the given account is an equity account. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. Source code in beancount/core/account_types.py def is_equity_account(account_name, account_types): \"\"\"Return true if the given account is an equity account. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an equity account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type == account_types.equity","title":"is_equity_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_income_statement_account","text":"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Parameters: account_name \u2013 A string, an account name. account_types \u2013 An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. Source code in beancount/core/account_types.py def is_income_statement_account(account_name, account_types): \"\"\"Return true if the given account is an income statement account. Income and expense accounts are income statement accounts. Args: account_name: A string, an account name. account_types: An instance of AccountTypes. Returns: A boolean, true if the account is an income statement account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) account_type = get_account_type(account_name) return account_type in (account_types.income, account_types.expenses)","title":"is_income_statement_account()"},{"location":"api_reference/beancount.core.html#beancount.core.account_types.is_root_account","text":"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Parameters: account_name \u2013 A string, the name of the account to check for. account_types \u2013 An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. Source code in beancount/core/account_types.py def is_root_account(account_name, account_types=None): \"\"\"Return true if the account name is a root account. This function does not verify whether the account root is a valid one, just that it is a root account or not. Args: account_name: A string, the name of the account to check for. account_types: An optional instance of the current account_types; if provided, we check against these values. If not provided, we merely check that name pattern is that of an account component with no separator. Returns: A boolean, true if the account is root account. \"\"\" assert isinstance(account_name, str), \"Account is not a string: {}\".format(account_name) if account_types is not None: assert isinstance(account_types, AccountTypes), ( \"Account types has invalid type: {}\".format(account_types)) return account_name in account_types else: return (account_name and bool(re.match(r'([A-Z][A-Za-z0-9\\-]+)$', account_name)))","title":"is_root_account()"},{"location":"api_reference/beancount.core.html#beancount.core.amount","text":"Amount class. This simple class is used to associate a number of units of a currency with its currency: (number, currency).","title":"amount"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount","text":"An 'Amount' represents a number of a particular unit of something. It's essentially a typed number, with corresponding manipulation operations defined on it.","title":"Amount"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__bool__","text":"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. Source code in beancount/core/amount.py def __bool__(self): \"\"\"Boolean predicate returns true if the number is non-zero. Returns: A boolean, true if non-zero number. \"\"\" return self.number != ZERO","title":"__bool__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__eq__","text":"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. Source code in beancount/core/amount.py def __eq__(self, other): \"\"\"Equality predicate. Returns true if both number and currency are equal. Returns: A boolean. \"\"\" if other is None: return False return (self.number, self.currency) == (other.number, other.currency)","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__hash__","text":"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. Source code in beancount/core/amount.py def __hash__(self): \"\"\"A hashing function for amounts. The hash includes the currency. Returns: An integer, the hash for this amount. \"\"\" return hash((self.number, self.currency))","title":"__hash__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__lt__","text":"Ordering comparison. This is used in the sorting key of positions. Parameters: other \u2013 An instance of Amount. Returns: True if this is less than the other Amount. Source code in beancount/core/amount.py def __lt__(self, other): \"\"\"Ordering comparison. This is used in the sorting key of positions. Args: other: An instance of Amount. Returns: True if this is less than the other Amount. \"\"\" return sortkey(self) < sortkey(other)","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__neg__","text":"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. Source code in beancount/core/amount.py def __neg__(self): \"\"\"Return the negative of this amount. Returns: A new instance of Amount, with the negative number of units. \"\"\" return Amount(-self.number, self.currency)","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__new__","text":"Constructor from a number and currency. Parameters: number \u2013 A string or Decimal instance. Will get converted automatically. currency \u2013 A string, the currency symbol to use. Source code in beancount/core/amount.py def __new__(cls, number, currency): \"\"\"Constructor from a number and currency. Args: number: A string or Decimal instance. Will get converted automatically. currency: A string, the currency symbol to use. \"\"\" assert isinstance(number, Amount.valid_types_number), repr(number) assert isinstance(currency, Amount.valid_types_currency), repr(currency) return _Amount.__new__(cls, number, currency)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__repr__","text":"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.__str__","text":"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def __str__(self): \"\"\"Convert an Amount instance to a printable string with the defaults. Returns: A formatted string of the quantized amount and symbol. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.from_string","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.Amount.to_string","text":"Convert an Amount instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/amount.py def to_string(self, dformat=DEFAULT_FORMATTER): \"\"\"Convert an Amount instance to a printable string. Args: dformat: An instance of DisplayFormatter. Returns: A formatted string of the quantized amount and symbol. \"\"\" number_fmt = (dformat.format(self.number, self.currency) if isinstance(self.number, Decimal) else str(self.number)) return \"{} {}\".format(number_fmt, self.currency)","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.A","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"A()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.abs","text":"Return the absolute value of the given amount. Parameters: amount \u2013 An instance of Amount. Returns: An instance of Amount. Source code in beancount/core/amount.py def abs(amount): \"\"\"Return the absolute value of the given amount. Args: amount: An instance of Amount. Returns: An instance of Amount. \"\"\" return (amount if amount.number >= ZERO else Amount(-amount.number, amount.currency))","title":"abs()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.add","text":"Add the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def add(amount1, amount2): \"\"\"Add the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the sum the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number + amount2.number, amount1.currency)","title":"add()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.div","text":"Divide the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. Source code in beancount/core/amount.py def div(amount, number): \"\"\"Divide the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with amount units divided by 'number'. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number / number, amount.currency)","title":"div()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.from_string","text":"Create an amount from a string. This is a miniature parser used for building tests. Parameters: string \u2013 A string of . Returns: A new instance of Amount. Source code in beancount/core/amount.py @staticmethod def from_string(string): \"\"\"Create an amount from a string. This is a miniature parser used for building tests. Args: string: A string of . Returns: A new instance of Amount. \"\"\" match = re.match(r'\\s*([-+]?[0-9.]+)\\s+({currency})'.format(currency=CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for amount: '{}'\".format(string)) number, currency = match.group(1, 2) return Amount(D(number), currency)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.mul","text":"Multiply the given amount by a number. Parameters: amount \u2013 An instance of Amount. number \u2013 A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. Source code in beancount/core/amount.py def mul(amount, number): \"\"\"Multiply the given amount by a number. Args: amount: An instance of Amount. number: A decimal number. Returns: An Amount, with the same currency, but with 'number' times units. \"\"\" assert isinstance(amount.number, Decimal), ( \"Amount's number is not a Decimal instance: {}\".format(amount.number)) assert isinstance(number, Decimal), ( \"Number is not a Decimal instance: {}\".format(number)) return Amount(amount.number * number, amount.currency)","title":"mul()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.sortkey","text":"A comparison function that sorts by currency first. Parameters: amount \u2013 An instance of Amount. Returns: A sort key, composed of the currency first and then the number. Source code in beancount/core/amount.py def sortkey(amount): \"\"\"A comparison function that sorts by currency first. Args: amount: An instance of Amount. Returns: A sort key, composed of the currency first and then the number. \"\"\" return (amount.currency, amount.number)","title":"sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.amount.sub","text":"Subtract the given amounts with the same currency. Parameters: amount1 \u2013 An instance of Amount. amount2 \u2013 An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. Source code in beancount/core/amount.py def sub(amount1, amount2): \"\"\"Subtract the given amounts with the same currency. Args: amount1: An instance of Amount. amount2: An instance of Amount. Returns: An instance of Amount, with the difference between the two amount's numbers, in the same currency. \"\"\" assert isinstance(amount1.number, Decimal), ( \"Amount1's number is not a Decimal instance: {}\".format(amount1.number)) assert isinstance(amount2.number, Decimal), ( \"Amount2's number is not a Decimal instance: {}\".format(amount2.number)) if amount1.currency != amount2.currency: raise ValueError( \"Unmatching currencies for operation on {} and {}\".format( amount1, amount2)) return Amount(amount1.number - amount2.number, amount1.currency)","title":"sub()"},{"location":"api_reference/beancount.core.html#beancount.core.compare","text":"Comparison helpers for data objects.","title":"compare"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError","text":"CompareError(source, message, entry)","title":"CompareError"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/compare.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__new__","text":"Create new instance of CompareError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.CompareError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/compare.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.compare_entries","text":"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Parameters: entries1 \u2013 A list of directives of any type. entries2 \u2013 Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are \u2013 success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def compare_entries(entries1, entries2): \"\"\"Compare two lists of entries. This is used for testing. The entries are compared with disregard for their file location. Args: entries1: A list of directives of any type. entries2: Another list of directives of any type. Returns: A tuple of (success, not_found1, not_found2), where the fields are: success: A boolean, true if all the values are equal. missing1: A list of directives from 'entries1' not found in 'entries2'. missing2: A list of directives from 'entries2' not found in 'entries1'. Raises: ValueError: If a duplicate entry is found. \"\"\" hashes1, errors1 = hash_entries(entries1, exclude_meta=True) hashes2, errors2 = hash_entries(entries2, exclude_meta=True) keys1 = set(hashes1.keys()) keys2 = set(hashes2.keys()) if errors1 or errors2: error = (errors1 + errors2)[0] raise ValueError(str(error)) same = keys1 == keys2 missing1 = data.sorted([hashes1[key] for key in keys1 - keys2]) missing2 = data.sorted([hashes2[key] for key in keys2 - keys1]) return (same, missing1, missing2)","title":"compare_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.excludes_entries","text":"Check that a list of entries does not appear in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def excludes_entries(subset_entries, entries): \"\"\"Check that a list of entries does not appear in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that should not include 'subset_entries'. Returns: A boolean and a list of entries that are not supposed to appear. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) intersection = keys.intersection(subset_keys) excludes = not bool(intersection) extra = data.sorted([subset_hashes[key] for key in intersection]) return (excludes, extra)","title":"excludes_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.hash_entries","text":"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Parameters: entries \u2013 A list of directives. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. Source code in beancount/core/compare.py def hash_entries(entries, exclude_meta=False): \"\"\"Compute unique hashes of each of the entries and return a map of them. This is used for comparisons between sets of entries. Args: entries: A list of directives. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A dict of hash-value to entry (for all entries) and a list of errors. Errors are created when duplicate entries are found. \"\"\" entry_hash_dict = {} errors = [] num_legal_duplicates = 0 for entry in entries: hash_ = hash_entry(entry, exclude_meta) if hash_ in entry_hash_dict: if isinstance(entry, Price): # Note: Allow duplicate Price entries, they should be common # because of the nature of stock markets (if they're closed, the # data source is likely to return an entry for the previously # available date, which may already have been fetched). num_legal_duplicates += 1 else: other_entry = entry_hash_dict[hash_] errors.append( CompareError(entry.meta, \"Duplicate entry: {} == {}\".format(entry, other_entry), entry)) entry_hash_dict[hash_] = entry if not errors: assert len(entry_hash_dict) + num_legal_duplicates == len(entries), ( len(entry_hash_dict), len(entries), num_legal_duplicates) return entry_hash_dict, errors","title":"hash_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.hash_entry","text":"Compute the stable hash of a single entry. Parameters: entry \u2013 A directive instance. exclude_meta \u2013 If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. Source code in beancount/core/compare.py def hash_entry(entry, exclude_meta=False): \"\"\"Compute the stable hash of a single entry. Args: entry: A directive instance. exclude_meta: If set, exclude the metadata from the hash. Use this for unit tests comparing entries coming from different sources as the filename and lineno will be distinct. However, when you're using the hashes to uniquely identify transactions, you want to include the filenames and line numbers (the default). Returns: A stable hexadecimal hash of this entry. \"\"\" return stable_hash_namedtuple(entry, IGNORED_FIELD_NAMES if exclude_meta else frozenset())","title":"hash_entry()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.includes_entries","text":"Check if a list of entries is included in another list. Parameters: subset_entries \u2013 The set of entries to look for in 'entries'. entries \u2013 The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Exceptions: ValueError \u2013 If a duplicate entry is found. Source code in beancount/core/compare.py def includes_entries(subset_entries, entries): \"\"\"Check if a list of entries is included in another list. Args: subset_entries: The set of entries to look for in 'entries'. entries: The larger list of entries that could include 'subset_entries'. Returns: A boolean and a list of missing entries. Raises: ValueError: If a duplicate entry is found. \"\"\" subset_hashes, subset_errors = hash_entries(subset_entries, exclude_meta=True) subset_keys = set(subset_hashes.keys()) hashes, errors = hash_entries(entries, exclude_meta=True) keys = set(hashes.keys()) if subset_errors or errors: error = (subset_errors + errors)[0] raise ValueError(str(error)) includes = subset_keys.issubset(keys) missing = data.sorted([subset_hashes[key] for key in subset_keys - keys]) return (includes, missing)","title":"includes_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.compare.stable_hash_namedtuple","text":"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Parameters: objtuple \u2013 A tuple object or other. ignore \u2013 A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. Source code in beancount/core/compare.py def stable_hash_namedtuple(objtuple, ignore=frozenset()): \"\"\"Hash the given namedtuple and its child fields. This iterates over all the members of objtuple, skipping the attributes from the 'ignore' set, and computes a unique hash string code. If the elements are lists or sets, sorts them for stability. Args: objtuple: A tuple object or other. ignore: A set of strings, attribute names to be skipped in computing a stable hash. For instance, circular references to objects or irrelevant data. \"\"\" # Note: this routine is slow and would stand to be implemented in C. hashobj = hashlib.md5() for attr_name, attr_value in zip(objtuple._fields, objtuple): if attr_name in ignore: continue if isinstance(attr_value, (list, set, frozenset)): subhashes = set() for element in attr_value: if isinstance(element, tuple): subhashes.add(stable_hash_namedtuple(element, ignore)) else: md5 = hashlib.md5() md5.update(str(element).encode()) subhashes.add(md5.hexdigest()) for subhash in sorted(subhashes): hashobj.update(subhash.encode()) else: hashobj.update(str(attr_value).encode()) return hashobj.hexdigest()","title":"stable_hash_namedtuple()"},{"location":"api_reference/beancount.core.html#beancount.core.convert","text":"Conversions from Position (or Posting) to units, cost, weight, market value. Units: Just the primary amount of the position. Cost: The cost basis of the position, if available. Weight: The cost basis or price of the position. Market Value: The units converted to a value via a price map. To convert an inventory's contents, simply use these functions in conjunction with Inventory.reduce() , like cost_inv = inv.reduce(convert.get_cost) This module equivalently converts Position and Posting instances. Note that we're specifically avoiding to create an import dependency on beancount.core.data in order to keep this module isolatable, but it works on postings due to duck-typing. Function named get_*() are used to compute values from postings to their price currency. Functions named convert_*() are used to convert postings and amounts to any currency.","title":"convert"},{"location":"api_reference/beancount.core.html#beancount.core.convert.convert_amount","text":"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Parameters: amt \u2013 An instance of Amount. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. via \u2013 A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. Source code in beancount/core/convert.py def convert_amount(amt, target_currency, price_map, date=None, via=None): \"\"\"Return the market value of an Amount in a particular currency. In addition, if a conversion rate isn't available, you can provide a list of currencies to attempt to synthesize a rate for via implied rates. Args: amt: An instance of Amount. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. via: A list of currencies to attempt to synthesize an implied rate if the direct conversion fails. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, the amount itself, unmodified. \"\"\" # First, attempt to convert directly. This should be the most # straightforward conversion. base_quote = (amt.currency, target_currency) _, rate = prices.get_price(price_map, base_quote, date) if rate is not None: # On success, just make the conversion directly. return Amount(amt.number * rate, target_currency) elif via: assert isinstance(via, (tuple, list)) # A price is unavailable, attempt to convert via cost/price currency # hop, if the value currency isn't the target currency. for implied_currency in via: if implied_currency == target_currency: continue base_quote1 = (amt.currency, implied_currency) _, rate1 = prices.get_price(price_map, base_quote1, date) if rate1 is not None: base_quote2 = (implied_currency, target_currency) _, rate2 = prices.get_price(price_map, base_quote2, date) if rate2 is not None: return Amount(amt.number * rate1 * rate2, target_currency) # We failed to infer a conversion rate; return the amt. return amt","title":"convert_amount()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.convert_position","text":"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Parameters: pos \u2013 An instance of Position or Posting, equivalently. target_currency \u2013 The target currency to convert to. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) Source code in beancount/core/convert.py def convert_position(pos, target_currency, price_map, date=None): \"\"\"Return the market value of a Position or Posting in a particular currency. In addition, if the rate from the position's currency to target_currency isn't available, an attempt is made to convert from its cost currency, if one is available. Args: pos: An instance of Position or Posting, equivalently. target_currency: The target currency to convert to. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. (See get_value() above for details.) \"\"\" cost = pos.cost value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) return convert_amount(pos.units, target_currency, price_map, date=date, via=(value_currency,))","title":"convert_position()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_cost","text":"Return the total cost of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_cost(pos): \"\"\"Return the total cost of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' cost = pos.cost return (Amount(cost.number * pos.units.number, cost.currency) if (isinstance(cost, Cost) and isinstance(cost.number, Decimal)) else pos.units)","title":"get_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_units","text":"Return the units of a Position or Posting. Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_units(pos): \"\"\"Return the units of a Position or Posting. Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' return pos.units","title":"get_units()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_value","text":"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see convert_*() functions below. Parameters: pos \u2013 An instance of Position or Posting, equivalently. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. Source code in beancount/core/convert.py def get_value(pos, price_map, date=None): \"\"\"Return the market value of a Position or Posting. Note that if the position is not held at cost, this does not convert anything, even if a price is available in the 'price_map'. We don't specify a target currency here. If you're attempting to make such a conversion, see ``convert_*()`` functions below. Args: pos: An instance of Position or Posting, equivalently. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance to evaluate the value at, or None. Returns: An Amount, either with a successful value currency conversion, or if we could not convert the value, just the units, unmodified. This is designed so that you could reduce an inventory with this and not lose any information silently in case of failure to convert (possibly due to an empty price map). Compare the returned currency to that of the input position if you need to check for success. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # Try to infer what the cost/price currency should be. value_currency = ( (isinstance(cost, Cost) and cost.currency) or (hasattr(pos, 'price') and pos.price and pos.price.currency) or None) if isinstance(value_currency, str): # We have a value currency; hit the price database. base_quote = (units.currency, value_currency) _, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: return Amount(units.number * price_number, value_currency) # We failed to infer a conversion rate; return the units. return units","title":"get_value()"},{"location":"api_reference/beancount.core.html#beancount.core.convert.get_weight","text":"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a key element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Parameters: pos \u2013 An instance of Position or Posting, equivalently. Returns: An Amount. Source code in beancount/core/convert.py def get_weight(pos): \"\"\"Return the weight of a Position or Posting. This is the amount that will need to be balanced from a posting of a transaction. This is a *key* element of the semantics of transactions in this software. A balance amount is the amount used to check the balance of a transaction. Here are all relevant examples, with the amounts used to balance the postings: Assets:Account 5234.50 USD -> 5234.50 USD Assets:Account 3877.41 EUR @ 1.35 USD -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} -> 5234.50 USD Assets:Account 10 HOOL {523.45 USD} @ 545.60 CAD -> 5234.50 USD Args: pos: An instance of Position or Posting, equivalently. Returns: An Amount. \"\"\" assert isinstance(pos, Position) or type(pos).__name__ == 'Posting' units = pos.units cost = pos.cost # It the object has a cost, use that as the weight, to balance. if isinstance(cost, Cost) and isinstance(cost.number, Decimal): weight = Amount(cost.number * pos.units.number, cost.currency) else: # Otherwise use the postings. weight = units # Unless there is a price available; use that if present. if not isinstance(pos, Position): price = pos.price if price is not None: # Note: Here we could assert that price.currency == units.currency. if price.number is MISSING or units.number is MISSING: converted_number = MISSING else: converted_number = price.number * units.number weight = Amount(converted_number, price.currency) return weight","title":"get_weight()"},{"location":"api_reference/beancount.core.html#beancount.core.data","text":"Basic data structures used to represent the Ledger entries.","title":"data"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance","text":"Balance(meta, date, account, amount, tolerance, diff_amount)","title":"Balance"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__new__","text":"Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Balance.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close","text":"Close(meta, date, account)","title":"Close"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__new__","text":"Create new instance of Close(meta, date, account)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Close.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity","text":"Commodity(meta, date, currency)","title":"Commodity"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__new__","text":"Create new instance of Commodity(meta, date, currency)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Commodity.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom","text":"Custom(meta, date, type, values)","title":"Custom"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__new__","text":"Create new instance of Custom(meta, date, type, values)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Custom.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document","text":"Document(meta, date, account, filename, tags, links)","title":"Document"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__new__","text":"Create new instance of Document(meta, date, account, filename, tags, links)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Document.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event","text":"Event(meta, date, type, description)","title":"Event"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__new__","text":"Create new instance of Event(meta, date, type, description)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Event.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note","text":"Note(meta, date, account, comment)","title":"Note"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__new__","text":"Create new instance of Note(meta, date, account, comment)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Note.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open","text":"Open(meta, date, account, currencies, booking)","title":"Open"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__new__","text":"Create new instance of Open(meta, date, account, currencies, booking)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Open.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad","text":"Pad(meta, date, account, source_account)","title":"Pad"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__new__","text":"Create new instance of Pad(meta, date, account, source_account)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Pad.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting","text":"Posting(account, units, cost, price, flag, meta)","title":"Posting"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__new__","text":"Create new instance of Posting(account, units, cost, price, flag, meta)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Posting.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price","text":"Price(meta, date, currency, amount)","title":"Price"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__new__","text":"Create new instance of Price(meta, date, currency, amount)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Price.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query","text":"Query(meta, date, name, query_string)","title":"Query"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__new__","text":"Create new instance of Query(meta, date, name, query_string)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Query.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction","text":"Transaction(meta, date, flag, payee, narration, tags, links, postings)","title":"Transaction"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__new__","text":"Create new instance of Transaction(meta, date, flag, payee, narration, tags, links, postings)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.Transaction.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting","text":"TxnPosting(txn, posting)","title":"TxnPosting"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/data.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__new__","text":"Create new instance of TxnPosting(txn, posting)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.TxnPosting.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/data.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.data.create_simple_posting","text":"Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting(entry, account, number, currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if number is None: units = None else: if not isinstance(number, Decimal): number = D(number) units = Amount(number, currency) posting = Posting(account, units, None, None, None, None) if entry is not None: entry.postings.append(posting) return posting","title":"create_simple_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.data.create_simple_posting_with_cost","text":"Create a simple posting on the entry, with just a number and currency (no cost). Parameters: entry \u2013 The entry instance to add the posting to. account \u2013 A string, the account to use on the posting. number \u2013 A Decimal number or string to use in the posting's Amount. currency \u2013 A string, the currency for the Amount. cost_number \u2013 A Decimal number or string to use for the posting's cost Amount. cost_currency \u2013 a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. Source code in beancount/core/data.py def create_simple_posting_with_cost(entry, account, number, currency, cost_number, cost_currency): \"\"\"Create a simple posting on the entry, with just a number and currency (no cost). Args: entry: The entry instance to add the posting to. account: A string, the account to use on the posting. number: A Decimal number or string to use in the posting's Amount. currency: A string, the currency for the Amount. cost_number: A Decimal number or string to use for the posting's cost Amount. cost_currency: a string, the currency for the cost Amount. Returns: An instance of Posting, and as a side-effect the entry has had its list of postings modified with the new Posting instance. \"\"\" if isinstance(account, str): pass if not isinstance(number, Decimal): number = D(number) if cost_number and not isinstance(cost_number, Decimal): cost_number = D(cost_number) units = Amount(number, currency) cost = Cost(cost_number, cost_currency, None, None) posting = Posting(account, units, cost, None, None, None) if entry is not None: entry.postings.append(posting) return posting","title":"create_simple_posting_with_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.data.entry_sortkey","text":"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. Source code in beancount/core/data.py def entry_sortkey(entry): \"\"\"Sort-key for entries. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: An entry instance. Returns: A tuple of (date, integer, integer), that forms the sort key for the entry. \"\"\" return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"])","title":"entry_sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.data.filter_txns","text":"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Parameters: entries \u2013 A list of directives. Yields: A sorted list of only the Transaction directives. Source code in beancount/core/data.py def filter_txns(entries): \"\"\"A generator that yields only the Transaction instances. This is such an incredibly common operation that it deserves a terse filtering mechanism. Args: entries: A list of directives. Yields: A sorted list of only the Transaction directives. \"\"\" for entry in entries: if isinstance(entry, Transaction): yield entry","title":"filter_txns()"},{"location":"api_reference/beancount.core.html#beancount.core.data.find_closest","text":"Find the closest entry from entries to (filename, lineno). Parameters: entries \u2013 A list of directives. filename \u2013 A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno \u2013 An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. Source code in beancount/core/data.py def find_closest(entries, filename, lineno): \"\"\"Find the closest entry from entries to (filename, lineno). Args: entries: A list of directives. filename: A string, the name of the ledger file to look for. Be careful to provide the very same filename, and note that the parser stores the absolute path of the filename here. lineno: An integer, the line number closest after the directive we're looking for. This may be the exact/first line of the directive. Returns: The closest entry found in the given file for the given filename, or None, if none could be found. \"\"\" min_diffline = sys.maxsize closest_entry = None for entry in entries: emeta = entry.meta if emeta[\"filename\"] == filename and emeta[\"lineno\"] > 0: diffline = lineno - emeta[\"lineno\"] if 0 <= diffline < min_diffline: min_diffline = diffline closest_entry = entry return closest_entry","title":"find_closest()"},{"location":"api_reference/beancount.core.html#beancount.core.data.get_entry","text":"Return the entry associated with the posting or entry. Parameters: entry \u2013 A TxnPosting or entry instance Returns: A datetime instance. Source code in beancount/core/data.py def get_entry(posting_or_entry): \"\"\"Return the entry associated with the posting or entry. Args: entry: A TxnPosting or entry instance Returns: A datetime instance. \"\"\" return (posting_or_entry.txn if isinstance(posting_or_entry, TxnPosting) else posting_or_entry)","title":"get_entry()"},{"location":"api_reference/beancount.core.html#beancount.core.data.has_entry_account_component","text":"Return true if one of the entry's postings has an account component. Parameters: entry \u2013 A Transaction entry. component \u2013 A string, a component of an account name. For instance, Food in Expenses:Food:Restaurant . All components are considered. Returns: Boolean \u2013 true if the component is in the account. Note that a component name must be whole, that is NY is not in Expenses:Taxes:StateNY . Source code in beancount/core/data.py def has_entry_account_component(entry, component): \"\"\"Return true if one of the entry's postings has an account component. Args: entry: A Transaction entry. component: A string, a component of an account name. For instance, ``Food`` in ``Expenses:Food:Restaurant``. All components are considered. Returns: Boolean: true if the component is in the account. Note that a component name must be whole, that is ``NY`` is not in ``Expenses:Taxes:StateNY``. \"\"\" return (isinstance(entry, Transaction) and any(has_component(posting.account, component) for posting in entry.postings))","title":"has_entry_account_component()"},{"location":"api_reference/beancount.core.html#beancount.core.data.iter_entry_dates","text":"Iterate over the entries in a date window. Parameters: entries \u2013 A date-sorted list of dated directives. date_begin \u2013 A datetime.date instance, the first date to include. date_end \u2013 A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. Source code in beancount/core/data.py def iter_entry_dates(entries, date_begin, date_end): \"\"\"Iterate over the entries in a date window. Args: entries: A date-sorted list of dated directives. date_begin: A datetime.date instance, the first date to include. date_end: A datetime.date instance, one day beyond the last date. Yields: Instances of the dated directives, between the dates, and in the order in which they appear. \"\"\" getdate = lambda entry: entry.date index_begin = bisect_left_with_key(entries, date_begin, key=getdate) index_end = bisect_left_with_key(entries, date_end, key=getdate) for index in range(index_begin, index_end): yield entries[index]","title":"iter_entry_dates()"},{"location":"api_reference/beancount.core.html#beancount.core.data.new_directive","text":"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Parameters: name \u2013 A string, the capitalized name of the directive. fields ( List[Tuple] ) \u2013 A string or the list of strings, names for the fields to add to the base tuple. Returns: \u2013 A type object for the new directive type. Source code in beancount/core/data.py def new_directive(clsname, fields: List[Tuple]) -> NamedTuple: \"\"\"Create a directive class. Do not include default fields. This should probably be carried out through inheritance. Args: name: A string, the capitalized name of the directive. fields: A string or the list of strings, names for the fields to add to the base tuple. Returns: A type object for the new directive type. \"\"\" return NamedTuple( clsname, [('meta', Meta), ('date', datetime.date)] + fields)","title":"new_directive()"},{"location":"api_reference/beancount.core.html#beancount.core.data.new_metadata","text":"Create a new metadata container from the filename and line number. Parameters: filename \u2013 A string, the filename for the creator of this directive. lineno \u2013 An integer, the line number where the directive has been created. kvlist \u2013 An optional container of key-values. Returns: A metadata dict. Source code in beancount/core/data.py def new_metadata(filename, lineno, kvlist=None): \"\"\"Create a new metadata container from the filename and line number. Args: filename: A string, the filename for the creator of this directive. lineno: An integer, the line number where the directive has been created. kvlist: An optional container of key-values. Returns: A metadata dict. \"\"\" meta = {'filename': filename, 'lineno': lineno} if kvlist: meta.update(kvlist) return meta","title":"new_metadata()"},{"location":"api_reference/beancount.core.html#beancount.core.data.posting_has_conversion","text":"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Parameters: posting \u2013 an instance of Posting Returns: A boolean, true if this posting has a price conversion. Source code in beancount/core/data.py def posting_has_conversion(posting): \"\"\"Return true if this position involves a conversion. A conversion is when there is a price attached to the amount but no cost. This is used on transactions to convert between units. Args: posting: an instance of Posting Return: A boolean, true if this posting has a price conversion. \"\"\" return (posting.cost is None and posting.price is not None)","title":"posting_has_conversion()"},{"location":"api_reference/beancount.core.html#beancount.core.data.posting_sortkey","text":"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Parameters: entry \u2013 A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. Source code in beancount/core/data.py def posting_sortkey(entry): \"\"\"Sort-key for entries or postings. We sort by date, except that checks should be placed in front of every list of entries of that same day, in order to balance linearly. Args: entry: A Posting or entry instance Returns: A tuple of (date, integer, integer), that forms the sort key for the posting or entry. \"\"\" if isinstance(entry, TxnPosting): entry = entry.txn return (entry.date, SORT_ORDER.get(type(entry), 0), entry.meta[\"lineno\"])","title":"posting_sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.data.remove_account_postings","text":"Remove all postings with the given account. Parameters: account \u2013 A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. Source code in beancount/core/data.py def remove_account_postings(account, entries): \"\"\"Remove all postings with the given account. Args: account: A string, the account name whose postings we want to remove. Returns: A list of entries without the rounding postings. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, Transaction) and ( any(posting.account == account for posting in entry.postings)): entry = entry._replace(postings=[posting for posting in entry.postings if posting.account != account]) new_entries.append(entry) return new_entries","title":"remove_account_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.data.sanity_check_types","text":"Check that the entry and its postings has all correct data types. Parameters: entry \u2013 An instance of one of the entries to be checked. allow_none_for_tags_and_links \u2013 A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Exceptions: AssertionError \u2013 If there is anything that is unexpected, raises an exception. Source code in beancount/core/data.py def sanity_check_types(entry, allow_none_for_tags_and_links=False): \"\"\"Check that the entry and its postings has all correct data types. Args: entry: An instance of one of the entries to be checked. allow_none_for_tags_and_links: A boolean, whether to allow plugins to generate Transaction objects with None as value for the 'tags' or 'links' attributes. Raises: AssertionError: If there is anything that is unexpected, raises an exception. \"\"\" assert isinstance(entry, ALL_DIRECTIVES), \"Invalid directive type\" assert isinstance(entry.meta, dict), \"Invalid type for meta\" assert 'filename' in entry.meta, \"Missing filename in metadata\" assert 'lineno' in entry.meta, \"Missing line number in metadata\" assert isinstance(entry.date, datetime.date), \"Invalid date type\" if isinstance(entry, Transaction): assert isinstance(entry.flag, (NoneType, str)), \"Invalid flag type\" assert isinstance(entry.payee, (NoneType, str)), \"Invalid payee type\" assert isinstance(entry.narration, (NoneType, str)), \"Invalid narration type\" set_types = ((NoneType, set, frozenset) if allow_none_for_tags_and_links else (set, frozenset)) assert isinstance(entry.tags, set_types), ( \"Invalid tags type: {}\".format(type(entry.tags))) assert isinstance(entry.links, set_types), ( \"Invalid links type: {}\".format(type(entry.links))) assert isinstance(entry.postings, list), \"Invalid postings list type\" for posting in entry.postings: assert isinstance(posting, Posting), \"Invalid posting type\" assert isinstance(posting.account, str), \"Invalid account type\" assert isinstance(posting.units, (Amount, NoneType)), \"Invalid units type\" assert isinstance(posting.cost, (Cost, CostSpec, NoneType)), \"Invalid cost type\" assert isinstance(posting.price, (Amount, NoneType)), \"Invalid price type\" assert isinstance(posting.flag, (str, NoneType)), \"Invalid flag type\"","title":"sanity_check_types()"},{"location":"api_reference/beancount.core.html#beancount.core.data.sorted","text":"A convenience to sort a list of entries, using entry_sortkey(). Parameters: entries \u2013 A list of directives. Returns: A sorted list of directives. Source code in beancount/core/data.py def sorted(entries): \"\"\"A convenience to sort a list of entries, using entry_sortkey(). Args: entries: A list of directives. Returns: A sorted list of directives. \"\"\" return builtins.sorted(entries, key=entry_sortkey)","title":"sorted()"},{"location":"api_reference/beancount.core.html#beancount.core.data.transaction_has_conversion","text":"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Parameters: transaction \u2013 an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. Source code in beancount/core/data.py def transaction_has_conversion(transaction): \"\"\"Given a Transaction entry, return true if at least one of the postings has a price conversion (without an associated cost). These are the source of non-zero conversion balances. Args: transaction: an instance of a Transaction entry. Returns: A boolean, true if this transaction contains at least one posting with a price conversion. \"\"\" assert isinstance(transaction, Transaction), ( \"Invalid type of entry for transaction: {}\".format(transaction)) for posting in transaction.postings: if posting_has_conversion(posting): return True return False","title":"transaction_has_conversion()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context","text":"A settings class to offer control over the number of digits rendered. This module contains routines that can accumulate information on the width and precision of numbers to be rendered and derive the precision required to render all of them consistently and under certain common alignment requirements. This is required in order to output neatly lined up columns of numbers in various styles. A common case is that the precision can be observed for numbers present in the input file. This display precision can be used as the \"precision by default\" if we write a routine for which it is inconvenient to feed all the numbers to build such an accumulator. Here are all the aspects supported by this module: PRECISION: Numbers for a particular currency are always rendered to the same precision, and they can be rendered to one of two precisions; either the most common number of fractional digits, or the maximum number of digits seen (this is useful for rendering prices). ALIGNMENT: Several alignment methods are supported. \"natural\": Render the strings as small as possible with no padding, but to their currency's precision. Like this: '1.2345' '764' '-7,409.01' '0.00000125' \"dot-aligned\": The periods will align vertically, the left and right sides are padded so that the column of numbers has the same width: ' 1.2345 ' ' 764 ' '-7,409.01 ' ' 0.00000125' \"right\": The strings are all flushed right, the left side is padded so that the column of numbers has the same width: ' 1.2345' ' 764' ' -7,409.01' ' 0.00000125' SIGN: If a negative sign is present in the input numbers, the rendered numbers reserve a space for it. If not, then we save the space. COMMAS: If the user requests to render commas, commas are rendered in the output. RESERVED: A number of extra integral digits reserved on the left in order to allow rendering novel numbers that haven't yet been seen. For example, balances may contains much larger numbers than the numbers seen in input files, and these need to be accommodated when aligning to the right.","title":"display_context"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.Align","text":"Alignment style for numbers.","title":"Align"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext","text":"A builder object used to construct a DisplayContext from a series of numbers. Attributes: Name Type Description ccontexts A dict of currency string to CurrencyContext instance. commas A bool, true if we should render commas. This just gets propagated onwards as the default value of to build with.","title":"DisplayContext"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.build","text":"Build a formatter for the given display context. Parameters: alignment \u2013 The desired alignment. precision \u2013 The desired precision. commas \u2013 Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved \u2013 An integer, the number of extra digits to be allocated in the maximum width calculations. Source code in beancount/core/display_context.py def build(self, alignment=Align.NATURAL, precision=Precision.MOST_COMMON, commas=None, reserved=0): \"\"\"Build a formatter for the given display context. Args: alignment: The desired alignment. precision: The desired precision. commas: Whether to render commas or not. If 'None', the default value carried by the context will be used. reserved: An integer, the number of extra digits to be allocated in the maximum width calculations. \"\"\" if commas is None: commas = self.commas if alignment == Align.NATURAL: build_method = self._build_natural elif alignment == Align.RIGHT: build_method = self._build_right elif alignment == Align.DOT: build_method = self._build_dot else: raise ValueError(\"Unknown alignment: {}\".format(alignment)) fmtstrings = build_method(precision, commas, reserved) return DisplayFormatter(self, precision, fmtstrings)","title":"build()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.quantize","text":"Quantize the given number to the given precision. Parameters: number \u2013 A Decimal instance, the number to be quantized. currency \u2013 A currency string. precision \u2013 Which precision to use. Returns: A Decimal instance, the quantized number. Source code in beancount/core/display_context.py def quantize(self, number, currency, precision=Precision.MOST_COMMON): \"\"\"Quantize the given number to the given precision. Args: number: A Decimal instance, the number to be quantized. currency: A currency string. precision: Which precision to use. Returns: A Decimal instance, the quantized number. \"\"\" assert isinstance(number, Decimal), \"Invalid data: {}\".format(number) ccontext = self.ccontexts[currency] num_fractional_digits = ccontext.get_fractional(precision) if num_fractional_digits is None: # Note: We could probably logging.warn() this situation here. return number qdigit = Decimal(1).scaleb(-num_fractional_digits) return number.quantize(qdigit)","title":"quantize()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.set_commas","text":"Set the default value for rendering commas. Source code in beancount/core/display_context.py def set_commas(self, commas): \"\"\"Set the default value for rendering commas.\"\"\" self.commas = commas","title":"set_commas()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayContext.update","text":"Update the builder with the given number for the given currency. Parameters: number \u2013 An instance of Decimal to consider for this currency. currency \u2013 An optional string, the currency this numbers applies to. Source code in beancount/core/display_context.py def update(self, number, currency='__default__'): \"\"\"Update the builder with the given number for the given currency. Args: number: An instance of Decimal to consider for this currency. currency: An optional string, the currency this numbers applies to. \"\"\" self.ccontexts[currency].update(number)","title":"update()"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.DisplayFormatter","text":"A class used to contain various settings that control how we output numbers. In particular, the precision used for each currency, and whether or not commas should be printed. This object is intended to be passed around to all functions that format numbers to strings. Attributes: Name Type Description dcontext A DisplayContext instance. precision An enum of Precision from which it was built. fmtstrings A dict of currency to pre-baked format strings for it. fmtfuncs A dict of currency to pre-baked formatting functions for it.","title":"DisplayFormatter"},{"location":"api_reference/beancount.core.html#beancount.core.display_context.Precision","text":"The type of precision required.","title":"Precision"},{"location":"api_reference/beancount.core.html#beancount.core.distribution","text":"A simple accumulator for data about a mathematical distribution.","title":"distribution"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution","text":"A class that computes a histogram of integer values. This is used to compute a length that will cover at least some decent fraction of the samples.","title":"Distribution"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.empty","text":"Return true if the distribution is empty. Returns: A boolean. Source code in beancount/core/distribution.py def empty(self): \"\"\"Return true if the distribution is empty. Returns: A boolean. \"\"\" return len(self.hist) == 0","title":"empty()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.max","text":"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def max(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[-1] return value","title":"max()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.min","text":"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def min(self): \"\"\"Return the minimum value seen in the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None value, _ = sorted(self.hist.items())[0] return value","title":"min()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.mode","text":"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. Source code in beancount/core/distribution.py def mode(self): \"\"\"Return the mode of the distribution. Returns: An element of the value type, or None, if the distribution was empty. \"\"\" if not self.hist: return None max_value = 0 max_count = 0 for value, count in sorted(self.hist.items()): if count >= max_count: max_count = count max_value = value return max_value","title":"mode()"},{"location":"api_reference/beancount.core.html#beancount.core.distribution.Distribution.update","text":"Add a sample to the distribution. Parameters: value \u2013 A value of the function. Source code in beancount/core/distribution.py def update(self, value): \"\"\"Add a sample to the distribution. Args: value: A value of the function. \"\"\" self.hist[value] += 1","title":"update()"},{"location":"api_reference/beancount.core.html#beancount.core.flags","text":"Flag constants.","title":"flags"},{"location":"api_reference/beancount.core.html#beancount.core.getters","text":"Getter functions that operate on lists of entries to return various lists of things that they reference, accounts, tags, links, currencies, etc.","title":"getters"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts","text":"Accounts gatherer.","title":"GetAccounts"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Balance","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Balance()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Close","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Close()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Commodity","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Commodity()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Custom","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Custom()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Document","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Document()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Event","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Event()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Note","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Note()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Open","text":"Process directives with a single account attribute. Parameters: entry \u2013 An instance of a directive. Returns: The single account of this directive. Source code in beancount/core/getters.py def _one(_, entry): \"\"\"Process directives with a single account attribute. Args: entry: An instance of a directive. Returns: The single account of this directive. \"\"\" return (entry.account,)","title":"Open()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Pad","text":"Process a Pad directive. Parameters: entry \u2013 An instance of Pad. Returns: The two accounts of the Pad directive. Source code in beancount/core/getters.py def Pad(_, entry): \"\"\"Process a Pad directive. Args: entry: An instance of Pad. Returns: The two accounts of the Pad directive. \"\"\" return (entry.account, entry.source_account)","title":"Pad()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Price","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Price()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Query","text":"Process directives with no accounts. Parameters: entry \u2013 An instance of a directive. Returns: An empty list Source code in beancount/core/getters.py def _zero(_, entry): \"\"\"Process directives with no accounts. Args: entry: An instance of a directive. Returns: An empty list \"\"\" return ()","title":"Query()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.Transaction","text":"Process a Transaction directive. Parameters: entry \u2013 An instance of Transaction. Yields: The accounts of the legs of the transaction. Source code in beancount/core/getters.py def Transaction(_, entry): \"\"\"Process a Transaction directive. Args: entry: An instance of Transaction. Yields: The accounts of the legs of the transaction. \"\"\" for posting in entry.postings: yield posting.account","title":"Transaction()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.get_accounts_use_map","text":"Gather the list of accounts from the list of entries. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(self, entries): \"\"\"Gather the list of accounts from the list of entries. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" accounts_first = {} accounts_last = {} for entry in entries: method = getattr(self, entry.__class__.__name__) for account_ in method(entry): if account_ not in accounts_first: accounts_first[account_] = entry.date accounts_last[account_] = entry.date return accounts_first, accounts_last","title":"get_accounts_use_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.GetAccounts.get_entry_accounts","text":"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entry \u2013 A directive instance. Returns: A set of account name strings. Source code in beancount/core/getters.py def get_entry_accounts(self, entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entry: A directive instance. Returns: A set of account name strings. \"\"\" method = getattr(self, entry.__class__.__name__) return set(method(entry))","title":"get_entry_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_account_components","text":"Gather all the account components available in the given directives. Parameters: entries \u2013 A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. Source code in beancount/core/getters.py def get_account_components(entries): \"\"\"Gather all the account components available in the given directives. Args: entries: A list of directive instances. Returns: A list of strings, the unique account components, including the root account names. \"\"\" accounts = get_accounts(entries) components = set() for account_name in accounts: components.update(account.split(account_name)) return sorted(components)","title":"get_account_components()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_account_open_close","text":"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Parameters: entries \u2013 A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. Source code in beancount/core/getters.py def get_account_open_close(entries): \"\"\"Fetch the open/close entries for each of the accounts. If an open or close entry happens to be duplicated, accept the earliest entry (chronologically). Args: entries: A list of directive instances. Returns: A map of account name strings to pairs of (open-directive, close-directive) tuples. \"\"\" # A dict of account name to (open-entry, close-entry). open_close_map = defaultdict(lambda: [None, None]) for entry in entries: if not isinstance(entry, (Open, Close)): continue open_close = open_close_map[entry.account] index = 0 if isinstance(entry, Open) else 1 previous_entry = open_close[index] if previous_entry is not None: if previous_entry.date <= entry.date: entry = previous_entry open_close[index] = entry return dict(open_close_map)","title":"get_account_open_close()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_accounts","text":"Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A set of account strings. Source code in beancount/core/getters.py def get_accounts(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A set of account strings. \"\"\" _, accounts_last = _GetAccounts.get_accounts_use_map(entries) return accounts_last.keys()","title":"get_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_accounts_use_map","text":"Gather all the accounts references by a list of directives. Parameters: entries \u2013 A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. Source code in beancount/core/getters.py def get_accounts_use_map(entries): \"\"\"Gather all the accounts references by a list of directives. Args: entries: A list of directive instances. Returns: A pair of dictionaries of account name to date, one for first date used and one for last date used. The keys should be identical. \"\"\" return _GetAccounts.get_accounts_use_map(entries)","title":"get_accounts_use_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_active_years","text":"Yield all the years that have at least one entry in them. Parameters: entries \u2013 A list of directive instances. Yields: Unique dates see in the list of directives. Source code in beancount/core/getters.py def get_active_years(entries): \"\"\"Yield all the years that have at least one entry in them. Args: entries: A list of directive instances. Yields: Unique dates see in the list of directives. \"\"\" seen = set() prev_year = None for entry in entries: year = entry.date.year if year != prev_year: prev_year = year assert year not in seen seen.add(year) yield year","title":"get_active_years()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_links","text":"Return a list of all the links seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of links strings. Source code in beancount/core/getters.py def get_all_links(entries): \"\"\"Return a list of all the links seen in the given entries. Args: entries: A list of directive instances. Returns: A set of links strings. \"\"\" all_links = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.links: all_links.update(entry.links) return sorted(all_links)","title":"get_all_links()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_payees","text":"Return a list of all the unique payees seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of payee strings. Source code in beancount/core/getters.py def get_all_payees(entries): \"\"\"Return a list of all the unique payees seen in the given entries. Args: entries: A list of directive instances. Returns: A set of payee strings. \"\"\" all_payees = set() for entry in entries: if not isinstance(entry, Transaction): continue all_payees.add(entry.payee) all_payees.discard(None) return sorted(all_payees)","title":"get_all_payees()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_all_tags","text":"Return a list of all the tags seen in the given entries. Parameters: entries \u2013 A list of directive instances. Returns: A set of tag strings. Source code in beancount/core/getters.py def get_all_tags(entries): \"\"\"Return a list of all the tags seen in the given entries. Args: entries: A list of directive instances. Returns: A set of tag strings. \"\"\" all_tags = set() for entry in entries: if not isinstance(entry, Transaction): continue if entry.tags: all_tags.update(entry.tags) return sorted(all_tags)","title":"get_all_tags()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_commodity_map","text":"Create map of commodity names to Commodity entries. Parameters: entries \u2013 A list of directive instances. create_missing \u2013 A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. Source code in beancount/core/getters.py def get_commodity_map(entries, create_missing=True): \"\"\"Create map of commodity names to Commodity entries. Args: entries: A list of directive instances. create_missing: A boolean, true if you want to automatically generate missing commodity directives if not present in the output map. Returns: A map of commodity name strings to Commodity directives. \"\"\" if not entries: return {} commodities_map = {} for entry in entries: if isinstance(entry, Commodity): commodities_map[entry.currency] = entry elif isinstance(entry, Transaction): for posting in entry.postings: # Main currency. units = posting.units commodities_map.setdefault(units.currency, None) # Currency in cost. cost = posting.cost if cost: commodities_map.setdefault(cost.currency, None) # Currency in price. price = posting.price if price: commodities_map.setdefault(price.currency, None) elif isinstance(entry, Balance): commodities_map.setdefault(entry.amount.currency, None) elif isinstance(entry, Price): commodities_map.setdefault(entry.currency, None) if create_missing: # Create missing Commodity directives when they haven't been specified explicitly. # (I think it might be better to always do this from the loader.) date = entries[0].date meta = data.new_metadata('', 0) commodities_map = { commodity: (entry if entry is not None else Commodity(meta, date, commodity)) for commodity, entry in commodities_map.items()} return commodities_map","title":"get_commodity_map()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_dict_accounts","text":"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Parameters: account_names \u2013 An iterable of account names (strings) Returns: A nested OrderedDict of account leafs Source code in beancount/core/getters.py def get_dict_accounts(account_names): \"\"\"Return a nested dict of all the unique leaf names. account names are labelled with LABEL=True Args: account_names: An iterable of account names (strings) Returns: A nested OrderedDict of account leafs \"\"\" leveldict = OrderedDict() for account_name in account_names: nested_dict = leveldict for component in account.split(account_name): nested_dict = nested_dict.setdefault(component, OrderedDict()) nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True return leveldict","title":"get_dict_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_entry_accounts","text":"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Parameters: entries \u2013 A directive instance. Returns: A set of account strings. Source code in beancount/core/getters.py def get_entry_accounts(entry): \"\"\"Gather all the accounts references by a single directive. Note: This should get replaced by a method on each directive eventually, that would be the clean way to do this. Args: entries: A directive instance. Returns: A set of account strings. \"\"\" return _GetAccounts.get_entry_accounts(entry)","title":"get_entry_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_leveln_parent_accounts","text":"Return a list of all the unique leaf names at level N in an account hierarchy. Parameters: account_names \u2013 A list of account names (strings) level \u2013 The level to cross-cut. 0 is for root accounts. nrepeats \u2013 A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. Source code in beancount/core/getters.py def get_leveln_parent_accounts(account_names, level, nrepeats=0): \"\"\"Return a list of all the unique leaf names at level N in an account hierarchy. Args: account_names: A list of account names (strings) level: The level to cross-cut. 0 is for root accounts. nrepeats: A minimum number of times a leaf is required to be present in the the list of unique account names in order to be returned by this function. Returns: A list of leaf node names. \"\"\" leveldict = defaultdict(int) for account_name in set(account_names): components = account.split(account_name) if level < len(components): leveldict[components[level]] += 1 levels = {level_ for level_, count in leveldict.items() if count > nrepeats} return sorted(levels)","title":"get_leveln_parent_accounts()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_min_max_dates","text":"Return the minimum and maximum dates in the list of entries. Parameters: entries \u2013 A list of directive instances. types \u2013 An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. Source code in beancount/core/getters.py def get_min_max_dates(entries, types=None): \"\"\"Return the minimum and maximum dates in the list of entries. Args: entries: A list of directive instances. types: An optional tuple of types to restrict the entries to. Returns: A pair of datetime.date dates, the minimum and maximum dates seen in the directives. \"\"\" date_first = date_last = None for entry in entries: if types and not isinstance(entry, types): continue date_first = entry.date break for entry in reversed(entries): if types and not isinstance(entry, types): continue date_last = entry.date break return (date_first, date_last)","title":"get_min_max_dates()"},{"location":"api_reference/beancount.core.html#beancount.core.getters.get_values_meta","text":"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Parameters: name_to_entries_map \u2013 A dict of something to an entry or None. meta_keys \u2013 A list of strings, the keys to fetch from the metadata. default \u2013 The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. Source code in beancount/core/getters.py def get_values_meta(name_to_entries_map, *meta_keys, default=None): \"\"\"Get a map of the metadata from a map of entries values. Given a dict of some key to a directive instance (or None), return a mapping of the key to the metadata extracted from each directive, or a default value. This can be used to gather a particular piece of metadata from an accounts map or a commodities map. Args: name_to_entries_map: A dict of something to an entry or None. meta_keys: A list of strings, the keys to fetch from the metadata. default: The default value to use if the metadata is not available or if the value/entry is None. Returns: A mapping of the keys of name_to_entries_map to the values of the 'meta_keys' metadata. If there are multiple 'meta_keys', each value is a tuple of them. On the other hand, if there is only a single one, the value itself is returned. \"\"\" value_map = {} for key, entry in name_to_entries_map.items(): value_list = [] for meta_key in meta_keys: value_list.append(entry.meta.get(meta_key, default) if entry is not None else default) value_map[key] = (value_list[0] if len(meta_keys) == 1 else tuple(value_list)) return value_map","title":"get_values_meta()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate","text":"Code used to automatically complete postings without positions.","title":"interpolate"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError","text":"BalanceError(source, message, entry)","title":"BalanceError"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/interpolate.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__new__","text":"Create new instance of BalanceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.BalanceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/interpolate.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_entries_balance","text":"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Parameters: entries \u2013 A list of directives. prefix \u2013 If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date \u2013 A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. Source code in beancount/core/interpolate.py def compute_entries_balance(entries, prefix=None, date=None): \"\"\"Compute the balance of all postings of a list of entries. Sum up all the positions in all the postings of all the transactions in the list of entries and return an inventory of it. Args: entries: A list of directives. prefix: If specified, a prefix string to restrict by account name. Only postings with an account that starts with this prefix will be summed up. date: A datetime.date instance at which to stop adding up the balance. The date is exclusive. Returns: An instance of Inventory. \"\"\" total_balance = Inventory() for entry in entries: if not (date is None or entry.date < date): break if isinstance(entry, Transaction): for posting in entry.postings: if prefix is None or posting.account.startswith(prefix): total_balance.add_position(posting) return total_balance","title":"compute_entries_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_entry_context","text":"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Parameters: entries \u2013 A list of directives. context_entry \u2013 The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. Source code in beancount/core/interpolate.py def compute_entry_context(entries, context_entry): \"\"\"Compute the balances of all accounts referenced by entry up to entry. This provides the inventory of the accounts to which the entry is to be applied, before and after. Args: entries: A list of directives. context_entry: The entry for which we want to obtain the before and after context. Returns: Two dicts of account-name to Inventory instance, one which represents the context before the entry is applied, and one that represents the context after it has been applied. \"\"\" assert context_entry is not None, \"context_entry is missing.\" # Get the set of accounts for which to compute the context. context_accounts = getters.get_entry_accounts(context_entry) # Iterate over the entries until we find the target one and accumulate the # balance. context_before = collections.defaultdict(inventory.Inventory) for entry in entries: if entry is context_entry: break if isinstance(entry, Transaction): for posting in entry.postings: if not any(posting.account == account for account in context_accounts): continue balance = context_before[posting.account] balance.add_position(posting) # Compute the after context for the entry. context_after = copy.deepcopy(context_before) if isinstance(context_entry, Transaction): for posting in entry.postings: balance = context_after[posting.account] balance.add_position(posting) return context_before, context_after","title":"compute_entry_context()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.compute_residual","text":"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Parameters: postings \u2013 A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. Source code in beancount/core/interpolate.py def compute_residual(postings): \"\"\"Compute the residual of a set of complete postings, and the per-currency precision. This is used to cross-check a balanced transaction. The precision is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. Args: postings: A list of Posting instances. Returns: An instance of Inventory, with the residual of the given list of postings. \"\"\" inventory = Inventory() for posting in postings: # Skip auto-postings inserted to absorb the residual (rounding error). if posting.meta and posting.meta.get(AUTOMATIC_RESIDUAL, False): continue # Add to total residual balance. inventory.add_amount(convert.get_weight(posting)) return inventory","title":"compute_residual()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.fill_residual_posting","text":"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Parameters: entry \u2013 An instance of a Transaction. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. Source code in beancount/core/interpolate.py def fill_residual_posting(entry, account_rounding): \"\"\"If necessary, insert a posting to absorb the residual. This makes the transaction balance exactly. Note: This was developed in order to tweak transactions before exporting them to Ledger. A better method would be to enable the feature that automatically inserts these rounding postings on all transactions, and so maybe this method can be deprecated if we do so. Args: entry: An instance of a Transaction. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A possibly new, modified entry with a new posting. If a residual was not needed - the transaction already balanced perfectly - no new leg is inserted. \"\"\" residual = compute_residual(entry.postings) if not residual.is_empty(): new_postings = list(entry.postings) new_postings.extend(get_residual_postings(residual, account_rounding)) entry = entry._replace(postings=new_postings) return entry","title":"fill_residual_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.get_residual_postings","text":"Create postings to book the given residuals. Parameters: residual \u2013 An Inventory, the residual positions. account_rounding \u2013 A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. Source code in beancount/core/interpolate.py def get_residual_postings(residual, account_rounding): \"\"\"Create postings to book the given residuals. Args: residual: An Inventory, the residual positions. account_rounding: A string, the name of the rounding account that absorbs residuals / rounding errors. Returns: A list of new postings to be inserted to reduce the given residual. \"\"\" meta = {AUTOMATIC_META: True, AUTOMATIC_RESIDUAL: True} return [Posting(account_rounding, -position.units, position.cost, None, None, meta.copy()) for position in residual.get_positions()]","title":"get_residual_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.has_nontrivial_balance","text":"Return True if a Posting has a balance amount that would have to be calculated. Parameters: posting \u2013 A Posting instance. Returns: A boolean. Source code in beancount/core/interpolate.py def has_nontrivial_balance(posting): \"\"\"Return True if a Posting has a balance amount that would have to be calculated. Args: posting: A Posting instance. Returns: A boolean. \"\"\" return posting.cost or posting.price","title":"has_nontrivial_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.infer_tolerances","text":"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Parameters: postings \u2013 A list of Posting instances. options_map \u2013 A dict of options. use_cost \u2013 A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. Source code in beancount/core/interpolate.py def infer_tolerances(postings, options_map, use_cost=None): \"\"\"Infer tolerances from a list of postings. The tolerance is the maximum fraction that is being used for each currency (a dict). We use the currency of the weight amount in order to infer the quantization precision for each currency. Integer amounts aren't contributing to the determination of precision. The 'use_cost' option allows one to experiment with letting postings at cost and at price influence the maximum value of the tolerance. It's tricky to use and alters the definition of the tolerance in a non-trivial way, if you use it. The tolerance is expanded by the sum of the cost times a fraction 'M' of the smallest digits in the number of units for all postings held at cost. For example, in this transaction: 2006-01-17 * \"Plan Contribution\" Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:VWELX 18.572 VWELX {30.96 USD} Assets:Investments:Cash -1150.00 USD The tolerance for units of USD will calculated as the MAXIMUM of: 0.01 * M = 0.005 (from the 1150.00 USD leg) The sum of 0.001 * M x 30.96 = 0.01548 + 0.001 * M x 30.96 = 0.01548 = 0.03096 So the tolerance for USD in this case is max(0.005, 0.03096) = 0.03096. Prices contribute similarly to the maximum tolerance allowed. Note that 'M' above is the inferred_tolerance_multiplier and its default value is 0.5. Args: postings: A list of Posting instances. options_map: A dict of options. use_cost: A boolean, true if we should be using a combination of the smallest digit of the number times the cost or price in order to infer the tolerance. If the value is left unspecified (as 'None'), the default value can be overridden by setting an option. Returns: A dict of currency to the tolerated difference amount to be used for it, e.g. 0.005. \"\"\" if use_cost is None: use_cost = options_map[\"infer_tolerance_from_cost\"] inferred_tolerance_multiplier = options_map[\"inferred_tolerance_multiplier\"] default_tolerances = options_map[\"inferred_tolerance_default\"] tolerances = default_tolerances.copy() cost_tolerances = collections.defaultdict(D) for posting in postings: # Skip the precision on automatically inferred postings. if posting.meta and AUTOMATIC_META in posting.meta: continue units = posting.units if not (isinstance(units, Amount) and isinstance(units.number, Decimal)): continue # Compute bounds on the number. currency = units.currency expo = units.number.as_tuple().exponent if expo < 0: # Note: the exponent is a negative value. tolerance = ONE.scaleb(expo) * inferred_tolerance_multiplier tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) if not use_cost: continue # Compute bounds on the smallest digit of the number implied as cost. cost = posting.cost if cost is not None: cost_currency = cost.currency if isinstance(cost, Cost): cost_tolerance = min(tolerance * cost.number, MAXIMUM_TOLERANCE) else: assert isinstance(cost, CostSpec) cost_tolerance = MAXIMUM_TOLERANCE for cost_number in cost.number_total, cost.number_per: if cost_number is None or cost_number is MISSING: continue cost_tolerance = min(tolerance * cost_number, cost_tolerance) cost_tolerances[cost_currency] += cost_tolerance # Compute bounds on the smallest digit of the number implied as cost. price = posting.price if isinstance(price, Amount) and isinstance(price.number, Decimal): price_currency = price.currency price_tolerance = min(tolerance * price.number, MAXIMUM_TOLERANCE) cost_tolerances[price_currency] += price_tolerance for currency, tolerance in cost_tolerances.items(): tolerances[currency] = max(tolerance, tolerances.get(currency, -1024)) default = tolerances.pop('*', ZERO) return defdict.ImmutableDictWithDefault(tolerances, default=default)","title":"infer_tolerances()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.is_tolerance_user_specified","text":"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Parameters: tolerance \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/interpolate.py def is_tolerance_user_specified(tolerance): \"\"\"Return true if the given tolerance number was user-specified. This would allow the user to provide a tolerance like # 0.1234 but not 0.123456. This is used to detect whether a tolerance value # is input by the user and not inferred automatically. Args: tolerance: An instance of Decimal. Returns: A boolean. \"\"\" return len(tolerance.as_tuple().digits) < MAX_TOLERANCE_DIGITS","title":"is_tolerance_user_specified()"},{"location":"api_reference/beancount.core.html#beancount.core.interpolate.quantize_with_tolerance","text":"Quantize the units using the tolerance dict. Parameters: tolerances \u2013 A dict of currency to tolerance Decimalvalues. number \u2013 A number to quantize. currency \u2013 A string currency. Returns: A Decimal, the number possibly quantized. Source code in beancount/core/interpolate.py def quantize_with_tolerance(tolerances, currency, number): \"\"\"Quantize the units using the tolerance dict. Args: tolerances: A dict of currency to tolerance Decimalvalues. number: A number to quantize. currency: A string currency. Returns: A Decimal, the number possibly quantized. \"\"\" # Applying rounding to the default tolerance, if there is one. tolerance = tolerances.get(currency) if tolerance: quantum = (tolerance * 2).normalize() # If the tolerance is a neat number provided by the user, # quantize the inferred numbers. See doc on quantize(): # # Unlike other operations, if the length of the coefficient # after the quantize operation would be greater than # precision, then an InvalidOperation is signaled. This # guarantees that, unless there is an error condition, the # quantized exponent is always equal to that of the # right-hand operand. if is_tolerance_user_specified(quantum): number = number.quantize(quantum) return number","title":"quantize_with_tolerance()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory","text":"A container for an inventory of positions. This module provides a container class that can hold positions. An inventory is a mapping of positions, where each position is keyed by (currency: str, cost: Cost) -> position: Position where 'currency': The commodity under consideration, USD, CAD, or stock units such as HOOL, MSFT, AAPL, etc.; 'cost': None or a Cost instance existing of cost currency, number, date, and label; 'position': A Position object, whose 'units' attribute is guaranteed to have the same currency as 'currency' and whose 'cost' attribute is equal to the 'cost' key. It basically stores the number of units. This is meant to accommodate both booked and non-booked amounts. The clever trick that we pull to do this is that for positions which aren't booked, we simply leave the 'cost' as None. This is the case for most of the transactions.","title":"inventory"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Booking","text":"Result of booking a new lot to an existing inventory.","title":"Booking"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory","text":"An Inventory is a set of positions. Attributes: Name Type Description positions A list of Position instances, held in this Inventory object.","title":"Inventory"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__abs__","text":"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __abs__(self): \"\"\"Return an inventory with the absolute value of each position. Returns: An instance of Inventory. \"\"\" return Inventory({key: abs(pos) for key, pos in self.items()})","title":"__abs__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__add__","text":"Add another inventory to this one. This inventory is not modified. Parameters: other \u2013 An instance of Inventory. Returns: A new instance of Inventory. Source code in beancount/core/inventory.py def __add__(self, other): \"\"\"Add another inventory to this one. This inventory is not modified. Args: other: An instance of Inventory. Returns: A new instance of Inventory. \"\"\" new_inventory = self.__copy__() new_inventory.add_inventory(other) return new_inventory","title":"__add__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__copy__","text":"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. Source code in beancount/core/inventory.py def __copy__(self): \"\"\"A shallow copy of this inventory object. Returns: An instance of Inventory, equal to this one. \"\"\" return Inventory(self)","title":"__copy__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__iadd__","text":"Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self","title":"__iadd__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__init__","text":"Create a new inventory using a list of existing positions. Parameters: positions \u2013 A list of Position instances or an existing dict or Inventory instance. Source code in beancount/core/inventory.py def __init__(self, positions=None): \"\"\"Create a new inventory using a list of existing positions. Args: positions: A list of Position instances or an existing dict or Inventory instance. \"\"\" if isinstance(positions, (dict, Inventory)): dict.__init__(self, positions) else: dict.__init__(self) if positions: assert isinstance(positions, Iterable) for position in positions: self.add_position(position)","title":"__init__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__iter__","text":"Iterate over the positions. Note that there is no guaranteed order. Source code in beancount/core/inventory.py def __iter__(self): \"\"\"Iterate over the positions. Note that there is no guaranteed order.\"\"\" return iter(self.values())","title":"__iter__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__lt__","text":"Inequality comparison operator. Source code in beancount/core/inventory.py def __lt__(self, other): \"\"\"Inequality comparison operator.\"\"\" return sorted(self) < sorted(other)","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__mul__","text":"Scale/multiply the contents of the inventory. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the inventory. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Inventory({key: pos * scalar for key, pos in self.items()})","title":"__mul__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__neg__","text":"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def __neg__(self): \"\"\"Return an inventory with the negative of values of this one. Returns: An instance of Inventory. \"\"\" return Inventory({key: -pos for key, pos in self.items()})","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__repr__","text":"Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.__str__","text":"Render as a human-readable string. Returns: A string, for human consumption. Source code in beancount/core/inventory.py def __str__(self): \"\"\"Render as a human-readable string. Returns: A string, for human consumption. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_amount","text":"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Parameters: units \u2013 An Amount instance to add. cost \u2013 An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. Source code in beancount/core/inventory.py def add_amount(self, units, cost=None): \"\"\"Add to this inventory using amount and cost. This adds with strict lot matching, that is, no partial matches are done on the arguments to the keys of the inventory. Args: units: An Amount instance to add. cost: An instance of Cost or None, as a key to the inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified BEFORE it was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Position may be None if there is no corresponding Position object, e.g. the position was deleted. \"\"\" if ASSERTS_TYPES: assert isinstance(units, Amount), ( \"Internal error: {!r} (type: {})\".format(units, type(units).__name__)) assert cost is None or isinstance(cost, Cost), ( \"Internal error: {!r} (type: {})\".format(cost, type(cost).__name__)) # Find the position. key = (units.currency, cost) pos = self.get(key, None) if pos is not None: # Note: In order to augment or reduce, all the fields have to match. # Check if reducing. booking = (Booking.REDUCED if not same_sign(pos.units.number, units.number) else Booking.AUGMENTED) # Compute the new number of units. number = pos.units.number + units.number if number == ZERO: # If empty, delete the position. del self[key] else: # Otherwise update it. self[key] = Position(Amount(number, units.currency), cost) else: # If not found, create a new one. if units.number == ZERO: booking = Booking.IGNORED else: self[key] = Position(units, cost) booking = Booking.CREATED return pos, booking","title":"add_amount()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_inventory","text":"Add all the positions of another Inventory instance to this one. Parameters: other \u2013 An instance of Inventory to add to this one. Returns: This inventory, modified. Source code in beancount/core/inventory.py def add_inventory(self, other): \"\"\"Add all the positions of another Inventory instance to this one. Args: other: An instance of Inventory to add to this one. Returns: This inventory, modified. \"\"\" if self.is_empty(): # Optimization for empty inventories; if the current one is empty, # adopt all of the other inventory's positions without running # through the full aggregation checks. This should be very cheap. We # can do this because the positions are immutable. self.update(other) else: for position in other.get_positions(): self.add_position(position) return self","title":"add_inventory()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.add_position","text":"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Parameters: position \u2013 The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. Source code in beancount/core/inventory.py def add_position(self, position): \"\"\"Add using a position (with strict lot matching). Return True if this position was booked against and reduced another. Args: position: The Posting or Position to add to this inventory. Returns: A pair of (position, booking) where 'position' is the position that that was modified, and where 'booking' is a Booking enum that hints at how the lot was booked to this inventory. \"\"\" if ASSERTS_TYPES: assert hasattr(position, 'units') and hasattr(position, 'cost'), ( \"Invalid type for position: {}\".format(position)) assert isinstance(position.cost, (type(None), Cost)), ( \"Invalid type for cost: {}\".format(position.cost)) return self.add_amount(position.units, position.cost)","title":"add_position()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.average","text":"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def average(self): \"\"\"Average all lots of the same currency together. Use the minimum date from each aggregated set of lots. Returns: An instance of Inventory. \"\"\" groups = collections.defaultdict(list) for position in self: key = (position.units.currency, position.cost.currency if position.cost else None) groups[key].append(position) average_inventory = Inventory() for (currency, cost_currency), positions in groups.items(): total_units = sum(position.units.number for position in positions) # Explicitly skip aggregates when resulting in zero units. if total_units == ZERO: continue units_amount = Amount(total_units, currency) if cost_currency: total_cost = sum(convert.get_cost(position).number for position in positions) cost_number = (Decimal('Infinity') if total_units == ZERO else (total_cost / total_units)) min_date = None for pos in positions: pos_date = pos.cost.date if pos.cost else None if pos_date is not None: min_date = (pos_date if min_date is None else min(min_date, pos_date)) cost = Cost(cost_number, cost_currency, min_date, None) else: cost = None average_inventory.add_amount(units_amount, cost) return average_inventory","title":"average()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.cost_currencies","text":"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def cost_currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A set of currency strings. \"\"\" return set(cost.currency for _, cost in self.keys() if cost is not None)","title":"cost_currencies()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.currencies","text":"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. Source code in beancount/core/inventory.py def currencies(self): \"\"\"Return the list of unit currencies held in this inventory. Returns: A list of currency strings. \"\"\" return set(currency for currency, _ in self.keys())","title":"currencies()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.currency_pairs","text":"Return the commodities held in this inventory. Returns: A set of currency strings. Source code in beancount/core/inventory.py def currency_pairs(self): \"\"\"Return the commodities held in this inventory. Returns: A set of currency strings. \"\"\" return set(position.currency_pair() for position in self)","title":"currency_pairs()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.from_string","text":"Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_currency_units","text":"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Parameters: currency \u2013 A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. Source code in beancount/core/inventory.py def get_currency_units(self, currency): \"\"\"Fetch the total amount across all the position in the given currency. This may sum multiple lots in the same currency denomination. Args: currency: A string, the currency to filter the positions with. Returns: An instance of Amount, with the given currency. \"\"\" total_units = ZERO for position in self: if position.units.currency == currency: total_units += position.units.number return Amount(total_units, currency)","title":"get_currency_units()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_only_position","text":"Return the first position and assert there are no more. If the inventory is empty, return None. Source code in beancount/core/inventory.py def get_only_position(self): \"\"\"Return the first position and assert there are no more. If the inventory is empty, return None. \"\"\" if len(self) > 0: if len(self) > 1: raise AssertionError(\"Inventory has more than one expected \" \"position: {}\".format(self)) return next(iter(self))","title":"get_only_position()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.get_positions","text":"Return the positions in this inventory. Returns: A shallow copy of the list of positions. Source code in beancount/core/inventory.py def get_positions(self): \"\"\"Return the positions in this inventory. Returns: A shallow copy of the list of positions. \"\"\" return list(iter(self))","title":"get_positions()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_empty","text":"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. Source code in beancount/core/inventory.py def is_empty(self): \"\"\"Return true if the inventory is empty, that is, has no positions. Returns: A boolean. \"\"\" return len(self) == 0","title":"is_empty()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_mixed","text":"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. Source code in beancount/core/inventory.py def is_mixed(self): \"\"\"Return true if the inventory contains a mix of positive and negative lots for at least one instrument. Returns: A boolean. \"\"\" signs_map = {} for position in self: sign = position.units.number >= 0 prev_sign = signs_map.setdefault(position.units.currency, sign) if sign != prev_sign: return True return False","title":"is_mixed()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_reduced_by","text":"Return true if the amount could reduce this inventory. Parameters: ramount \u2013 An instance of Amount. Returns: A boolean. Source code in beancount/core/inventory.py def is_reduced_by(self, ramount): \"\"\"Return true if the amount could reduce this inventory. Args: ramount: An instance of Amount. Returns: A boolean. \"\"\" if ramount.number == ZERO: return False for position in self: units = position.units if (ramount.currency == units.currency and not same_sign(ramount.number, units.number)): return True return False","title":"is_reduced_by()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.is_small","text":"Return true if all the positions in the inventory are small. Parameters: tolerances \u2013 A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. Source code in beancount/core/inventory.py def is_small(self, tolerances): \"\"\"Return true if all the positions in the inventory are small. Args: tolerances: A Decimal, the small number of units under which a position is considered small, or a dict of currency to such epsilon precision. Returns: A boolean. \"\"\" if isinstance(tolerances, dict): for position in self: tolerance = tolerances.get(position.units.currency, ZERO) if abs(position.units.number) > tolerance: return False small = True else: small = not any(abs(position.units.number) > tolerances for position in self) return small","title":"is_small()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.reduce","text":"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. Source code in beancount/core/inventory.py def reduce(self, reducer, *args): \"\"\"Reduce an inventory using one of the conversion functions. See functions in beancount.core.convert. Returns: An instance of Inventory. \"\"\" inventory = Inventory() for position in self: inventory.add_amount(reducer(position, *args)) return inventory","title":"reduce()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.segregate_units","text":"Split up the list of positions to the given currencies. Parameters: currencies \u2013 A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. Source code in beancount/core/inventory.py def segregate_units(self, currencies): \"\"\"Split up the list of positions to the given currencies. Args: currencies: A list of currency strings, the currencies to isolate. Returns: A dict of currency to Inventory instances. \"\"\" per_currency_dict = {currency: Inventory() for currency in currencies} per_currency_dict[None] = Inventory() for position in self: currency = position.units.currency key = (currency if currency in currencies else None) per_currency_dict[key].add_position(position) return per_currency_dict","title":"segregate_units()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.Inventory.to_string","text":"Convert an Inventory instance to a printable string. Parameters: dformat \u2013 An instance of DisplayFormatter. parents \u2013 A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. Source code in beancount/core/inventory.py def to_string(self, dformat=DEFAULT_FORMATTER, parens=True): \"\"\"Convert an Inventory instance to a printable string. Args: dformat: An instance of DisplayFormatter. parents: A boolean, true if we should surround the results by parentheses. Returns: A formatted string of the quantized amount and symbol. \"\"\" fmt = '({})' if parens else '{}' return fmt.format( ', '.join(pos.to_string(dformat) for pos in sorted(self)))","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.check_invariants","text":"Check the invariants of the Inventory. Parameters: inventory \u2013 An instance of Inventory. Returns: True if the invariants are respected. Source code in beancount/core/inventory.py def check_invariants(inv): \"\"\"Check the invariants of the Inventory. Args: inventory: An instance of Inventory. Returns: True if the invariants are respected. \"\"\" # Check that all the keys are unique. lots = set((pos.units.currency, pos.cost) for pos in inv) assert len(lots) == len(inv), \"Invalid inventory: {}\".format(inv) # Check that none of the amounts is zero. for pos in inv: assert pos.units.number != ZERO, \"Invalid position size: {}\".format(pos)","title":"check_invariants()"},{"location":"api_reference/beancount.core.html#beancount.core.inventory.from_string","text":"Create an Inventory from a string. This is useful for writing tests. Parameters: string \u2013 A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. Source code in beancount/core/inventory.py @staticmethod def from_string(string): \"\"\"Create an Inventory from a string. This is useful for writing tests. Args: string: A comma-separated string of with an optional { } for the cost. Returns: A new instance of Inventory with the given balances. \"\"\" new_inventory = Inventory() # We need to split the comma-separated positions but ignore commas # occurring within a {...cost...} specification. position_strs = re.split( r'([-+]?[0-9,.]+\\s+[A-Z]+\\s*(?:{[^}]*})?)\\s*,?\\s*', string)[1::2] for position_str in position_strs: new_inventory.add_position(position_from_string(position_str)) return new_inventory","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.number","text":"The module contains the basic Decimal type import. About Decimal usage: Do not import Decimal from the 'decimal' or 'cdecimal' modules; always import your Decimal class from beancount.core.amount. Prefer to use D() to create new instances of Decimal objects, which handles more syntax, e.g., handles None, and numbers with commas.","title":"number"},{"location":"api_reference/beancount.core.html#beancount.core.number.D","text":"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Parameters: strord \u2013 A string or Decimal instance. Returns: A Decimal instance. Source code in beancount/core/number.py def D(strord=None): \"\"\"Convert a string into a Decimal object. This is used in parsing amounts from files in the importers. This is the main function you should use to build all numbers the system manipulates (never use floating-point in an accounting system). Commas are stripped and ignored, as they are assumed to be thousands separators (the French comma separator as decimal is not supported). This function just returns the argument if it is already a Decimal object, for convenience. Args: strord: A string or Decimal instance. Returns: A Decimal instance. \"\"\" try: # Note: try a map lookup and optimize performance here. if strord is None or strord == '': return Decimal() elif isinstance(strord, str): return Decimal(_CLEAN_NUMBER_RE.sub('', strord)) elif isinstance(strord, Decimal): return strord elif isinstance(strord, (int, float)): return Decimal(strord) else: assert strord is None, \"Invalid value to convert: {}\".format(strord) except Exception as exc: raise ValueError(\"Impossible to create Decimal instance from {!s}: {}\".format( strord, exc))","title":"D()"},{"location":"api_reference/beancount.core.html#beancount.core.number.is_fast_decimal","text":"Return true if a fast C decimal implementation is installed. Source code in beancount/core/number.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType)","title":"is_fast_decimal()"},{"location":"api_reference/beancount.core.html#beancount.core.number.round_to","text":"Round a number down to a particular increment. Parameters: number \u2013 A Decimal, the number to be rounded. increment \u2013 A Decimal, the size of the increment. Returns: A Decimal, the rounded number. Source code in beancount/core/number.py def round_to(number, increment): \"\"\"Round a number *down* to a particular increment. Args: number: A Decimal, the number to be rounded. increment: A Decimal, the size of the increment. Returns: A Decimal, the rounded number. \"\"\" return int((number / increment)) * increment","title":"round_to()"},{"location":"api_reference/beancount.core.html#beancount.core.number.same_sign","text":"Return true if both numbers have the same sign. Parameters: number1 \u2013 An instance of Decimal. number2 \u2013 An instance of Decimal. Returns: A boolean. Source code in beancount/core/number.py def same_sign(number1, number2): \"\"\"Return true if both numbers have the same sign. Args: number1: An instance of Decimal. number2: An instance of Decimal. Returns: A boolean. \"\"\" return (number1 >= 0) == (number2 >= 0)","title":"same_sign()"},{"location":"api_reference/beancount.core.html#beancount.core.position","text":"A position object, which consists of units Amount and cost Cost. See types below for details.","title":"position"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost","text":"Cost(number, currency, date, label)","title":"Cost"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__new__","text":"Create new instance of Cost(number, currency, date, label)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Cost.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec","text":"CostSpec(number_per, number_total, currency, date, label, merge)","title":"CostSpec"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/core/position.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__new__","text":"Create new instance of CostSpec(number_per, number_total, currency, date, label, merge)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.CostSpec.__repr__","text":"Return a nicely formatted representation string Source code in beancount/core/position.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position","text":"A 'Position' is a pair of units and optional cost. This is used to track inventories. Attributes: Name Type Description units Amount An Amount, the number of units and its currency. cost Cost A Cost that represents the lot, or None.","title":"Position"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__abs__","text":"Return the absolute value of the position. Returns: An instance of Position with the absolute units. Source code in beancount/core/position.py def __abs__(self): \"\"\"Return the absolute value of the position. Returns: An instance of Position with the absolute units. \"\"\" return Position(amount_abs(self.units), self.cost)","title":"__abs__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__copy__","text":"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. Source code in beancount/core/position.py def __copy__(self): \"\"\"Shallow copy, except for the lot, which can be shared. This is important for performance reasons; a lot of time is spent here during balancing. Returns: A shallow copy of this position. \"\"\" # Note: We use Decimal() for efficiency. return Position(copy.copy(self.units), copy.copy(self.cost))","title":"__copy__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__eq__","text":"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Parameters: other \u2013 An instance of Position, or None. Returns: A boolean, true if the positions are equal. Source code in beancount/core/position.py def __eq__(self, other): \"\"\"Equality comparison with another Position. The objects are considered equal if both number and lot are matching, and if the number of units is zero and the other position is None, that is also okay. Args: other: An instance of Position, or None. Returns: A boolean, true if the positions are equal. \"\"\" return (self.units.number == ZERO if other is None else (self.units == other.units and self.cost == other.cost))","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__hash__","text":"Compute a hash for this position. Returns: A hash of this position object. Source code in beancount/core/position.py def __hash__(self): \"\"\"Compute a hash for this position. Returns: A hash of this position object. \"\"\" return hash((self.units, self.cost))","title":"__hash__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__lt__","text":"A less-than comparison operator for positions. Parameters: other \u2013 Another instance of Position. Returns: True if this positions is smaller than the other position. Source code in beancount/core/position.py def __lt__(self, other): \"\"\"A less-than comparison operator for positions. Args: other: Another instance of Position. Returns: True if this positions is smaller than the other position. \"\"\" return self.sortkey() < other.sortkey()","title":"__lt__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__mul__","text":"Scale/multiply the contents of the position. Parameters: scalar \u2013 A Decimal. Returns: An instance of Inventory. Source code in beancount/core/position.py def __mul__(self, scalar): \"\"\"Scale/multiply the contents of the position. Args: scalar: A Decimal. Returns: An instance of Inventory. \"\"\" return Position(amount_mul(self.units, scalar), self.cost)","title":"__mul__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__neg__","text":"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost)","title":"__neg__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__new__","text":"Create new instance of _Position(units, cost) Source code in beancount/core/position.py def __new__(cls, units, cost=None): assert isinstance(units, Amount), ( \"Expected an Amount for units; received '{}'\".format(units)) assert cost is None or isinstance(cost, Position.cost_types), ( \"Expected a Cost for cost; received '{}'\".format(cost)) return _Position.__new__(cls, units, cost)","title":"__new__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__repr__","text":"Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string()","title":"__repr__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.__str__","text":"Return a string representation of the position. Returns: A string, a printable representation of the position. Source code in beancount/core/position.py def __str__(self): \"\"\"Return a string representation of the position. Returns: A string, a printable representation of the position. \"\"\" return self.to_string()","title":"__str__()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.currency_pair","text":"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. Source code in beancount/core/position.py def currency_pair(self): \"\"\"Return the currency pair associated with this position. Returns: A pair of a currency string and a cost currency string or None. \"\"\" return (self.units.currency, self.cost.currency if self.cost else None)","title":"currency_pair()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.from_amounts","text":"Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost)","title":"from_amounts()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.from_string","text":"Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.get_negative","text":"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. Source code in beancount/core/position.py def get_negative(self): \"\"\"Get a copy of this position but with a negative number. Returns: An instance of Position which represents the inverse of this Position. \"\"\" # Note: We use Decimal() for efficiency. return Position(-self.units, self.cost)","title":"get_negative()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.is_negative_at_cost","text":"Return true if the position is held at cost and negative. Returns: A boolean. Source code in beancount/core/position.py def is_negative_at_cost(self): \"\"\"Return true if the position is held at cost and negative. Returns: A boolean. \"\"\" return (self.units.number < ZERO and self.cost is not None)","title":"is_negative_at_cost()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.sortkey","text":"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. Source code in beancount/core/position.py def sortkey(self): \"\"\"Return a key to sort positions by. This key depends on the order of the currency of the lot (we want to order common currencies first) and the number of units. Returns: A tuple, used to sort lists of positions. \"\"\" currency = self.units.currency order_units = CURRENCY_ORDER.get(currency, NCURRENCIES + len(currency)) if self.cost is not None: cost_number = self.cost.number cost_currency = self.cost.currency else: cost_number = ZERO cost_currency = '' return (order_units, cost_number, cost_currency, self.units.number)","title":"sortkey()"},{"location":"api_reference/beancount.core.html#beancount.core.position.Position.to_string","text":"Render the position to a string.See to_string() for details. Source code in beancount/core/position.py def to_string(self, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the position to a string.See to_string() for details. \"\"\" return to_string(self, dformat, detail)","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.cost_to_str","text":"Format an instance of Cost or a CostSpec to a string. Parameters: cost \u2013 An instance of Cost or CostSpec. dformat \u2013 A DisplayFormatter object. detail \u2013 A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. Source code in beancount/core/position.py def cost_to_str(cost, dformat, detail=True): \"\"\"Format an instance of Cost or a CostSpec to a string. Args: cost: An instance of Cost or CostSpec. dformat: A DisplayFormatter object. detail: A boolean, true if we should render the non-amount components. Returns: A string, suitable for formatting. \"\"\" strlist = [] if isinstance(cost, Cost): if isinstance(cost.number, Decimal): strlist.append(Amount(cost.number, cost.currency).to_string(dformat)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) elif isinstance(cost, CostSpec): if isinstance(cost.number_per, Decimal) or isinstance(cost.number_total, Decimal): amountlist = [] if isinstance(cost.number_per, Decimal): amountlist.append(dformat.format(cost.number_per)) if isinstance(cost.number_total, Decimal): amountlist.append('#') amountlist.append(dformat.format(cost.number_total)) if isinstance(cost.currency, str): amountlist.append(cost.currency) strlist.append(' '.join(amountlist)) if detail: if cost.date: strlist.append(cost.date.isoformat()) if cost.label: strlist.append('\"{}\"'.format(cost.label)) if cost.merge: strlist.append('*') return ', '.join(strlist)","title":"cost_to_str()"},{"location":"api_reference/beancount.core.html#beancount.core.position.from_amounts","text":"Create a position from an amount and a cost. Parameters: amount \u2013 An amount, that represents the number of units and the lot currency. cost_amount \u2013 If not None, represents the cost amount. Returns: A Position instance. Source code in beancount/core/position.py @staticmethod def from_amounts(units, cost_amount=None): \"\"\"Create a position from an amount and a cost. Args: amount: An amount, that represents the number of units and the lot currency. cost_amount: If not None, represents the cost amount. Returns: A Position instance. \"\"\" assert cost_amount is None or isinstance(cost_amount, Amount), ( \"Invalid type for cost: {}\".format(cost_amount)) cost = (Cost(cost_amount.number, cost_amount.currency, None, None) if cost_amount else None) return Position(units, cost)","title":"from_amounts()"},{"location":"api_reference/beancount.core.html#beancount.core.position.from_string","text":"Create a position from a string specification. This is a miniature parser used for building tests. Parameters: string \u2013 A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. Source code in beancount/core/position.py @staticmethod def from_string(string): \"\"\"Create a position from a string specification. This is a miniature parser used for building tests. Args: string: A string of with an optional { } for the cost, similar to the parser syntax. Returns: A new instance of Position. \"\"\" match = re.match( (r'\\s*({})\\s+({})' r'(?:\\s+{{([^}}]*)}})?' r'\\s*$').format(NUMBER_RE, CURRENCY_RE), string) if not match: raise ValueError(\"Invalid string for position: '{}'\".format(string)) number = D(match.group(1)) currency = match.group(2) # Parse a cost expression. cost_number = None cost_currency = None date = None label = None cost_expression = match.group(3) if match.group(3): expressions = [expr.strip() for expr in re.split('[,/]', cost_expression)] for expr in expressions: # Match a compound number. match = re.match( r'({NUMBER_RE})\\s*(?:#\\s*({NUMBER_RE}))?\\s+({CURRENCY_RE})$' .format(NUMBER_RE=NUMBER_RE, CURRENCY_RE=CURRENCY_RE), expr ) if match: per_number, total_number, cost_currency = match.group(1, 2, 3) per_number = D(per_number) if per_number else ZERO total_number = D(total_number) if total_number else ZERO if total_number: # Calculate the per-unit cost. total = number * per_number + total_number per_number = total / number cost_number = per_number continue # Match a date. match = re.match(r'(\\d\\d\\d\\d)[-/](\\d\\d)[-/](\\d\\d)$', expr) if match: date = datetime.date(*map(int, match.group(1, 2, 3))) continue # Match a label. match = re.match(r'\"([^\"]+)*\"$', expr) if match: label = match.group(1) continue # Match a merge-cost marker. match = re.match(r'\\*$', expr) if match: raise ValueError(\"Merge-code not supported in string constructor.\") raise ValueError(\"Invalid cost component: '{}'\".format(expr)) cost = Cost(cost_number, cost_currency, date, label) else: cost = None return Position(Amount(number, currency), cost)","title":"from_string()"},{"location":"api_reference/beancount.core.html#beancount.core.position.get_position","text":"Build a Position instance from a Posting instance. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Position. Source code in beancount/core/position.py def get_position(posting): \"\"\"Build a Position instance from a Posting instance. Args: posting: An instance of Posting. Returns: An instance of Position. \"\"\" return Position(posting.units, posting.cost)","title":"get_position()"},{"location":"api_reference/beancount.core.html#beancount.core.position.to_string","text":"Render the Position or Posting instance to a string. Parameters: pos \u2013 An instance of Position or Posting. dformat \u2013 An instance of DisplayFormatter. detail \u2013 A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. Source code in beancount/core/position.py def to_string(pos, dformat=DEFAULT_FORMATTER, detail=True): \"\"\"Render the Position or Posting instance to a string. Args: pos: An instance of Position or Posting. dformat: An instance of DisplayFormatter. detail: A boolean, true if we should only render the lot details beyond the cost (lot-date, label, etc.). If false, we only render the cost, if present. Returns: A string, the rendered position. \"\"\" pos_str = pos.units.to_string(dformat) if pos.cost is not None: pos_str = '{} {{{}}}'.format(pos_str, cost_to_str(pos.cost, dformat, detail)) return pos_str","title":"to_string()"},{"location":"api_reference/beancount.core.html#beancount.core.prices","text":"This module has code that can build a database of historical prices at various times, from which unrealized capital gains and market value can be deduced. Prices are deduced from Price entries found in the file, or perhaps created by scripts (for example you could build a script that will fetch live prices online and create entries on-the-fly).","title":"prices"},{"location":"api_reference/beancount.core.html#beancount.core.prices.PriceMap","text":"A price map dictionary. The keys include both the set of forward (base, quote) pairs and their inverse. In order to determine which are the forward pairs, access the 'forward_pairs' attribute Attributes: Name Type Description forward_pairs A list of (base, quote) keys for the forward pairs.","title":"PriceMap"},{"location":"api_reference/beancount.core.html#beancount.core.prices.build_price_map","text":"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Parameters: entries \u2013 A list of directives, hopefully including some Price and/or Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. Source code in beancount/core/prices.py def build_price_map(entries): \"\"\"Build a price map from a list of arbitrary entries. If multiple prices are found for the same (currency, cost-currency) pair at the same date, the latest date is kept and the earlier ones (for that day) are discarded. If inverse price pairs are found, e.g. USD in AUD and AUD in USD, the inverse that has the smallest number of price points is converted into the one that has the most price points. In that way they are reconciled into a single one. Args: entries: A list of directives, hopefully including some Price and/or Transaction entries. Returns: A dict of (currency, cost-currency) keys to sorted lists of (date, number) pairs, where 'date' is the date the price occurs at and 'number' a Decimal that represents the price, or rate, between these two currencies/commodities. Each date occurs only once in the sorted list of prices of a particular key. All of the inverses are automatically generated in the price map. \"\"\" # Fetch a list of all the price entries seen in the ledger. price_entries = [entry for entry in entries if isinstance(entry, Price)] # Build a map of exchange rates between these units. # (base-currency, quote-currency) -> List of (date, rate). price_map = collections.defaultdict(list) for price in price_entries: base_quote = (price.currency, price.amount.currency) price_map[base_quote].append((price.date, price.amount.number)) # Find pairs of inversed units. inversed_units = [] for base_quote, values in price_map.items(): base, quote = base_quote if (quote, base) in price_map: inversed_units.append(base_quote) # Find pairs of inversed units, and swallow the conversion with the smaller # number of rates into the other one. for base, quote in inversed_units: bq_prices = price_map[(base, quote)] qb_prices = price_map[(quote, base)] remove = ((base, quote) if len(bq_prices) < len(qb_prices) else (quote, base)) base, quote = remove remove_list = price_map[remove] insert_list = price_map[(quote, base)] del price_map[remove] inverted_list = [(date, ONE/rate) for (date, rate) in remove_list if rate != ZERO] insert_list.extend(inverted_list) # Unzip and sort each of the entries and eliminate duplicates on the date. sorted_price_map = PriceMap({ base_quote: list(misc_utils.sorted_uniquify(date_rates, lambda x: x[0], last=True)) for (base_quote, date_rates) in price_map.items()}) # Compute and insert all the inverted rates. forward_pairs = list(sorted_price_map.keys()) for (base, quote), price_list in list(sorted_price_map.items()): # Note: You have to filter out zero prices for zero-cost postings, like # gifted options. sorted_price_map[(quote, base)] = [ (date, ONE/price) for date, price in price_list if price != ZERO] sorted_price_map.forward_pairs = forward_pairs return sorted_price_map","title":"build_price_map()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_all_prices","text":"Return a sorted list of all (date, number) price pairs. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Exceptions: KeyError \u2013 If the base/quote could not be found. Source code in beancount/core/prices.py def get_all_prices(price_map, base_quote): \"\"\"Return a sorted list of all (date, number) price pairs. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A list of (date, Decimal) pairs, sorted by date. Raises: KeyError: If the base/quote could not be found. \"\"\" base_quote = normalize_base_quote(base_quote) return _lookup_price_and_inverse(price_map, base_quote)","title":"get_all_prices()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_last_price_entries","text":"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Parameters: entries \u2013 A list of directives. date \u2013 An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. Source code in beancount/core/prices.py def get_last_price_entries(entries, date): \"\"\"Run through the entries until the given date and return the last Price entry encountered for each (currency, cost-currency) pair. Args: entries: A list of directives. date: An instance of datetime.date. If None, the very latest price is returned. Returns: A list of price entries. \"\"\" price_entry_map = {} for entry in entries: if date is not None and entry.date >= date: break if isinstance(entry, Price): base_quote = (entry.currency, entry.amount.currency) price_entry_map[base_quote] = entry return sorted(price_entry_map.values(), key=data.entry_sortkey)","title":"get_last_price_entries()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_latest_price","text":"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. Source code in beancount/core/prices.py def get_latest_price(price_map, base_quote): \"\"\"Return the latest price/rate from a price map for the given base/quote pair. This is often used to just get the 'current' price if you're looking at the entire set of entries. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. Returns: A pair of (date, number), where 'date' is a datetime.date instance and 'number' is a Decimal of the price, or rate, at that date. The date is the latest date which we have an available price for in the price map. \"\"\" base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) # Look up the list and return the latest element. The lists are assumed to # be sorted. try: price_list = _lookup_price_and_inverse(price_map, base_quote) except KeyError: price_list = None if price_list: return price_list[-1] else: return None, None","title":"get_latest_price()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.get_price","text":"Return the price as of the given date. If the date is unspecified, return the latest price. Parameters: price_map \u2013 A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date \u2013 A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). Source code in beancount/core/prices.py def get_price(price_map, base_quote, date=None): \"\"\"Return the price as of the given date. If the date is unspecified, return the latest price. Args: price_map: A price map, which is a dict of (base, quote) -> list of (date, number) tuples, as created by build_price_map. base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. date: A datetime.date instance, the date at which we want the conversion rate. Returns: A pair of (datetime.date, Decimal) instance. If no price information could be found, return (None, None). \"\"\" if date is None: return get_latest_price(price_map, base_quote) base_quote = normalize_base_quote(base_quote) # Handle the degenerate case of a currency priced into its own. base, quote = base_quote if quote is None or base == quote: return (None, ONE) try: price_list = _lookup_price_and_inverse(price_map, base_quote) index = bisect_key.bisect_right_with_key(price_list, date, key=lambda x: x[0]) if index == 0: return None, None else: return price_list[index-1] except KeyError: return None, None","title":"get_price()"},{"location":"api_reference/beancount.core.html#beancount.core.prices.normalize_base_quote","text":"Convert a slash-separated string to a pair of strings. Parameters: base_quote \u2013 A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. Source code in beancount/core/prices.py def normalize_base_quote(base_quote): \"\"\"Convert a slash-separated string to a pair of strings. Args: base_quote: A pair of strings, the base currency to lookup, and the quote currency to lookup, which expresses which units the base currency is denominated in. This may also just be a string, with a '/' separator. Returns: A pair of strings. \"\"\" if isinstance(base_quote, str): base_quote_norm = tuple(base_quote.split('/')) assert len(base_quote_norm) == 2, base_quote base_quote = base_quote_norm assert isinstance(base_quote, tuple), base_quote return base_quote","title":"normalize_base_quote()"},{"location":"api_reference/beancount.core.html#beancount.core.realization","text":"Realization of specific lists of account postings into reports. This code converts a list of entries into a tree of RealAccount nodes (which stands for \"realized accounts\"). The RealAccount objects contain lists of Posting instances instead of Transactions, or other entry types that are attached to an account, such as a Balance or Note entry. The interface of RealAccount corresponds to that of a regular Python dict, where the keys are the names of the individual components of an account's name, and the values are always other RealAccount instances. If you want to get an account by long account name, there are helper functions in this module for this purpose (see realization.get(), for instance). RealAccount instances also contain the final balance of that account, resulting from its list of postings. You should not build RealAccount trees yourself; instead, you should filter the list of desired directives to display and call the realize() function with them.","title":"realization"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount","text":"A realized account, inserted in a tree, that contains the list of realized entries. Attributes: Name Type Description account A string, the full name of the corresponding account. postings A list of postings associated with this accounting (does not include the postings of children accounts). balance The final balance of the list of postings associated with this account.","title":"RealAccount"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__eq__","text":"Equality predicate. All attributes are compared. Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. Source code in beancount/core/realization.py def __eq__(self, other): \"\"\"Equality predicate. All attributes are compared. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are equal. \"\"\" return (dict.__eq__(self, other) and self.account == other.account and self.balance == other.balance and self.txn_postings == other.txn_postings)","title":"__eq__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__init__","text":"Create a RealAccount instance. Parameters: account_name \u2013 a string, the name of the account. Maybe not be None. Source code in beancount/core/realization.py def __init__(self, account_name, *args, **kwargs): \"\"\"Create a RealAccount instance. Args: account_name: a string, the name of the account. Maybe not be None. \"\"\" super().__init__(*args, **kwargs) assert isinstance(account_name, str) self.account = account_name self.txn_postings = [] self.balance = inventory.Inventory()","title":"__init__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__ne__","text":"Not-equality predicate. See eq . Parameters: other \u2013 Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. Source code in beancount/core/realization.py def __ne__(self, other): \"\"\"Not-equality predicate. See __eq__. Args: other: Another instance of RealAccount. Returns: A boolean, True if the two real accounts are not equal. \"\"\" return not self.__eq__(other)","title":"__ne__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.__setitem__","text":"Prevent the setting of non-string or non-empty keys on this dict. Parameters: key \u2013 The dictionary key. Must be a string. value \u2013 The value, must be a RealAccount instance. Exceptions: KeyError \u2013 If the key is not a string, or is invalid. ValueError \u2013 If the value is not a RealAccount instance. Source code in beancount/core/realization.py def __setitem__(self, key, value): \"\"\"Prevent the setting of non-string or non-empty keys on this dict. Args: key: The dictionary key. Must be a string. value: The value, must be a RealAccount instance. Raises: KeyError: If the key is not a string, or is invalid. ValueError: If the value is not a RealAccount instance. \"\"\" if not isinstance(key, str) or not key: raise KeyError(\"Invalid RealAccount key: '{}'\".format(key)) if not isinstance(value, RealAccount): raise ValueError(\"Invalid RealAccount value: '{}'\".format(value)) if not value.account.endswith(key): raise ValueError(\"RealAccount name '{}' inconsistent with key: '{}'\".format( value.account, key)) return super().__setitem__(key, value)","title":"__setitem__()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.RealAccount.copy","text":"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. Source code in beancount/core/realization.py def copy(self): \"\"\"Override dict.copy() to clone a RealAccount. This is only necessary to correctly implement the copy method. Otherwise, calling .copy() on a RealAccount instance invokes the base class' method, which return just a dict. Returns: A cloned instance of RealAccount, with all members shallow-copied. \"\"\" return copy.copy(self)","title":"copy()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.compute_balance","text":"Compute the total balance of this account and all its subaccounts. Parameters: real_account \u2013 A RealAccount instance. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Returns: An Inventory. Source code in beancount/core/realization.py def compute_balance(real_account, leaf_only=False): \"\"\"Compute the total balance of this account and all its subaccounts. Args: real_account: A RealAccount instance. leaf_only: A boolean flag, true if we should yield only leaves. Returns: An Inventory. \"\"\" return functools.reduce(operator.add, [ ra.balance for ra in iter_children(real_account, leaf_only)])","title":"compute_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.compute_postings_balance","text":"Compute the balance of a list of Postings's or TxnPosting's positions. Parameters: postings \u2013 A list of Posting instances and other directives (which are skipped). Returns: An Inventory. Source code in beancount/core/realization.py def compute_postings_balance(txn_postings): \"\"\"Compute the balance of a list of Postings's or TxnPosting's positions. Args: postings: A list of Posting instances and other directives (which are skipped). Returns: An Inventory. \"\"\" final_balance = inventory.Inventory() for txn_posting in txn_postings: if isinstance(txn_posting, Posting): final_balance.add_position(txn_posting) elif isinstance(txn_posting, TxnPosting): final_balance.add_position(txn_posting.posting) return final_balance","title":"compute_postings_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.contains","text":"True if the given account node contains the subaccount name. Parameters: account_name \u2013 A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. Source code in beancount/core/realization.py def contains(real_account, account_name): \"\"\"True if the given account node contains the subaccount name. Args: account_name: A string, the name of a direct or indirect subaccount of this node. Returns: A boolean, true the name is a child of this node. \"\"\" return get(real_account, account_name) is not None","title":"contains()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.dump","text":"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Parameters: root_account \u2013 A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. Source code in beancount/core/realization.py def dump(root_account): \"\"\"Convert a RealAccount node to a line of lines. Note: this is not meant to be used to produce text reports; the reporting code should produce an intermediate object that contains the structure of it, which can then be rendered to ASCII, HTML or CSV formats. This is intended as a convenient little function for dumping trees of data for debugging purposes. Args: root_account: A RealAccount instance. Returns: A list of tuples of (first_line, continuation_line, real_account) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. real_account: The RealAccount instance which corresponds to this line. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root_account.account, root_account, True)] while stack: prefix, name, real_account, is_last = stack.pop(-1) if real_account is root_account: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(real_account) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (real_account is root_account and not name): lines.append((first + name, cont + cont_name, real_account)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. child_items = sorted(real_account.items(), reverse=True) if child_items: child_iter = iter(child_items) child_name, child_account = next(child_iter) stack.append((cont, child_name, child_account, True)) for child_name, child_account in child_iter: stack.append((cont, child_name, child_account, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), real_node) for (first_line, cont_line, real_node) in lines]","title":"dump()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.dump_balances","text":"Dump a realization tree with balances. Parameters: real_root \u2013 An instance of RealAccount. dformat \u2013 An instance of DisplayFormatter to format the numbers with. at_cost \u2013 A boolean, if true, render the values at cost. fullnames \u2013 A boolean, if true, don't render a tree of accounts and render the full account names. file \u2013 A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. Source code in beancount/core/realization.py def dump_balances(real_root, dformat, at_cost=False, fullnames=False, file=None): \"\"\"Dump a realization tree with balances. Args: real_root: An instance of RealAccount. dformat: An instance of DisplayFormatter to format the numbers with. at_cost: A boolean, if true, render the values at cost. fullnames: A boolean, if true, don't render a tree of accounts and render the full account names. file: A file object to dump the output to. If not specified, we return the output as a string. Returns: A string, the rendered tree, or nothing, if 'file' was provided. \"\"\" if fullnames: # Compute the maximum account name length; maxlen = max(len(real_child.account) for real_child in iter_children(real_root, leaf_only=True)) line_format = '{{:{width}}} {{}}\\n'.format(width=maxlen) else: line_format = '{} {}\\n' output = file or io.StringIO() for first_line, cont_line, real_account in dump(real_root): if not real_account.balance.is_empty(): if at_cost: rinv = real_account.balance.reduce(convert.get_cost) else: rinv = real_account.balance.reduce(convert.get_units) amounts = [position.units for position in rinv.get_positions()] positions = [amount_.to_string(dformat) for amount_ in sorted(amounts, key=amount.sortkey)] else: positions = [''] if fullnames: for position in positions: if not position and len(real_account) > 0: continue # Skip parent accounts with no position to render. output.write(line_format.format(real_account.account, position)) else: line = first_line for position in positions: output.write(line_format.format(line, position)) line = cont_line if file is None: return output.getvalue()","title":"dump_balances()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.filter","text":"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Parameters: real_account \u2013 An instance of RealAccount. predicate \u2013 A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. Source code in beancount/core/realization.py def filter(real_account, predicate): \"\"\"Filter a RealAccount tree of nodes by the predicate. This function visits the tree and applies the predicate on each node. It returns a partial clone of RealAccount whereby on each node - either the predicate is true, or - for at least one child of the node the predicate is true. All the leaves have the predicate be true. Args: real_account: An instance of RealAccount. predicate: A callable/function which accepts a real_account and returns a boolean. If the function returns True, the node is kept. Returns: A shallow clone of RealAccount is always returned. \"\"\" assert isinstance(real_account, RealAccount) real_copy = RealAccount(real_account.account) real_copy.balance = real_account.balance real_copy.txn_postings = real_account.txn_postings for child_name, real_child in real_account.items(): real_child_copy = filter(real_child, predicate) if real_child_copy is not None: real_copy[child_name] = real_child_copy if len(real_copy) > 0 or predicate(real_account): return real_copy","title":"filter()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.find_last_active_posting","text":"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Parameters: txn_postings \u2013 a list of postings or entries. Returns: An entry, or None, if the input list was empty. Source code in beancount/core/realization.py def find_last_active_posting(txn_postings): \"\"\"Look at the end of the list of postings, and find the last posting or entry that is not an automatically added directive. Note that if the account is closed, the last posting is assumed to be a Close directive (this is the case if the input is valid and checks without errors. Args: txn_postings: a list of postings or entries. Returns: An entry, or None, if the input list was empty. \"\"\" for txn_posting in reversed(txn_postings): assert not isinstance(txn_posting, Posting) if not isinstance(txn_posting, (TxnPosting, Open, Close, Pad, Balance, Note)): continue # pylint: disable=bad-continuation if (isinstance(txn_posting, TxnPosting) and txn_posting.txn.flag == flags.FLAG_UNREALIZED): continue return txn_posting","title":"find_last_active_posting()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get","text":"Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of. account_name \u2013 A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default \u2013 The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get(real_account, account_name, default=None): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of. account_name: A string, the name of a possibly indirect child leaf found down the tree of 'real_account' nodes. default: The default value that should be returned if the child subaccount is not found. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) for component in components: real_child = real_account.get(component, default) if real_child is default: return default real_account = real_child return real_account","title":"get()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get_or_create","text":"Fetch the subaccount name from the real_account node. Parameters: real_account \u2013 An instance of RealAccount, the parent node to look for children of, or create under. account_name \u2013 A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. Source code in beancount/core/realization.py def get_or_create(real_account, account_name): \"\"\"Fetch the subaccount name from the real_account node. Args: real_account: An instance of RealAccount, the parent node to look for children of, or create under. account_name: A string, the name of the direct or indirect child leaf to get or create. Returns: A RealAccount instance for the child, or the default value, if the child is not found. \"\"\" if not isinstance(account_name, str): raise ValueError components = account.split(account_name) path = [] for component in components: path.append(component) real_child = real_account.get(component, None) if real_child is None: real_child = RealAccount(account.join(*path)) real_account[component] = real_child real_account = real_child return real_account","title":"get_or_create()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.get_postings","text":"Return a sorted list a RealAccount's postings and children. Parameters: real_account \u2013 An instance of RealAccount. Returns: A list of Posting or directories. Source code in beancount/core/realization.py def get_postings(real_account): \"\"\"Return a sorted list a RealAccount's postings and children. Args: real_account: An instance of RealAccount. Returns: A list of Posting or directories. \"\"\" # We accumulate all the postings at once here instead of incrementally # because we need to return them sorted. accumulator = [] for real_child in iter_children(real_account): accumulator.extend(real_child.txn_postings) accumulator.sort(key=data.posting_sortkey) return accumulator","title":"get_postings()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.index_key","text":"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Parameters: sequence \u2013 The sequence to search. value \u2013 The value to search for. key \u2013 A predicate to call to obtain the value to compare against. cmp \u2013 A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. Source code in beancount/core/realization.py def index_key(sequence, value, key, cmp): \"\"\"Find the index of the first element in 'sequence' which is equal to 'value'. If 'key' is specified, the value compared to the value returned by this function. If the value is not found, return None. Args: sequence: The sequence to search. value: The value to search for. key: A predicate to call to obtain the value to compare against. cmp: A comparison predicate. Returns: The index of the first element found, or None, if the element was not found. \"\"\" for index, element in enumerate(sequence): if cmp(key(element), value): return index return","title":"index_key()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.iter_children","text":"Iterate this account node and all its children, depth-first. Parameters: real_account \u2013 An instance of RealAccount. leaf_only \u2013 A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. Source code in beancount/core/realization.py def iter_children(real_account, leaf_only=False): \"\"\"Iterate this account node and all its children, depth-first. Args: real_account: An instance of RealAccount. leaf_only: A boolean flag, true if we should yield only leaves. Yields: Instances of RealAccount, beginning with this account. The order is undetermined. \"\"\" if leaf_only: if len(real_account) == 0: yield real_account else: for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child, leaf_only): yield real_subchild else: yield real_account for key, real_child in sorted(real_account.items()): for real_subchild in iter_children(real_child): yield real_subchild","title":"iter_children()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.iterate_with_balance","text":"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance after adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Parameters: txn_postings \u2013 A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. Source code in beancount/core/realization.py def iterate_with_balance(txn_postings): \"\"\"Iterate over the entries, accumulating the running balance. For each entry, this yields tuples of the form: (entry, postings, change, balance) entry: This is the directive for this line. If the list contained Posting instance, this yields the corresponding Transaction object. postings: A list of postings on this entry that affect the balance. Only the postings encountered in the input list are included; only those affect the balance. If 'entry' is not a Transaction directive, this should always be an empty list. We preserve the original ordering of the postings as they appear in the input list. change: An Inventory object that reflects the total change due to the postings from this entry that appear in the list. For example, if a Transaction has three postings and two are in the input list, the sum of the two postings will be in the 'change' Inventory object. However, the position for the transactions' third posting--the one not included in the input list--will not be in this inventory. balance: An Inventory object that reflects the balance *after* adding the 'change' inventory due to this entry's transaction. The 'balance' yielded is never None, even for entries that do not affect the balance, that is, with an empty 'change' inventory. It's up to the caller, the one rendering the entry, to decide whether to render this entry's change for a particular entry type. Note that if the input list of postings-or-entries is not in sorted date order, two postings for the same entry appearing twice with a different date in between will cause the entry appear twice. This is correct behavior, and it is expected that in practice this should never happen anyway, because the list of postings or entries should always be sorted. This method attempts to detect this and raises an assertion if this is seen. Args: txn_postings: A list of postings or directive instances. Postings affect the balance; other entries do not. Yields: Tuples of (entry, postings, change, balance) as described above. \"\"\" # The running balance. running_balance = inventory.Inventory() # Previous date. prev_date = None # A list of entries at the current date. date_entries = [] first = lambda pair: pair[0] for txn_posting in txn_postings: # Get the posting if we are dealing with one. assert not isinstance(txn_posting, Posting) if isinstance(txn_posting, TxnPosting): posting = txn_posting.posting entry = txn_posting.txn else: posting = None entry = txn_posting if entry.date != prev_date: assert prev_date is None or entry.date > prev_date, ( \"Invalid date order for postings: {} > {}\".format(prev_date, entry.date)) prev_date = entry.date # Flush the dated entries. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: # Compute the change due to this transaction and update the # total balance at the same time. for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear() assert not date_entries if posting is not None: # De-dup multiple postings on the same transaction entry by # grouping their positions together. index = index_key(date_entries, entry, first, operator.is_) if index is None: date_entries.append((entry, [posting])) else: # We are indeed de-duping! postings = date_entries[index][1] postings.append(posting) else: # This is a regular entry; nothing to add/remove. date_entries.append((entry, [])) # Flush the final dated entries if any, same as above. for date_entry, date_postings in date_entries: change = inventory.Inventory() if date_postings: for date_posting in date_postings: change.add_position(date_posting) running_balance.add_position(date_posting) yield date_entry, date_postings, change, running_balance date_entries.clear()","title":"iterate_with_balance()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.postings_by_account","text":"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Parameters: entries \u2013 A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. Source code in beancount/core/realization.py def postings_by_account(entries): \"\"\"Create lists of postings and balances by account. This routine aggregates postings and entries grouping them by account name. The resulting lists contain postings in-lieu of Transaction directives, but the other directives are stored as entries. This yields a list of postings or other entries by account. All references to accounts are taken into account. Args: entries: A list of directives. Returns: A mapping of account name to list of TxnPosting instances or non-Transaction directives, sorted in the same order as the entries. \"\"\" txn_postings_map = collections.defaultdict(list) for entry in entries: if isinstance(entry, Transaction): # Insert an entry for each of the postings. for posting in entry.postings: txn_postings_map[posting.account].append( TxnPosting(entry, posting)) elif isinstance(entry, (Open, Close, Balance, Note, Document)): # Append some other entries in the realized list. txn_postings_map[entry.account].append(entry) elif isinstance(entry, Pad): # Insert the pad entry in both realized accounts. txn_postings_map[entry.account].append(entry) txn_postings_map[entry.source_account].append(entry) elif isinstance(entry, Custom): # Insert custom entry for each account in its values. for custom_value in entry.values: if custom_value.dtype == account.TYPE: txn_postings_map[custom_value.value].append(entry) return txn_postings_map","title":"postings_by_account()"},{"location":"api_reference/beancount.core.html#beancount.core.realization.realize","text":"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\ v \\.__ +---------+ +-----+ -------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Parameters: entries \u2013 A list of directives. min_accounts \u2013 A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance \u2013 A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. Source code in beancount/core/realization.py def realize(entries, min_accounts=None, compute_balance=True): r\"\"\"Group entries by account, into a \"tree\" of realized accounts. RealAccount's are essentially containers for lists of postings and the final balance of each account, and may be non-leaf accounts (used strictly for organizing accounts into a hierarchy). This is then used to issue reports. The lists of postings in each account my be any of the entry types, except for Transaction, whereby Transaction entries are replaced by the specific Posting legs that belong to the account. Here's a simple diagram that summarizes this seemingly complex, but rather simple data structure: +-------------+ postings +------+ | RealAccount |---------->| Open | +-------------+ +------+ | v +------------+ +-------------+ | TxnPosting |---->| Transaction | +------------+ +-------------+ | \\ \\\\\\ v `\\.__ +---------+ +-----+ `-------->| Posting | | Pad | +---------+ +-----+ | v +---------+ | Balance | +---------+ | v +-------+ | Close | +-------+ | . Args: entries: A list of directives. min_accounts: A list of strings, account names to ensure we create, regardless of whether there are postings on those accounts or not. This can be used to ensure the root accounts all exist. compute_balance: A boolean, true if we should compute the final balance on the realization. Returns: The root RealAccount instance. \"\"\" # Create lists of the entries by account. txn_postings_map = postings_by_account(entries) # Create a RealAccount tree and compute the balance for each. real_root = RealAccount('') for account_name, txn_postings in txn_postings_map.items(): real_account = get_or_create(real_root, account_name) real_account.txn_postings = txn_postings if compute_balance: real_account.balance = compute_postings_balance(txn_postings) # Ensure a minimum set of accounts that should exist. This is typically # called with an instance of AccountTypes to make sure that those exist. if min_accounts: for account_name in min_accounts: get_or_create(real_root, account_name) return real_root","title":"realize()"},{"location":"api_reference/beancount.loader.html","text":"beancount.loader \uf0c1 Loader code. This is the main entry point to load up a file. beancount.loader.LoadError ( tuple ) \uf0c1 LoadError(source, message, entry) beancount.loader.LoadError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/loader.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.loader.LoadError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LoadError(source, message, entry) beancount.loader.LoadError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/loader.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.loader.aggregate_options_map(options_map, src_options_map) \uf0c1 Aggregate some of the attributes of options map. Parameters: options_map \u2013 The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map \u2013 A source map whose values we'd like to see aggregated. Source code in beancount/loader.py def aggregate_options_map(options_map, src_options_map): \"\"\"Aggregate some of the attributes of options map. Args: options_map: The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map: A source map whose values we'd like to see aggregated. \"\"\" op_currencies = options_map[\"operating_currency\"] for currency in src_options_map[\"operating_currency\"]: if currency not in op_currencies: op_currencies.append(currency) commodities = options_map[\"commodities\"] for currency in src_options_map[\"commodities\"]: commodities.add(currency) beancount.loader.combine_plugins(*plugin_modules) \uf0c1 Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Parameters: *plugins_modules \u2013 A sequence of module objects. Returns: A list that can be assigned to the new module's plugins attribute. Source code in beancount/loader.py def combine_plugins(*plugin_modules): \"\"\"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Args: *plugins_modules: A sequence of module objects. Returns: A list that can be assigned to the new module's __plugins__ attribute. \"\"\" modules = [] for module in plugin_modules: modules.extend([getattr(module, name) for name in module.__plugins__]) return modules beancount.loader.compute_input_hash(filenames) \uf0c1 Compute a hash of the input data. Parameters: filenames \u2013 A list of input files. Order is not relevant. Source code in beancount/loader.py def compute_input_hash(filenames): \"\"\"Compute a hash of the input data. Args: filenames: A list of input files. Order is not relevant. \"\"\" md5 = hashlib.md5() for filename in sorted(filenames): md5.update(filename.encode('utf8')) if not path.exists(filename): continue stat = os.stat(filename) md5.update(struct.pack('dd', stat.st_mtime_ns, stat.st_size)) return md5.hexdigest() beancount.loader.delete_cache_function(cache_getter, function) \uf0c1 A wrapper that removes the cached filename. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function \u2013 A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. Source code in beancount/loader.py def delete_cache_function(cache_getter, function): \"\"\"A wrapper that removes the cached filename. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function: A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): # Delete the cache. cache_filename = cache_getter(toplevel_filename) if path.exists(cache_filename): os.remove(cache_filename) # Invoke the original function. return function(toplevel_filename, *args, **kw) return wrapped beancount.loader.get_cache_filename(pattern, filename) \uf0c1 Compute the cache filename from a given pattern and the top-level filename. Parameters: pattern ( str ) \u2013 A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename ( str ) \u2013 The top-level filename. Returns: str \u2013 The resolved cache filename. Source code in beancount/loader.py def get_cache_filename(pattern: str, filename: str) -> str: \"\"\"Compute the cache filename from a given pattern and the top-level filename. Args: pattern: A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename: The top-level filename. Returns: The resolved cache filename. \"\"\" abs_filename = path.abspath(filename) if path.isabs(pattern): abs_pattern = pattern else: abs_pattern = path.join(path.dirname(abs_filename), pattern) return abs_pattern.format(filename=path.basename(filename)) beancount.loader.initialize(use_cache, cache_filename=None) \uf0c1 Initialize the loader. Source code in beancount/loader.py def initialize(use_cache: bool, cache_filename: Optional[str] = None): \"\"\"Initialize the loader.\"\"\" # Unless an environment variable disables it, use the pickle load cache # automatically. Note that this works across all Python programs running the # loader which is why it's located here. # pylint: disable=invalid-name global _load_file # Make a function to compute the cache filename. cache_pattern = (cache_filename or os.getenv('BEANCOUNT_LOAD_CACHE_FILENAME') or PICKLE_CACHE_FILENAME) cache_getter = functools.partial(get_cache_filename, cache_pattern) if use_cache: _load_file = pickle_cache_function(cache_getter, PICKLE_CACHE_THRESHOLD, _uncached_load_file) else: if cache_filename is not None: logging.warning(\"Cache disabled; \" \"Explicitly overridden cache filename %s will be ignored.\", cache_filename) _load_file = delete_cache_function(cache_getter, _uncached_load_file) beancount.loader.load_doc(expect_errors=False) \uf0c1 A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. Source code in beancount/loader.py def load_doc(expect_errors=False): \"\"\"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: A callable method, that accepts the three return arguments that load() returns. Returns: A decorated test function. \"\"\" @functools.wraps(fun) def wrapper(self): entries, errors, options_map = load_string(fun.__doc__, dedent=True) if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") # Note: Even if we expected no errors, we call this function with an # empty 'errors' list. This is so that the interface does not change # based on the arguments to the decorator, which would be somewhat # ugly and which would require explanation. return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator beancount.loader.load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None) \uf0c1 Load an encrypted Beancount input file. Parameters: filename \u2013 The name of an encrypted file to be parsed. log_timings \u2013 See load_string(). log_errors \u2013 See load_string(). extra_validations \u2013 See load_string(). dedent \u2013 See load_string(). encoding \u2013 See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Load an encrypted Beancount input file. Args: filename: The name of an encrypted file to be parsed. log_timings: See load_string(). log_errors: See load_string(). extra_validations: See load_string(). dedent: See load_string(). encoding: See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" contents = encryption.read_encrypted_file(filename) return load_string(contents, log_timings=log_timings, log_errors=log_errors, extra_validations=extra_validations, encoding=encoding) beancount.loader.load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None) \uf0c1 Open a Beancount input file, parse it, run transformations and validate. Parameters: filename \u2013 The name of the file to be parsed. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. encoding \u2013 A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None): \"\"\"Open a Beancount input file, parse it, run transformations and validate. Args: filename: The name of the file to be parsed. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. encoding: A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" filename = path.expandvars(path.expanduser(filename)) if not path.isabs(filename): filename = path.normpath(path.join(os.getcwd(), filename)) if encryption.is_encrypted_file(filename): # Note: Caching is not supported for encrypted files. entries, errors, options_map = load_encrypted_file( filename, log_timings, log_errors, extra_validations, False, encoding) else: entries, errors, options_map = _load_file( filename, log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map beancount.loader.load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None) \uf0c1 Open a Beancount input string, parse it, run transformations and validate. Parameters: string \u2013 A Beancount input string. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. dedent \u2013 A boolean, if set, remove the whitespace in front of the lines. encoding \u2013 A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. Source code in beancount/loader.py def load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Open a Beancount input string, parse it, run transformations and validate. Args: string: A Beancount input string. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. dedent: A boolean, if set, remove the whitespace in front of the lines. encoding: A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. \"\"\" if dedent: string = textwrap.dedent(string) entries, errors, options_map = _load([(string, False)], log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map beancount.loader.needs_refresh(options_map) \uf0c1 Predicate that returns true if at least one of the input files may have changed. Parameters: options_map \u2013 An options dict as per the parser. mtime \u2013 A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. Source code in beancount/loader.py def needs_refresh(options_map): \"\"\"Predicate that returns true if at least one of the input files may have changed. Args: options_map: An options dict as per the parser. mtime: A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. \"\"\" if options_map is None: return True input_hash = compute_input_hash(options_map['include']) return 'input_hash' not in options_map or input_hash != options_map['input_hash'] beancount.loader.pickle_cache_function(cache_getter, time_threshold, function) \uf0c1 Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold \u2013 A float, the number of seconds below which we don't bother caching. function \u2013 A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. Source code in beancount/loader.py def pickle_cache_function(cache_getter, time_threshold, function): \"\"\"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold: A float, the number of seconds below which we don't bother caching. function: A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): cache_filename = cache_getter(toplevel_filename) # Read the cache if it exists in order to get the list of files whose # timestamps to check. exists = path.exists(cache_filename) if exists: with open(cache_filename, 'rb') as file: try: result = pickle.load(file) except Exception as exc: # Note: Not a big fan of doing this, but here we handle all # possible exceptions because unpickling of an old or # corrupted pickle file manifests as a variety of different # exception types. # The cache file is corrupted; ignore it and recompute. logging.error(\"Cache file is corrupted: %s; recomputing.\", exc) result = None else: # Check that the latest timestamp has not been written after the # cache file. entries, errors, options_map = result if not needs_refresh(options_map): # All timestamps are legit; cache hit. return result # We failed; recompute the value. if exists: try: os.remove(cache_filename) except OSError as exc: # Warn for errors on read-only filesystems. logging.warning(\"Could not remove picklecache file %s: %s\", cache_filename, exc) time_before = time.time() result = function(toplevel_filename, *args, **kw) time_after = time.time() # Overwrite the cache file if the time it takes to compute it # justifies it. if time_after - time_before > time_threshold: try: with open(cache_filename, 'wb') as file: pickle.dump(result, file) except Exception as exc: logging.warning(\"Could not write to picklecache file %s: %s\", cache_filename, exc) return result return wrapped beancount.loader.run_transformations(entries, parse_errors, options_map, log_timings) \uf0c1 Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Parameters: entries \u2013 A list of directives as read from the parser. parse_errors \u2013 A list of errors so far. options_map \u2013 An options dict as read from the parser. log_timings \u2013 A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. Source code in beancount/loader.py def run_transformations(entries, parse_errors, options_map, log_timings): \"\"\"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Args: entries: A list of directives as read from the parser. parse_errors: A list of errors so far. options_map: An options dict as read from the parser. log_timings: A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. \"\"\" # A list of errors to extend (make a copy to avoid modifying the input). errors = list(parse_errors) # Process the plugins. if options_map['plugin_processing_mode'] == 'raw': plugins_iter = options_map[\"plugin\"] elif options_map['plugin_processing_mode'] == 'default': plugins_iter = itertools.chain(DEFAULT_PLUGINS_PRE, options_map[\"plugin\"], DEFAULT_PLUGINS_POST) else: assert \"Invalid value for plugin_processing_mode: {}\".format( options_map['plugin_processing_mode']) for plugin_name, plugin_config in plugins_iter: # Issue a warning on a renamed module. renamed_name = RENAMED_MODULES.get(plugin_name, None) if renamed_name: warnings.warn(\"Deprecation notice: Module '{}' has been renamed to '{}'; \" \"please adjust your plugin directive.\".format( plugin_name, renamed_name)) plugin_name = renamed_name # Try to import the module. try: module = importlib.import_module(plugin_name) if not hasattr(module, '__plugins__'): continue with misc_utils.log_time(plugin_name, log_timings, indent=2): # Run each transformer function in the plugin. for function_name in module.__plugins__: if isinstance(function_name, str): # Support plugin functions provided by name. callback = getattr(module, function_name) else: # Support function types directly, not just names. callback = function_name if plugin_config is not None: entries, plugin_errors = callback(entries, options_map, plugin_config) else: entries, plugin_errors = callback(entries, options_map) errors.extend(plugin_errors) # Ensure that the entries are sorted. Don't trust the plugins # themselves. entries.sort(key=data.entry_sortkey) except (ImportError, TypeError) as exc: # Upon failure, just issue an error. errors.append(LoadError(data.new_metadata(\"\", 0), 'Error importing \"{}\": {}'.format( plugin_name, str(exc)), None)) return entries, errors","title":"beancount.loader"},{"location":"api_reference/beancount.loader.html#beancountloader","text":"Loader code. This is the main entry point to load up a file.","title":"beancount.loader"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError","text":"LoadError(source, message, entry)","title":"LoadError"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/loader.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__new__","text":"Create new instance of LoadError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.LoadError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/loader.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.loader.html#beancount.loader.aggregate_options_map","text":"Aggregate some of the attributes of options map. Parameters: options_map \u2013 The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map \u2013 A source map whose values we'd like to see aggregated. Source code in beancount/loader.py def aggregate_options_map(options_map, src_options_map): \"\"\"Aggregate some of the attributes of options map. Args: options_map: The target map in which we want to aggregate attributes. Note: This value is mutated in-place. src_options_map: A source map whose values we'd like to see aggregated. \"\"\" op_currencies = options_map[\"operating_currency\"] for currency in src_options_map[\"operating_currency\"]: if currency not in op_currencies: op_currencies.append(currency) commodities = options_map[\"commodities\"] for currency in src_options_map[\"commodities\"]: commodities.add(currency)","title":"aggregate_options_map()"},{"location":"api_reference/beancount.loader.html#beancount.loader.combine_plugins","text":"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Parameters: *plugins_modules \u2013 A sequence of module objects. Returns: A list that can be assigned to the new module's plugins attribute. Source code in beancount/loader.py def combine_plugins(*plugin_modules): \"\"\"Combine the plugins from the given plugin modules. This is used to create plugins of plugins. Args: *plugins_modules: A sequence of module objects. Returns: A list that can be assigned to the new module's __plugins__ attribute. \"\"\" modules = [] for module in plugin_modules: modules.extend([getattr(module, name) for name in module.__plugins__]) return modules","title":"combine_plugins()"},{"location":"api_reference/beancount.loader.html#beancount.loader.compute_input_hash","text":"Compute a hash of the input data. Parameters: filenames \u2013 A list of input files. Order is not relevant. Source code in beancount/loader.py def compute_input_hash(filenames): \"\"\"Compute a hash of the input data. Args: filenames: A list of input files. Order is not relevant. \"\"\" md5 = hashlib.md5() for filename in sorted(filenames): md5.update(filename.encode('utf8')) if not path.exists(filename): continue stat = os.stat(filename) md5.update(struct.pack('dd', stat.st_mtime_ns, stat.st_size)) return md5.hexdigest()","title":"compute_input_hash()"},{"location":"api_reference/beancount.loader.html#beancount.loader.delete_cache_function","text":"A wrapper that removes the cached filename. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function \u2013 A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. Source code in beancount/loader.py def delete_cache_function(cache_getter, function): \"\"\"A wrapper that removes the cached filename. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. function: A function object to decorate for caching. Returns: A decorated function which will delete the cached filename, if it exists. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): # Delete the cache. cache_filename = cache_getter(toplevel_filename) if path.exists(cache_filename): os.remove(cache_filename) # Invoke the original function. return function(toplevel_filename, *args, **kw) return wrapped","title":"delete_cache_function()"},{"location":"api_reference/beancount.loader.html#beancount.loader.get_cache_filename","text":"Compute the cache filename from a given pattern and the top-level filename. Parameters: pattern ( str ) \u2013 A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename ( str ) \u2013 The top-level filename. Returns: str \u2013 The resolved cache filename. Source code in beancount/loader.py def get_cache_filename(pattern: str, filename: str) -> str: \"\"\"Compute the cache filename from a given pattern and the top-level filename. Args: pattern: A cache filename or pattern. If the pattern contains '{filename}' this will get replaced by the top-level filename. This may be absolute or relative. filename: The top-level filename. Returns: The resolved cache filename. \"\"\" abs_filename = path.abspath(filename) if path.isabs(pattern): abs_pattern = pattern else: abs_pattern = path.join(path.dirname(abs_filename), pattern) return abs_pattern.format(filename=path.basename(filename))","title":"get_cache_filename()"},{"location":"api_reference/beancount.loader.html#beancount.loader.initialize","text":"Initialize the loader. Source code in beancount/loader.py def initialize(use_cache: bool, cache_filename: Optional[str] = None): \"\"\"Initialize the loader.\"\"\" # Unless an environment variable disables it, use the pickle load cache # automatically. Note that this works across all Python programs running the # loader which is why it's located here. # pylint: disable=invalid-name global _load_file # Make a function to compute the cache filename. cache_pattern = (cache_filename or os.getenv('BEANCOUNT_LOAD_CACHE_FILENAME') or PICKLE_CACHE_FILENAME) cache_getter = functools.partial(get_cache_filename, cache_pattern) if use_cache: _load_file = pickle_cache_function(cache_getter, PICKLE_CACHE_THRESHOLD, _uncached_load_file) else: if cache_filename is not None: logging.warning(\"Cache disabled; \" \"Explicitly overridden cache filename %s will be ignored.\", cache_filename) _load_file = delete_cache_function(cache_getter, _uncached_load_file)","title":"initialize()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_doc","text":"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. Source code in beancount/loader.py def load_doc(expect_errors=False): \"\"\"A factory of decorators that loads the docstring and calls the function with entries. This is an incredibly convenient tool to write lots of tests. Write a unittest using the standard TestCase class and put the input entries in the function's docstring. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. Returns: A wrapped method that accepts a single 'self' argument. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: A callable method, that accepts the three return arguments that load() returns. Returns: A decorated test function. \"\"\" @functools.wraps(fun) def wrapper(self): entries, errors, options_map = load_string(fun.__doc__, dedent=True) if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") # Note: Even if we expected no errors, we call this function with an # empty 'errors' list. This is so that the interface does not change # based on the arguments to the decorator, which would be somewhat # ugly and which would require explanation. return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator","title":"load_doc()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_encrypted_file","text":"Load an encrypted Beancount input file. Parameters: filename \u2013 The name of an encrypted file to be parsed. log_timings \u2013 See load_string(). log_errors \u2013 See load_string(). extra_validations \u2013 See load_string(). dedent \u2013 See load_string(). encoding \u2013 See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_encrypted_file(filename, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Load an encrypted Beancount input file. Args: filename: The name of an encrypted file to be parsed. log_timings: See load_string(). log_errors: See load_string(). extra_validations: See load_string(). dedent: See load_string(). encoding: See load_string(). Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" contents = encryption.read_encrypted_file(filename) return load_string(contents, log_timings=log_timings, log_errors=log_errors, extra_validations=extra_validations, encoding=encoding)","title":"load_encrypted_file()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_file","text":"Open a Beancount input file, parse it, run transformations and validate. Parameters: filename \u2013 The name of the file to be parsed. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. encoding \u2013 A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. Source code in beancount/loader.py def load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None): \"\"\"Open a Beancount input file, parse it, run transformations and validate. Args: filename: The name of the file to be parsed. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. encoding: A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the file, \"errors\" a list of error objects generated while parsing and validating the file, and \"options_map\", a dict of the options parsed from the file. \"\"\" filename = path.expandvars(path.expanduser(filename)) if not path.isabs(filename): filename = path.normpath(path.join(os.getcwd(), filename)) if encryption.is_encrypted_file(filename): # Note: Caching is not supported for encrypted files. entries, errors, options_map = load_encrypted_file( filename, log_timings, log_errors, extra_validations, False, encoding) else: entries, errors, options_map = _load_file( filename, log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map","title":"load_file()"},{"location":"api_reference/beancount.loader.html#beancount.loader.load_string","text":"Open a Beancount input string, parse it, run transformations and validate. Parameters: string \u2013 A Beancount input string. log_timings \u2013 A file object or function to write timings to, or None, if it should remain quiet. log_errors \u2013 A file object or function to write errors to, or None, if it should remain quiet. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. dedent \u2013 A boolean, if set, remove the whitespace in front of the lines. encoding \u2013 A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. Source code in beancount/loader.py def load_string(string, log_timings=None, log_errors=None, extra_validations=None, dedent=False, encoding=None): \"\"\"Open a Beancount input string, parse it, run transformations and validate. Args: string: A Beancount input string. log_timings: A file object or function to write timings to, or None, if it should remain quiet. log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. dedent: A boolean, if set, remove the whitespace in front of the lines. encoding: A string or None, the encoding to decode the input string with. Returns: A triple of (entries, errors, option_map) where \"entries\" is a date-sorted list of entries from the string, \"errors\" a list of error objects generated while parsing and validating the string, and \"options_map\", a dict of the options parsed from the string. \"\"\" if dedent: string = textwrap.dedent(string) entries, errors, options_map = _load([(string, False)], log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map","title":"load_string()"},{"location":"api_reference/beancount.loader.html#beancount.loader.needs_refresh","text":"Predicate that returns true if at least one of the input files may have changed. Parameters: options_map \u2013 An options dict as per the parser. mtime \u2013 A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. Source code in beancount/loader.py def needs_refresh(options_map): \"\"\"Predicate that returns true if at least one of the input files may have changed. Args: options_map: An options dict as per the parser. mtime: A modified time, to check if it covers the include files in the options_map. Returns: A boolean, true if the input is obsoleted by changes in the input files. \"\"\" if options_map is None: return True input_hash = compute_input_hash(options_map['include']) return 'input_hash' not in options_map or input_hash != options_map['input_hash']","title":"needs_refresh()"},{"location":"api_reference/beancount.loader.html#beancount.loader.pickle_cache_function","text":"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Parameters: cache_getter \u2013 A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold \u2013 A float, the number of seconds below which we don't bother caching. function \u2013 A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. Source code in beancount/loader.py def pickle_cache_function(cache_getter, time_threshold, function): \"\"\"Decorate a loader function to make it loads its result from a pickle cache. This considers the first argument as a top-level filename and assumes the function to be cached returns an (entries, errors, options_map) triple. We use the 'include' option value in order to check whether any of the included files has changed. It's essentially a special case for an on-disk memoizer. If any of the included files are more recent than the cache, the function is recomputed and the cache refreshed. Args: cache_getter: A function of one argument, the top-level filename, which will return the name of the corresponding cache file. time_threshold: A float, the number of seconds below which we don't bother caching. function: A function object to decorate for caching. Returns: A decorated function which will pull its result from a cache file if it is available. \"\"\" @functools.wraps(function) def wrapped(toplevel_filename, *args, **kw): cache_filename = cache_getter(toplevel_filename) # Read the cache if it exists in order to get the list of files whose # timestamps to check. exists = path.exists(cache_filename) if exists: with open(cache_filename, 'rb') as file: try: result = pickle.load(file) except Exception as exc: # Note: Not a big fan of doing this, but here we handle all # possible exceptions because unpickling of an old or # corrupted pickle file manifests as a variety of different # exception types. # The cache file is corrupted; ignore it and recompute. logging.error(\"Cache file is corrupted: %s; recomputing.\", exc) result = None else: # Check that the latest timestamp has not been written after the # cache file. entries, errors, options_map = result if not needs_refresh(options_map): # All timestamps are legit; cache hit. return result # We failed; recompute the value. if exists: try: os.remove(cache_filename) except OSError as exc: # Warn for errors on read-only filesystems. logging.warning(\"Could not remove picklecache file %s: %s\", cache_filename, exc) time_before = time.time() result = function(toplevel_filename, *args, **kw) time_after = time.time() # Overwrite the cache file if the time it takes to compute it # justifies it. if time_after - time_before > time_threshold: try: with open(cache_filename, 'wb') as file: pickle.dump(result, file) except Exception as exc: logging.warning(\"Could not write to picklecache file %s: %s\", cache_filename, exc) return result return wrapped","title":"pickle_cache_function()"},{"location":"api_reference/beancount.loader.html#beancount.loader.run_transformations","text":"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Parameters: entries \u2013 A list of directives as read from the parser. parse_errors \u2013 A list of errors so far. options_map \u2013 An options dict as read from the parser. log_timings \u2013 A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. Source code in beancount/loader.py def run_transformations(entries, parse_errors, options_map, log_timings): \"\"\"Run the various transformations on the entries. This is where entries are being synthesized, checked, plugins are run, etc. Args: entries: A list of directives as read from the parser. parse_errors: A list of errors so far. options_map: An options dict as read from the parser. log_timings: A function to write timing log entries to, or None, if it should be quiet. Returns: A list of modified entries, and a list of errors, also possibly modified. \"\"\" # A list of errors to extend (make a copy to avoid modifying the input). errors = list(parse_errors) # Process the plugins. if options_map['plugin_processing_mode'] == 'raw': plugins_iter = options_map[\"plugin\"] elif options_map['plugin_processing_mode'] == 'default': plugins_iter = itertools.chain(DEFAULT_PLUGINS_PRE, options_map[\"plugin\"], DEFAULT_PLUGINS_POST) else: assert \"Invalid value for plugin_processing_mode: {}\".format( options_map['plugin_processing_mode']) for plugin_name, plugin_config in plugins_iter: # Issue a warning on a renamed module. renamed_name = RENAMED_MODULES.get(plugin_name, None) if renamed_name: warnings.warn(\"Deprecation notice: Module '{}' has been renamed to '{}'; \" \"please adjust your plugin directive.\".format( plugin_name, renamed_name)) plugin_name = renamed_name # Try to import the module. try: module = importlib.import_module(plugin_name) if not hasattr(module, '__plugins__'): continue with misc_utils.log_time(plugin_name, log_timings, indent=2): # Run each transformer function in the plugin. for function_name in module.__plugins__: if isinstance(function_name, str): # Support plugin functions provided by name. callback = getattr(module, function_name) else: # Support function types directly, not just names. callback = function_name if plugin_config is not None: entries, plugin_errors = callback(entries, options_map, plugin_config) else: entries, plugin_errors = callback(entries, options_map) errors.extend(plugin_errors) # Ensure that the entries are sorted. Don't trust the plugins # themselves. entries.sort(key=data.entry_sortkey) except (ImportError, TypeError) as exc: # Upon failure, just issue an error. errors.append(LoadError(data.new_metadata(\"\", 0), 'Error importing \"{}\": {}'.format( plugin_name, str(exc)), None)) return entries, errors","title":"run_transformations()"},{"location":"api_reference/beancount.ops.html","text":"beancount.ops \uf0c1 Operations on the entries defined in the core modules. This package contains various functions which operate on lists of entries. beancount.ops.balance \uf0c1 Automatic padding of gaps between entries. beancount.ops.balance.BalanceError ( tuple ) \uf0c1 BalanceError(source, message, entry) beancount.ops.balance.BalanceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/balance.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.balance.BalanceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BalanceError(source, message, entry) beancount.ops.balance.BalanceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/balance.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.balance.check(entries, options_map) \uf0c1 Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Parameters: entries \u2013 A list of directives. options_map \u2013 A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. Source code in beancount/ops/balance.py def check(entries, options_map): \"\"\"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Args: entries: A list of directives. options_map: A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. \"\"\" new_entries = [] check_errors = [] # This is similar to realization, but performed in a different order, and # where we only accumulate inventories for accounts that have balance # assertions in them (this saves on time). Here we process the entries one # by one along with the balance checks. We use a temporary realization in # order to hold the incremental tree of balances, so that we can easily get # the amounts of an account's subaccounts for making checks on parent # accounts. real_root = realization.RealAccount('') # Figure out the set of accounts for which we need to compute a running # inventory balance. asserted_accounts = {entry.account for entry in entries if isinstance(entry, Balance)} # Add all children accounts of an asserted account to be calculated as well, # and pre-create these accounts, and only those (we're just being tight to # make sure). asserted_match_list = [account.parent_matcher(account_) for account_ in asserted_accounts] for account_ in getters.get_accounts(entries): if (account_ in asserted_accounts or any(match(account_) for match in asserted_match_list)): realization.get_or_create(real_root, account_) # Get the Open directives for each account. open_close_map = getters.get_account_open_close(entries) for entry in entries: if isinstance(entry, Transaction): # For each of the postings' accounts, update the balance inventory. for posting in entry.postings: real_account = realization.get(real_root, posting.account) # The account will have been created only if we're meant to track it. if real_account is not None: # Note: Always allow negative lots for the purpose of balancing. # This error should show up somewhere else than here. real_account.balance.add_position(posting) elif isinstance(entry, Balance): # Check that the currency of the balance check is one of the allowed # currencies for that account. expected_amount = entry.amount try: open, _ = open_close_map[entry.account] except KeyError: check_errors.append( BalanceError(entry.meta, \"Account '{}' does not exist: \".format(entry.account), entry)) continue if (expected_amount is not None and open and open.currencies and expected_amount.currency not in open.currencies): check_errors.append( BalanceError(entry.meta, \"Invalid currency '{}' for Balance directive: \".format( expected_amount.currency), entry)) # Sum up the current balances for this account and its # sub-accounts. We want to support checks for parent accounts # for the total sum of their subaccounts. # # FIXME: Improve the performance further by computing the balance # for the desired currency only. This won't allow us to cache in # this way but may be faster, if we're not asserting all the # currencies. Furthermore, we could probably avoid recomputing the # balance if a subtree of positions hasn't been invalidated by a new # position added to the realization. Do this. real_account = realization.get(real_root, entry.account) assert real_account is not None, \"Missing {}\".format(entry.account) subtree_balance = realization.compute_balance(real_account, leaf_only=False) # Get only the amount in the desired currency. balance_amount = subtree_balance.get_currency_units(expected_amount.currency) # Check if the amount is within bounds of the expected amount. diff_amount = amount.sub(balance_amount, expected_amount) # Use the specified tolerance or automatically infer it. tolerance = get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: check_errors.append( BalanceError(entry.meta, (\"Balance failed for '{}': \" \"expected {} != accumulated {} ({} {})\").format( entry.account, expected_amount, balance_amount, abs(diff_amount.number), ('too much' if diff_amount.number > 0 else 'too little')), entry)) # Substitute the entry by a failing entry, with the diff_amount # field set on it. I'm not entirely sure that this is the best # of ideas, maybe leaving the original check intact and insert a # new error entry might be more functional or easier to # understand. entry = entry._replace( meta=entry.meta.copy(), diff_amount=diff_amount) new_entries.append(entry) return new_entries, check_errors beancount.ops.balance.get_balance_tolerance(balance_entry, options_map) \uf0c1 Get the tolerance amount for a single entry. Parameters: balance_entry \u2013 An instance of data.Balance options_map \u2013 An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. Source code in beancount/ops/balance.py def get_balance_tolerance(balance_entry, options_map): \"\"\"Get the tolerance amount for a single entry. Args: balance_entry: An instance of data.Balance options_map: An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. \"\"\" if balance_entry.tolerance is not None: # Use the balance-specific tolerance override if it is provided. tolerance = balance_entry.tolerance else: expo = balance_entry.amount.number.as_tuple().exponent if expo < 0: # Be generous and always allow twice the multiplier on Balance and # Pad because the user creates these and the rounding of those # balances may often be further off than those used within a single # transaction. tolerance = options_map[\"inferred_tolerance_multiplier\"] * 2 tolerance = ONE.scaleb(expo) * tolerance else: tolerance = ZERO return tolerance beancount.ops.basicops \uf0c1 Basic filtering and aggregation operations on lists of entries. This module contains some common basic operations on entries that are complex enough not to belong in core/data.py. beancount.ops.basicops.filter_link(link, entries) \uf0c1 Yield all the entries which have the given link. Parameters: link \u2013 A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. Source code in beancount/ops/basicops.py def filter_link(link, entries): \"\"\"Yield all the entries which have the given link. Args: link: A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.links and link in entry.links): yield entry beancount.ops.basicops.filter_tag(tag, entries) \uf0c1 Yield all the entries which have the given tag. Parameters: tag \u2013 A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. Source code in beancount/ops/basicops.py def filter_tag(tag, entries): \"\"\"Yield all the entries which have the given tag. Args: tag: A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.tags and tag in entry.tags): yield entry beancount.ops.basicops.get_common_accounts(entries) \uf0c1 Compute the intersection of the accounts on the given entries. Parameters: entries \u2013 A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. Source code in beancount/ops/basicops.py def get_common_accounts(entries): \"\"\"Compute the intersection of the accounts on the given entries. Args: entries: A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. \"\"\" assert all(isinstance(entry, data.Transaction) for entry in entries) # If there is a single entry, the common accounts to it is all its accounts. # Note that this also works with no entries (yields an empty set). if len(entries) < 2: if entries: intersection = {posting.account for posting in entries[0].postings} else: intersection = set() else: entries_iter = iter(entries) intersection = set(posting.account for posting in next(entries_iter).postings) for entry in entries_iter: accounts = set(posting.account for posting in entry.postings) intersection &= accounts if not intersection: break return intersection beancount.ops.basicops.group_entries_by_link(entries) \uf0c1 Group the list of entries by link. Parameters: entries \u2013 A list of directives/transactions to process. Returns: A dict of link-name to list of entries. Source code in beancount/ops/basicops.py def group_entries_by_link(entries): \"\"\"Group the list of entries by link. Args: entries: A list of directives/transactions to process. Returns: A dict of link-name to list of entries. \"\"\" link_groups = defaultdict(list) for entry in entries: if not (isinstance(entry, data.Transaction) and entry.links): continue for link in entry.links: link_groups[link].append(entry) return link_groups beancount.ops.compress \uf0c1 Compress multiple entries into a single one. This can be used during import to compress the effective output, for accounts with a large number of similar entries. For example, I had a trading account which would pay out interest every single day. I have no desire to import the full detail of these daily interests, and compressing these interest-only entries to monthly ones made sense. This is the code that was used to carry this out. beancount.ops.compress.compress(entries, predicate) \uf0c1 Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Parameters: entries \u2013 A list of directives. predicate \u2013 A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. Source code in beancount/ops/compress.py def compress(entries, predicate): \"\"\"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Args: entries: A list of directives. predicate: A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. \"\"\" new_entries = [] pending = [] for entry in entries: if isinstance(entry, data.Transaction) and predicate(entry): # Save for compressing later. pending.append(entry) else: # Compress and output all the pending entries. if pending: new_entries.append(merge(pending, pending[-1])) pending.clear() # Output the differing entry. new_entries.append(entry) if pending: new_entries.append(merge(pending, pending[-1])) return new_entries beancount.ops.compress.merge(entries, prototype_txn) \uf0c1 Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Parameters: entries \u2013 A list of directives. prototype_txn \u2013 A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. Source code in beancount/ops/compress.py def merge(entries, prototype_txn): \"\"\"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Args: entries: A list of directives. prototype_txn: A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. \"\"\" # Aggregate the postings together. This is a mapping of numberless postings # to their number of units. postings_map = collections.defaultdict(Decimal) for entry in data.filter_txns(entries): for posting in entry.postings: # We strip the number off the posting to act as an aggregation key. key = data.Posting(posting.account, Amount(None, posting.units.currency), posting.cost, posting.price, posting.flag, None) postings_map[key] += posting.units.number # Create a new transaction with the aggregated postings. new_entry = data.Transaction(prototype_txn.meta, prototype_txn.date, prototype_txn.flag, prototype_txn.payee, prototype_txn.narration, data.EMPTY_SET, data.EMPTY_SET, []) # Sort for at least some stability of output. sorted_items = sorted(postings_map.items(), key=lambda item: (item[0].account, item[0].units.currency, item[1])) # Issue the merged postings. for posting, number in sorted_items: units = Amount(number, posting.units.currency) new_entry.postings.append( data.Posting(posting.account, units, posting.cost, posting.price, posting.flag, posting.meta)) return new_entry beancount.ops.documents \uf0c1 Everything that relates to creating the Document directives. beancount.ops.documents.DocumentError ( tuple ) \uf0c1 DocumentError(source, message, entry) beancount.ops.documents.DocumentError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/documents.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.documents.DocumentError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of DocumentError(source, message, entry) beancount.ops.documents.DocumentError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/documents.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.documents.find_documents(directory, input_filename, accounts_only=None, strict=False) \uf0c1 Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Parameters: directory \u2013 A string, the name of the root of the directory hierarchy to be searched. input_filename \u2013 The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only \u2013 A set of valid accounts strings to search for. strict \u2013 A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. Source code in beancount/ops/documents.py def find_documents(directory, input_filename, accounts_only=None, strict=False): \"\"\"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Args: directory: A string, the name of the root of the directory hierarchy to be searched. input_filename: The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only: A set of valid accounts strings to search for. strict: A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. \"\"\" errors = [] # Compute the documents directory name relative to the beancount input # file itself. if not path.isabs(directory): input_directory = path.dirname(input_filename) directory = path.abspath(path.normpath(path.join(input_directory, directory))) # If the directory does not exist, just generate an error and return. if not path.exists(directory): meta = data.new_metadata(input_filename, 0) error = DocumentError( meta, \"Document root '{}' does not exist\".format(directory), None) return ([], [error]) # Walk the hierarchy of files. entries = [] for root, account_name, dirs, files in account.walk(directory): # Look for files that have a dated filename. for filename in files: match = re.match(r'(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d).(.*)', filename) if not match: continue # If a restricting set of accounts was specified, skip document # directives found in accounts with no corresponding account name. if accounts_only is not None and not account_name in accounts_only: if strict: if any(account_name.startswith(account) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in child account {}\".format( filename, account_name), None)) elif any(account.startswith(account_name) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in parent account {}\".format( filename, account_name), None)) continue # Create a new directive. meta = data.new_metadata(input_filename, 0) try: date = datetime.date(*map(int, match.group(1, 2, 3))) except ValueError as exc: errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Invalid date on document file '{}': {}\".format( filename, exc), None)) else: entry = data.Document(meta, date, account_name, path.join(root, filename), data.EMPTY_SET, data.EMPTY_SET) entries.append(entry) return (entries, errors) beancount.ops.documents.process_documents(entries, options_map) \uf0c1 Check files for document directives and create documents directives automatically. Parameters: entries \u2013 A list of all directives parsed from the file. options_map \u2013 An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. Source code in beancount/ops/documents.py def process_documents(entries, options_map): \"\"\"Check files for document directives and create documents directives automatically. Args: entries: A list of all directives parsed from the file. options_map: An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. \"\"\" filename = options_map[\"filename\"] # Detect filenames that should convert into entries. autodoc_entries = [] autodoc_errors = [] document_dirs = options_map['documents'] if document_dirs: # Restrict to the list of valid accounts only. accounts = getters.get_accounts(entries) # Accumulate all the entries. for directory in map(path.normpath, document_dirs): new_entries, new_errors = find_documents(directory, filename, accounts) autodoc_entries.extend(new_entries) autodoc_errors.extend(new_errors) # Merge the two lists of entries and errors. Keep the entries sorted. entries.extend(autodoc_entries) entries.sort(key=data.entry_sortkey) return (entries, autodoc_errors) beancount.ops.documents.verify_document_files_exist(entries, unused_options_map) \uf0c1 Verify that the document entries point to existing files. Parameters: entries \u2013 a list of directives whose documents need to be validated. unused_options_map \u2013 A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. Source code in beancount/ops/documents.py def verify_document_files_exist(entries, unused_options_map): \"\"\"Verify that the document entries point to existing files. Args: entries: a list of directives whose documents need to be validated. unused_options_map: A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. \"\"\" errors = [] for entry in entries: if not isinstance(entry, data.Document): continue if not path.exists(entry.filename): errors.append( DocumentError(entry.meta, 'File does not exist: \"{}\"'.format(entry.filename), entry)) return entries, errors beancount.ops.holdings \uf0c1 Compute final holdings for a list of entries. beancount.ops.holdings.Holding ( tuple ) \uf0c1 Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) beancount.ops.holdings.Holding.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/holdings.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.holdings.Holding.__new__(_cls, account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) special staticmethod \uf0c1 Create new instance of Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date) beancount.ops.holdings.Holding.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/holdings.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.holdings.aggregate_holdings_by(holdings, keyfun) \uf0c1 Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Parameters: keyfun \u2013 A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. Source code in beancount/ops/holdings.py def aggregate_holdings_by(holdings, keyfun): \"\"\"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Args: keyfun: A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. \"\"\" # Aggregate the groups of holdings. grouped = collections.defaultdict(list) for holding in holdings: key = (keyfun(holding), holding.cost_currency) grouped[key].append(holding) grouped_holdings = (aggregate_holdings_list(key_holdings) for key_holdings in grouped.values()) # We could potentially filter out holdings with zero units here. These types # of holdings might occur on a group with leaked (i.e., non-zero) cost basis # and zero units. However, sometimes are valid merging of multiple # currencies may occur, and the number value will be legitimately set to # ZERO (for various reasons downstream), so we prefer not to ignore the # holding. Callers must be prepared to deal with a holding of ZERO units and # a non-zero cost basis. {0ed05c502e63, b/16} ## nonzero_holdings = (holding ## for holding in grouped_holdings ## if holding.number != ZERO) # Return the holdings in order. return sorted(grouped_holdings, key=lambda holding: (holding.account, holding.currency)) beancount.ops.holdings.aggregate_holdings_list(holdings) \uf0c1 Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Parameters: holdings \u2013 A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Exceptions: ValueError \u2013 If multiple cost currencies encountered. Source code in beancount/ops/holdings.py def aggregate_holdings_list(holdings): \"\"\"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Args: holdings: A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Raises: ValueError: If multiple cost currencies encountered. \"\"\" if not holdings: return None # Note: Holding is a bit overspecified with book and market values. We # recompute them from cost and price numbers here anyhow. units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() price_dates = set() book_value_seen = False market_value_seen = False for holding in holdings: units += holding.number accounts.add(holding.account) price_dates.add(holding.price_date) currencies.add(holding.currency) cost_currencies.add(holding.cost_currency) if holding.book_value is not None: total_book_value += holding.book_value book_value_seen = True elif holding.cost_number is not None: total_book_value += holding.number * holding.cost_number book_value_seen = True if holding.market_value is not None: total_market_value += holding.market_value market_value_seen = True elif holding.price_number is not None: total_market_value += holding.number * holding.price_number market_value_seen = True if book_value_seen: average_cost = total_book_value / units if units else None else: total_book_value = None average_cost = None if market_value_seen: average_price = total_market_value / units if units else None else: total_market_value = None average_price = None if len(cost_currencies) != 1: raise ValueError(\"Cost currencies are not homogeneous for aggregation: {}\".format( ','.join(map(str, cost_currencies)))) units = units if len(currencies) == 1 else ZERO currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) price_date = price_dates.pop() if len(price_dates) == 1 else None return Holding(account_, units, currency, average_cost, cost_currency, total_book_value, total_market_value, average_price, price_date) beancount.ops.holdings.convert_to_currency(price_map, target_currency, holdings_list) \uf0c1 Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Parameters: price_map \u2013 A price-map, as built by prices.build_price_map(). target_currency \u2013 The target common currency to convert amounts to. holdings_list \u2013 A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. Source code in beancount/ops/holdings.py def convert_to_currency(price_map, target_currency, holdings_list): \"\"\"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Args: price_map: A price-map, as built by prices.build_price_map(). target_currency: The target common currency to convert amounts to. holdings_list: A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. \"\"\" # A list of the fields we should convert. convert_fields = ('cost_number', 'book_value', 'market_value', 'price_number') new_holdings = [] for holding in holdings_list: if holding.cost_currency == target_currency: # The holding is already priced in the target currency; do nothing. new_holding = holding else: if holding.cost_currency is None: # There is no cost currency; make the holding priced in its own # units. The price-map should yield a rate of 1.0 and everything # else works out. if holding.currency is None: raise ValueError(\"Invalid currency '{}'\".format(holding.currency)) holding = holding._replace(cost_currency=holding.currency) # Fill in with book and market value as well. if holding.book_value is None: holding = holding._replace(book_value=holding.number) if holding.market_value is None: holding = holding._replace(market_value=holding.number) assert holding.cost_currency, \"Missing cost currency: {}\".format(holding) base_quote = (holding.cost_currency, target_currency) # Get the conversion rate and replace the required numerical # fields.. _, rate = prices.get_latest_price(price_map, base_quote) if rate is not None: new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number, r=rate: number if number is None else number * r, holding) # Ensure we set the new cost currency after conversion. new_holding = new_holding._replace(cost_currency=target_currency) else: # Could not get the rate... clear every field and set the cost # currency to None. This enough marks the holding conversion as # a failure. new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number: None, holding) new_holding = new_holding._replace(cost_currency=None) new_holdings.append(new_holding) return new_holdings beancount.ops.holdings.get_commodities_at_date(entries, options_map, date=None) \uf0c1 Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency \u2013 The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). Source code in beancount/ops/holdings.py def get_commodities_at_date(entries, options_map, date=None): \"\"\"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: * The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. * The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Args: entries: A list of directives. date: A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency: The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). \"\"\" # Remove all the entries after the given date, if requested. if date is not None: entries = summarize.truncate(entries, date) # Get the list of holdings at the particular date. holdings_list = get_final_holdings(entries) # Obtain the unique list of currencies we need to fetch. commodities_list = {(holding.currency, holding.cost_currency) for holding in holdings_list} # Add in the associated ticker symbols. commodities_map = getters.get_commodity_map(entries) commodities_symbols_list = [] for currency, cost_currency in sorted(commodities_list): try: commodity_entry = commodities_map[currency] ticker = commodity_entry.meta.get('ticker', None) quote_currency = commodity_entry.meta.get('quote', None) except KeyError: ticker = None quote_currency = None commodities_symbols_list.append( (currency, cost_currency, quote_currency, ticker)) return commodities_symbols_list beancount.ops.holdings.get_final_holdings(entries, included_account_types=None, price_map=None, date=None) \uf0c1 Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Parameters: entries \u2013 A list of directives. included_account_types \u2013 A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields \u2013 Source code in beancount/ops/holdings.py def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): \"\"\"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Args: entries: A list of directives. included_account_types: A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields: \"\"\" # Remove the entries inserted by unrealized gains/losses. Those entries do # affect asset accounts, and we don't want them to appear in holdings. # # Note: Perhaps it would make sense to generalize this concept of \"inserted # unrealized gains.\" simple_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or entry.flag != flags.FLAG_UNREALIZED)] # Realize the accounts into a tree (because we want the positions by-account). root_account = realization.realize(simple_entries) # For each account, look at the list of positions and build a list. holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): if included_account_types: # Skip accounts of invalid types, we only want to reflect the requested # account types, typically assets and liabilities. account_type = account_types.get_account_type(real_account.account) if account_type not in included_account_types: continue for pos in real_account.balance.get_positions(): if pos.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (pos.units.currency, pos.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = pos.units.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.units.number, pos.units.currency, pos.cost.number, pos.cost.currency, pos.units.number * pos.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.units.number, pos.units.currency, None, pos.units.currency, pos.units.number, pos.units.number, None, None) holdings.append(holding) return holdings beancount.ops.holdings.holding_to_position(holding) \uf0c1 Convert the holding to a position. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_position(holding): \"\"\"Convert the holding to a position. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" return position.Position( amount.Amount(holding.number, holding.currency), (position.Cost(holding.cost_number, holding.cost_currency, None, None) if holding.cost_number else None)) beancount.ops.holdings.holding_to_posting(holding) \uf0c1 Convert the holding to an instance of Posting. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_posting(holding): \"\"\"Convert the holding to an instance of Posting. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" position_ = holding_to_position(holding) price = (amount.Amount(holding.price_number, holding.cost_currency) if holding.price_number else None) return data.Posting(holding.account, position_.units, position_.cost, price, None, None) beancount.ops.holdings.reduce_relative(holdings) \uf0c1 Convert the market and book values of the given list of holdings to relative data. Parameters: holdings \u2013 A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. Source code in beancount/ops/holdings.py def reduce_relative(holdings): \"\"\"Convert the market and book values of the given list of holdings to relative data. Args: holdings: A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. \"\"\" # Group holdings by value currency. by_currency = collections.defaultdict(list) ordering = {} for index, holding in enumerate(holdings): ordering.setdefault(holding.cost_currency, index) by_currency[holding.cost_currency].append(holding) fractional_holdings = [] for currency in sorted(by_currency, key=ordering.get): currency_holdings = by_currency[currency] # Compute total market value for that currency. total_book_value = ZERO total_market_value = ZERO for holding in currency_holdings: if holding.book_value: total_book_value += holding.book_value if holding.market_value: total_market_value += holding.market_value # Sort the currency's holdings with decreasing values of market value. currency_holdings.sort( key=lambda holding: holding.market_value or ZERO, reverse=True) # Output new holdings with the relevant values replaced. for holding in currency_holdings: fractional_holdings.append( holding._replace(book_value=(holding.book_value / total_book_value if holding.book_value is not None else None), market_value=(holding.market_value / total_market_value if holding.market_value is not None else None))) return fractional_holdings beancount.ops.holdings.scale_holding(holding, scale_factor) \uf0c1 Scale the values of a holding. Parameters: holding \u2013 An instance of Holding. scale_factor \u2013 A float or Decimal number. Returns: A scaled copy of the holding. Source code in beancount/ops/holdings.py def scale_holding(holding, scale_factor): \"\"\"Scale the values of a holding. Args: holding: An instance of Holding. scale_factor: A float or Decimal number. Returns: A scaled copy of the holding. \"\"\" return Holding( holding.account, holding.number * scale_factor if holding.number else None, holding.currency, holding.cost_number, holding.cost_currency, holding.book_value * scale_factor if holding.book_value else None, holding.market_value * scale_factor if holding.market_value else None, holding.price_number, holding.price_date) beancount.ops.lifetimes \uf0c1 Given a Beancount ledger, compute time intervals where we hold each commodity. This script computes, for each commodity, which time intervals it is required at. This can then be used to identify a list of dates at which we need to fetch prices in order to properly fill the price database. beancount.ops.lifetimes.compress_intervals_days(intervals, num_days) \uf0c1 Compress a list of date pairs to ignore short stretches of unused days. Parameters: intervals \u2013 A list of pairs of datetime.date instances. num_days \u2013 An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_intervals_days(intervals, num_days): \"\"\"Compress a list of date pairs to ignore short stretches of unused days. Args: intervals: A list of pairs of datetime.date instances. num_days: An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" ignore_interval = datetime.timedelta(days=num_days) new_intervals = [] iter_intervals = iter(intervals) last_begin, last_end = next(iter_intervals) for date_begin, date_end in iter_intervals: if date_begin - last_end < ignore_interval: # Compress. last_end = date_end continue new_intervals.append((last_begin, last_end)) last_begin, last_end = date_begin, date_end new_intervals.append((last_begin, last_end)) return new_intervals beancount.ops.lifetimes.compress_lifetimes_days(lifetimes_map, num_days) \uf0c1 Compress a lifetimes map to ignore short stretches of unused days. Parameters: lifetimes_map \u2013 A dict of currency intervals as returned by get_commodity_lifetimes. num_days \u2013 An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_lifetimes_days(lifetimes_map, num_days): \"\"\"Compress a lifetimes map to ignore short stretches of unused days. Args: lifetimes_map: A dict of currency intervals as returned by get_commodity_lifetimes. num_days: An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" return {currency_pair: compress_intervals_days(intervals, num_days) for currency_pair, intervals in lifetimes_map.items()} beancount.ops.lifetimes.get_commodity_lifetimes(entries) \uf0c1 Given a list of directives, figure out the life of each commodity. Parameters: entries \u2013 A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day after the last date seen. Source code in beancount/ops/lifetimes.py def get_commodity_lifetimes(entries): \"\"\"Given a list of directives, figure out the life of each commodity. Args: entries: A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day _after_ the last date seen. \"\"\" lifetimes = collections.defaultdict(list) # The current set of active commodities. commodities = set() # The current balances across all accounts. balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Process only transaction entries. if not isinstance(entry, data.Transaction): continue # Update the balance of affected accounts and check locally whether that # triggered a change in the set of commodities. commodities_changed = False for posting in entry.postings: balance = balances[posting.account] commodities_before = balance.currency_pairs() balance.add_position(posting) commodities_after = balance.currency_pairs() if commodities_after != commodities_before: commodities_changed = True # If there was a change in one of the affected account's list of # commodities, recompute the total set globally. This should not # occur very frequently. if commodities_changed: new_commodities = set( itertools.chain(*(inv.currency_pairs() for inv in balances.values()))) if new_commodities != commodities: # The new global set of commodities has changed; update our # the dictionary of intervals. for currency in new_commodities - commodities: lifetimes[currency].append((entry.date, None)) for currency in commodities - new_commodities: lifetime = lifetimes[currency] begin_date, end_date = lifetime.pop(-1) assert end_date is None lifetime.append((begin_date, entry.date + ONEDAY)) # Update our current set. commodities = new_commodities return lifetimes beancount.ops.lifetimes.required_weekly_prices(lifetimes_map, date_last) \uf0c1 Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Parameters: lifetimes_map \u2013 A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last \u2013 A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). Source code in beancount/ops/lifetimes.py def required_weekly_prices(lifetimes_map, date_last): \"\"\"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Args: lifetimes_map: A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last: A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). \"\"\" results = [] for currency_pair, intervals in lifetimes_map.items(): if currency_pair[1] is None: continue for date_begin, date_end in intervals: # Find first Friday before the minimum date. diff_days = 4 - date_begin.weekday() if diff_days > 1: diff_days -= 7 date = date_begin + datetime.timedelta(days=diff_days) # Iterate over all Fridays. if date_end is None: date_end = date_last while date < date_end: results.append((date, currency_pair[0], currency_pair[1])) date += ONE_WEEK return sorted(results) beancount.ops.pad \uf0c1 Automatic padding of gaps between entries. beancount.ops.pad.PadError ( tuple ) \uf0c1 PadError(source, message, entry) beancount.ops.pad.PadError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/pad.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.pad.PadError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of PadError(source, message, entry) beancount.ops.pad.PadError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/pad.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.pad.pad(entries, options_map) \uf0c1 Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Parameters: entries \u2013 A list of directives. options_map \u2013 A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. Source code in beancount/ops/pad.py def pad(entries, options_map): \"\"\"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Args: entries: A list of directives. options_map: A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. \"\"\" pad_errors = [] # Find all the pad entries and group them by account. pads = list(misc_utils.filter_type(entries, data.Pad)) pad_dict = misc_utils.groupby(lambda x: x.account, pads) # Partially realize the postings, so we can iterate them by account. by_account = realization.postings_by_account(entries) # A dict of pad -> list of entries to be inserted. new_entries = {id(pad): [] for pad in pads} # Process each account that has a padding group. for account_, pad_list in sorted(pad_dict.items()): # Last encountered / currency active pad entry. active_pad = None # Gather all the postings for the account and its children. postings = [] is_child = account.parent_matcher(account_) for item_account, item_postings in by_account.items(): if is_child(item_account): postings.extend(item_postings) postings.sort(key=data.posting_sortkey) # A set of currencies already padded so far in this account. padded_lots = set() pad_balance = inventory.Inventory() for entry in postings: assert not isinstance(entry, data.Posting) if isinstance(entry, data.TxnPosting): # This is a transaction; update the running balance for this # account. pad_balance.add_position(entry.posting) elif isinstance(entry, data.Pad): if entry.account == account_: # Mark this newly encountered pad as active and allow all lots # to be padded heretofore. active_pad = entry padded_lots = set() elif isinstance(entry, data.Balance): check_amount = entry.amount # Compare the current balance amount to the expected one from # the check entry. IMPORTANT: You need to understand that this # does not check a single position, but rather checks that the # total amount for a particular currency (which itself is # distinct from the cost). balance_amount = pad_balance.get_currency_units(check_amount.currency) diff_amount = amount.sub(balance_amount, check_amount) # Use the specified tolerance or automatically infer it. tolerance = balance.get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: # The check fails; we need to pad. # Pad only if pad entry is active and we haven't already # padded that lot since it was last encountered. if active_pad and (check_amount.currency not in padded_lots): # Note: we decide that it's an error to try to pad # positions at cost; we check here that all the existing # positions with that currency have no cost. positions = [pos for pos in pad_balance.get_positions() if pos.units.currency == check_amount.currency] for position_ in positions: if position_.cost is not None: pad_errors.append( PadError(entry.meta, (\"Attempt to pad an entry with cost for \" \"balance: {}\".format(pad_balance)), active_pad)) # Thus our padding lot is without cost by default. diff_position = position.Position.from_amounts( amount.Amount(check_amount.number - balance_amount.number, check_amount.currency)) # Synthesize a new transaction entry for the difference. narration = ('(Padding inserted for Balance of {} for ' 'difference {})').format(check_amount, diff_position) new_entry = data.Transaction( active_pad.meta.copy(), active_pad.date, flags.FLAG_PADDING, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) new_entry.postings.append( data.Posting(active_pad.account, diff_position.units, diff_position.cost, None, None, None)) neg_diff_position = -diff_position new_entry.postings.append( data.Posting(active_pad.source_account, neg_diff_position.units, neg_diff_position.cost, None, None, None)) # Save it for later insertion after the active pad. new_entries[id(active_pad)].append(new_entry) # Fixup the running balance. pos, _ = pad_balance.add_position(diff_position) if pos is not None and pos.is_negative_at_cost(): raise ValueError( \"Position held at cost goes negative: {}\".format(pos)) # Mark this lot as padded. Further checks should not pad this lot. padded_lots.add(check_amount.currency) # Insert the newly created entries right after the pad entries that created them. padded_entries = [] for entry in entries: padded_entries.append(entry) if isinstance(entry, data.Pad): entry_list = new_entries[id(entry)] if entry_list: padded_entries.extend(entry_list) else: # Generate errors on unused pad entries. pad_errors.append( PadError(entry.meta, \"Unused Pad entry\", entry)) return padded_entries, pad_errors beancount.ops.summarize \uf0c1 Summarization of entries. This code is used to summarize a sequence of entries (e.g. during a time period) into a few \"opening balance\" entries. This is when computing a balance sheet for a specific time period: we don't want to see the entries from before some period of time, so we fold them into a single transaction per account that has the sum total amount of that account. beancount.ops.summarize.balance_by_account(entries, date=None) \uf0c1 Sum up the balance per account for all entries strictly before 'date'. Parameters: entries \u2013 A list of directives. date \u2013 An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. Source code in beancount/ops/summarize.py def balance_by_account(entries, date=None): \"\"\"Sum up the balance per account for all entries strictly before 'date'. Args: entries: A list of directives. date: An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. \"\"\" balances = collections.defaultdict(inventory.Inventory) for index, entry in enumerate(entries): if date and entry.date >= date: break if isinstance(entry, Transaction): for posting in entry.postings: account_balance = balances[posting.account] # Note: We must allow negative lots at cost, because this may be # used to reduce a filtered list of entries which may not # include the entries necessary to keep units at cost always # above zero. The only summation that is guaranteed to be above # zero is if all the entries are being summed together, no # entries are filtered, at least for a particular account's # postings. account_balance.add_position(posting) else: index = len(entries) return balances, index beancount.ops.summarize.cap(entries, account_types, conversion_currency, account_earnings, account_conversions) \uf0c1 Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Parameters: entries \u2013 A list of directives. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions \u2013 A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. Source code in beancount/ops/summarize.py def cap(entries, account_types, conversion_currency, account_earnings, account_conversions): \"\"\"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Args: entries: A list of directives. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions: A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. \"\"\" # Transfer the balances of income and expense accounts as earnings / net # income. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, None, income_statement_account_pred, account_earnings) # Insert final conversion entries. entries = conversions(entries, account_conversions, conversion_currency, None) return entries beancount.ops.summarize.cap_opt(entries, options_map) \uf0c1 Close by getting all the parameters from an options map. See cap() for details. Parameters: entries \u2013 See cap(). options_map \u2013 A parser's option_map. Returns: Same as close(). Source code in beancount/ops/summarize.py def cap_opt(entries, options_map): \"\"\"Close by getting all the parameters from an options map. See cap() for details. Args: entries: See cap(). options_map: A parser's option_map. Returns: Same as close(). \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) conversion_currency = options_map['conversion_currency'] return cap(entries, account_types, conversion_currency, *current_accounts) beancount.ops.summarize.clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions) \uf0c1 Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Parameters: entries \u2013 A list of directive tuples. begin_date \u2013 A datetime.date instance, the beginning of the period. end_date \u2013 A datetime.date instance, one day beyond the end of the period. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Args: entries: A list of directive tuples. begin_date: A datetime.date instance, the beginning of the period. end_date: A datetime.date instance, one day beyond the end of the period. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, begin_date, income_statement_account_pred, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, begin_date, account_opening) # Truncate the entries after this. entries = truncate(entries, end_date) # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, end_date) return entries, index beancount.ops.summarize.clamp_opt(entries, begin_date, end_date, options_map) \uf0c1 Clamp by getting all the parameters from an options map. See clamp() for details. Parameters: entries \u2013 See clamp(). begin_date \u2013 See clamp(). end_date \u2013 See clamp(). options_map \u2013 A parser's option_map. Returns: Same as clamp(). Source code in beancount/ops/summarize.py def clamp_opt(entries, begin_date, end_date, options_map): \"\"\"Clamp by getting all the parameters from an options map. See clamp() for details. Args: entries: See clamp(). begin_date: See clamp(). end_date: See clamp(). options_map: A parser's option_map. Returns: Same as clamp(). \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return clamp(entries, begin_date, end_date, account_types, conversion_currency, *previous_accounts) beancount.ops.summarize.clear(entries, date, account_types, account_earnings) \uf0c1 Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types \u2013 An instance of AccountTypes. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. Source code in beancount/ops/summarize.py def clear(entries, date, account_types, account_earnings): \"\"\"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types: An instance of AccountTypes. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. \"\"\" index = len(entries) # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) new_entries = transfer_balances(entries, date, income_statement_account_pred, account_earnings) return new_entries, index beancount.ops.summarize.clear_opt(entries, date, options_map) \uf0c1 Convenience function to clear() using an options map. Source code in beancount/ops/summarize.py def clear_opt(entries, date, options_map): \"\"\"Convenience function to clear() using an options map. \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) return clear(entries, date, account_types, current_accounts[0]) beancount.ops.summarize.close(entries, date, conversion_currency, account_conversions) \uf0c1 Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will Remove all entries which occur after 'date', if given. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. Source code in beancount/ops/summarize.py def close(entries, date, conversion_currency, account_conversions): \"\"\"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will 1. Remove all entries which occur after 'date', if given. 2. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. \"\"\" # Truncate the entries after the date, if a date has been provided. if date is not None: entries = truncate(entries, date) # Keep an index to the truncated list of entries (before conversions). index = len(entries) # Insert a conversions entry to ensure the total balance of all accounts is # flush zero. entries = conversions(entries, account_conversions, conversion_currency, date) return entries, index beancount.ops.summarize.close_opt(entries, date, options_map) \uf0c1 Convenience function to close() using an options map. Source code in beancount/ops/summarize.py def close_opt(entries, date, options_map): \"\"\"Convenience function to close() using an options map. \"\"\" conversion_currency = options_map['conversion_currency'] current_accounts = options.get_current_accounts(options_map) return close(entries, date, conversion_currency, current_accounts[1]) beancount.ops.summarize.conversions(entries, conversion_account, conversion_currency, date=None) \uf0c1 Insert a conversion entry at date 'date' at the given account. Parameters: entries \u2013 A list of entries. conversion_account \u2013 A string, the account to book against. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. date \u2013 The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. Source code in beancount/ops/summarize.py def conversions(entries, conversion_account, conversion_currency, date=None): \"\"\"Insert a conversion entry at date 'date' at the given account. Args: entries: A list of entries. conversion_account: A string, the account to book against. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. date: The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. \"\"\" # Compute the balance at the given date. conversion_balance = interpolate.compute_entries_balance(entries, date=date) # Early exit if there is nothing to do. conversion_cost_balance = conversion_balance.reduce(convert.get_cost) if conversion_cost_balance.is_empty(): return entries # Calculate the index and the date for the new entry. We want to store it as # the last transaction of the day before. if date is not None: index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) last_date = date - datetime.timedelta(days=1) else: index = len(entries) last_date = entries[-1].date meta = data.new_metadata('', -1) narration = 'Conversion for {}'.format(conversion_balance) conversion_entry = Transaction(meta, last_date, flags.FLAG_CONVERSIONS, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) for position in conversion_cost_balance.get_positions(): # Important note: Set the cost to zero here to maintain the balance # invariant. (This is the only single place we cheat on the balance rule # in the entire system and this is necessary; see documentation on # Conversions.) price = amount.Amount(ZERO, conversion_currency) neg_pos = -position conversion_entry.postings.append( data.Posting(conversion_account, neg_pos.units, neg_pos.cost, price, None, None)) # Make a copy of the list of entries and insert the new transaction into it. new_entries = list(entries) new_entries.insert(index, conversion_entry) return new_entries beancount.ops.summarize.create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template) \uf0c1 \"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Parameters: balances \u2013 A dict of account name strings to Inventory instances. date \u2013 A datetime.date object, the date at which to create the transaction. source_account \u2013 A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction \u2013 If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta \u2013 A dict to use as metadata for the transactions. flag \u2013 A string, the flag to use for the transactions. narration_template \u2013 A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. Source code in beancount/ops/summarize.py def create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template): \"\"\"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Args: balances: A dict of account name strings to Inventory instances. date: A datetime.date object, the date at which to create the transaction. source_account: A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction: If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta: A dict to use as metadata for the transactions. flag: A string, the flag to use for the transactions. narration_template: A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. \"\"\" new_entries = [] for account, account_balance in sorted(balances.items()): # Don't create new entries where there is no balance. if account_balance.is_empty(): continue narration = narration_template.format(account=account, date=date) if not direction: account_balance = -account_balance postings = [] new_entry = Transaction( meta, date, flag, None, narration, data.EMPTY_SET, data.EMPTY_SET, postings) for position in account_balance.get_positions(): postings.append(data.Posting(account, position.units, position.cost, None, None, None)) cost = -convert.get_cost(position) postings.append(data.Posting(source_account, cost, None, None, None, None)) new_entries.append(new_entry) return new_entries beancount.ops.summarize.get_open_entries(entries, date) \uf0c1 Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Parameters: entries \u2013 A list of directives. date \u2013 The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. Source code in beancount/ops/summarize.py def get_open_entries(entries, date): \"\"\"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Args: entries: A list of directives. date: The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. \"\"\" open_entries = {} for index, entry in enumerate(entries): if date is not None and entry.date >= date: break if isinstance(entry, Open): try: ex_index, ex_entry = open_entries[entry.account] if entry.date < ex_entry.date: open_entries[entry.account] = (index, entry) except KeyError: open_entries[entry.account] = (index, entry) elif isinstance(entry, Close): # If there is no corresponding open, don't raise an error. open_entries.pop(entry.account, None) return [entry for (index, entry) in sorted(open_entries.values())] beancount.ops.summarize.open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions) \uf0c1 Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will Insert conversion transactions at the given open date, then Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, the date at which to do this. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will 1. Insert conversion transactions at the given open date, then 2. Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally 3. It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Args: entries: A list of directive tuples. date: A datetime.date instance, the date at which to do this. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, date) # Transfer income and expenses before the period to equity. entries, _ = clear(entries, date, account_types, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, date, account_opening) return entries, index beancount.ops.summarize.open_opt(entries, date, options_map) \uf0c1 Convenience function to open() using an options map. Source code in beancount/ops/summarize.py def open_opt(entries, date, options_map): \"\"\"Convenience function to open() using an options map. \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return open(entries, date, account_types, conversion_currency, *previous_accounts) beancount.ops.summarize.summarize(entries, date, account_opening) \uf0c1 Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the cutoff date before which to summarize. account_opening \u2013 A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. Source code in beancount/ops/summarize.py def summarize(entries, date, account_opening): \"\"\"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Args: entries: A list of directives. date: A datetime.date instance, the cutoff date before which to summarize. account_opening: A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. \"\"\" # Compute balances at date. balances, index = balance_by_account(entries, date) # We need to insert the entries with a date previous to subsequent checks, # to maintain ensure the open directives show up before any transaction. summarize_date = date - datetime.timedelta(days=1) # Create summarization / opening balance entries. summarizing_entries = create_entries_from_balances( balances, summarize_date, account_opening, True, data.new_metadata('', 0), flags.FLAG_SUMMARIZE, \"Opening balance for '{account}' (Summarization)\") # Insert the last price entry for each commodity from before the date. price_entries = prices.get_last_price_entries(entries, date) # Gather the list of active open entries at date. open_entries = get_open_entries(entries, date) # Compute entries before the date and preserve the entries after the date. before_entries = sorted(open_entries + price_entries + summarizing_entries, key=data.entry_sortkey) after_entries = entries[index:] # Return a new list of entries and the index that points after the entries # were inserted. return (before_entries + after_entries), len(before_entries) beancount.ops.summarize.transfer_balances(entries, date, account_pred, transfer_account) \uf0c1 Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to make the transfer. account_pred \u2013 A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account \u2013 A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. Source code in beancount/ops/summarize.py def transfer_balances(entries, date, account_pred, transfer_account): \"\"\"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Args: entries: A list of directives. date: A datetime.date instance, the date at which to make the transfer. account_pred: A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account: A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. \"\"\" # Don't bother doing anything if there are no entries. if not entries: return entries # Compute balances at date. balances, index = balance_by_account(entries, date) # Filter out to keep only the accounts we want. transfer_balances = {account: balance for account, balance in balances.items() if account_pred(account)} # We need to insert the entries at the end of the previous day. if date: transfer_date = date - datetime.timedelta(days=1) else: transfer_date = entries[-1].date # Create transfer entries. transfer_entries = create_entries_from_balances( transfer_balances, transfer_date, transfer_account, False, data.new_metadata('', 0), flags.FLAG_TRANSFER, \"Transfer balance for '{account}' (Transfer balance)\") # Remove balance assertions that occur after a transfer on an account that # has been transferred away; they would break. after_entries = [entry for entry in entries[index:] if not (isinstance(entry, balance.Balance) and entry.account in transfer_balances)] # Split the new entries in a new list. return (entries[:index] + transfer_entries + after_entries) beancount.ops.summarize.truncate(entries, date) \uf0c1 Filter out all the entries at and after date. Returns a new list of entries. Parameters: entries \u2013 A sorted list of directives. date \u2013 A datetime.date instance. Returns: A truncated list of directives. Source code in beancount/ops/summarize.py def truncate(entries, date): \"\"\"Filter out all the entries at and after date. Returns a new list of entries. Args: entries: A sorted list of directives. date: A datetime.date instance. Returns: A truncated list of directives. \"\"\" index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) return entries[:index] beancount.ops.validation \uf0c1 Validation checks. These checks are intended to be run after all the plugins have transformed the list of entries, just before serving them or generating reports from them. The idea is to ensure a reasonable set of invariants and generate errors if those invariants are violated. They are not sanity checks--user data is subject to constraints which are hopefully detected here and which will result in errors trickled up to the user. beancount.ops.validation.ValidationError ( tuple ) \uf0c1 ValidationError(source, message, entry) beancount.ops.validation.ValidationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/validation.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.ops.validation.ValidationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ValidationError(source, message, entry) beancount.ops.validation.ValidationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/ops/validation.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.ops.validation.validate(entries, options_map, log_timings=None, extra_validations=None) \uf0c1 Perform all the standard checks on parsed contents. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. log_timings \u2013 An optional function to use for logging the time of individual operations. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate(entries, options_map, log_timings=None, extra_validations=None): \"\"\"Perform all the standard checks on parsed contents. Args: entries: A list of directives. unused_options_map: An options map. log_timings: An optional function to use for logging the time of individual operations. extra_validations: A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. \"\"\" validation_tests = VALIDATIONS if extra_validations: validation_tests += extra_validations # Run various validation routines define above. errors = [] for validation_function in validation_tests: with misc_utils.log_time('function: {}'.format(validation_function.__name__), log_timings, indent=2): new_errors = validation_function(entries, options_map) errors.extend(new_errors) return errors beancount.ops.validation.validate_active_accounts(entries, unused_options_map) \uf0c1 Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_active_accounts(entries, unused_options_map): \"\"\"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" error_pairs = [] active_set = set() opened_accounts = set() for entry in entries: if isinstance(entry, data.Open): active_set.add(entry.account) opened_accounts.add(entry.account) elif isinstance(entry, data.Close): active_set.discard(entry.account) else: for account in getters.get_entry_accounts(entry): if account not in active_set: # Allow document and note directives that occur after an # account is closed. if (isinstance(entry, ALLOW_AFTER_CLOSE) and account in opened_accounts): continue # Register an error to be logged later, with an appropriate # message. error_pairs.append((account, entry)) # Refine the error message to disambiguate between the case of an account # that has never been seen and one that was simply not active at the time. errors = [] for account, entry in error_pairs: if account in opened_accounts: message = \"Invalid reference to inactive account '{}'\".format(account) else: message = \"Invalid reference to unknown account '{}'\".format(account) errors.append(ValidationError(entry.meta, message, entry)) return errors beancount.ops.validation.validate_check_transaction_balances(entries, options_map) \uf0c1 Check again that all transaction postings balance, as users may have transformed transactions. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_check_transaction_balances(entries, options_map): \"\"\"Check again that all transaction postings balance, as users may have transformed transactions. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Note: this is a bit slow; we could limit our checks to the original # transactions by using the hash function in the loader. errors = [] for entry in entries: if isinstance(entry, Transaction): # IMPORTANT: This validation is _crucial_ and cannot be skipped. # This is where we actually detect and warn on unbalancing # transactions. This _must_ come after the user routines, because # unbalancing input is legal, as those types of transactions may be # \"fixed up\" by a user-plugin. In other words, we want to allow # users to input unbalancing transactions as long as the final # transactions objects that appear on the stream (after processing # the plugins) are balanced. See {9e6c14b51a59}. # # Detect complete sets of postings that have residual balance; residual = interpolate.compute_residual(entry.postings) tolerances = interpolate.infer_tolerances(entry.postings, options_map) if not residual.is_small(tolerances): errors.append( ValidationError(entry.meta, \"Transaction does not balance: {}\".format(residual), entry)) return errors beancount.ops.validation.validate_currency_constraints(entries, options_map) \uf0c1 Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_currency_constraints(entries, options_map): \"\"\"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Get all the open entries with currency constraints. open_map = {entry.account: entry for entry in entries if isinstance(entry, Open) and entry.currencies} errors = [] for entry in entries: if not isinstance(entry, Transaction): continue for posting in entry.postings: # Look up the corresponding account's valid currencies; skip the # check if there are none specified. try: open_entry = open_map[posting.account] valid_currencies = open_entry.currencies if not valid_currencies: continue except KeyError: continue # Perform the check. if posting.units.currency not in valid_currencies: errors.append( ValidationError( entry.meta, \"Invalid currency {} for account '{}'\".format( posting.units.currency, posting.account), entry)) return errors beancount.ops.validation.validate_data_types(entries, options_map) \uf0c1 Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_data_types(entries, options_map): \"\"\"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] for entry in entries: try: data.sanity_check_types( entry, options_map[\"allow_deprecated_none_for_tags_and_links\"]) except AssertionError as exc: errors.append( ValidationError(entry.meta, \"Invalid data types: {}\".format(exc), entry)) return errors beancount.ops.validation.validate_documents_paths(entries, options_map) \uf0c1 Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_documents_paths(entries, options_map): \"\"\"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" return [ValidationError(entry.meta, \"Invalid relative path for entry\", entry) for entry in entries if (isinstance(entry, Document) and not path.isabs(entry.filename))] beancount.ops.validation.validate_duplicate_balances(entries, unused_options_map) \uf0c1 Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_balances(entries, unused_options_map): \"\"\"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. balance_entries = {} for entry in entries: if not isinstance(entry, data.Balance): continue key = (entry.account, entry.amount.currency, entry.date) try: previous_entry = balance_entries[key] if entry.amount != previous_entry.amount: errors.append( ValidationError( entry.meta, \"Duplicate balance assertion with different amounts\", entry)) except KeyError: balance_entries[key] = entry return errors beancount.ops.validation.validate_duplicate_commodities(entries, unused_options_map) \uf0c1 Check that commodity entries are unique for each commodity. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_commodities(entries, unused_options_map): \"\"\"Check that commodity entries are unique for each commodity. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. commodity_entries = {} for entry in entries: if not isinstance(entry, data.Commodity): continue key = entry.currency try: previous_entry = commodity_entries[key] if previous_entry: errors.append( ValidationError( entry.meta, \"Duplicate commodity directives for '{}'\".format(key), entry)) except KeyError: commodity_entries[key] = entry return errors beancount.ops.validation.validate_open_close(entries, unused_options_map) \uf0c1 Check constraints on open and close directives themselves. This method checks two kinds of constraints: An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. Close directives may only appears if an open directive has been seen previous (chronologically). The date of close directives must be strictly greater than their corresponding open directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_open_close(entries, unused_options_map): \"\"\"Check constraints on open and close directives themselves. This method checks two kinds of constraints: 1. An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. 2. Close directives may only appears if an open directive has been seen previous (chronologically). 3. The date of close directives must be strictly greater than their corresponding open directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] open_map = {} close_map = {} for entry in entries: if isinstance(entry, Open): if entry.account in open_map: errors.append( ValidationError( entry.meta, \"Duplicate open directive for {}\".format(entry.account), entry)) else: open_map[entry.account] = entry elif isinstance(entry, Close): if entry.account in close_map: errors.append( ValidationError( entry.meta, \"Duplicate close directive for {}\".format(entry.account), entry)) else: try: open_entry = open_map[entry.account] if entry.date <= open_entry.date: errors.append( ValidationError( entry.meta, \"Internal error: closing date for {} \" \"appears before opening date\".format(entry.account), entry)) except KeyError: errors.append( ValidationError( entry.meta, \"Unopened account {} is being closed\".format(entry.account), entry)) close_map[entry.account] = entry return errors","title":"beancount.ops"},{"location":"api_reference/beancount.ops.html#beancountops","text":"Operations on the entries defined in the core modules. This package contains various functions which operate on lists of entries.","title":"beancount.ops"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance","text":"Automatic padding of gaps between entries.","title":"balance"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError","text":"BalanceError(source, message, entry)","title":"BalanceError"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/balance.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__new__","text":"Create new instance of BalanceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.BalanceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/balance.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.check","text":"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Parameters: entries \u2013 A list of directives. options_map \u2013 A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. Source code in beancount/ops/balance.py def check(entries, options_map): \"\"\"Process the balance assertion directives. For each Balance directive, check that their expected balance corresponds to the actual balance computed at that time and replace failing ones by new ones with a flag that indicates failure. Args: entries: A list of directives. options_map: A dict of options, parsed from the input file. Returns: A pair of a list of directives and a list of balance check errors. \"\"\" new_entries = [] check_errors = [] # This is similar to realization, but performed in a different order, and # where we only accumulate inventories for accounts that have balance # assertions in them (this saves on time). Here we process the entries one # by one along with the balance checks. We use a temporary realization in # order to hold the incremental tree of balances, so that we can easily get # the amounts of an account's subaccounts for making checks on parent # accounts. real_root = realization.RealAccount('') # Figure out the set of accounts for which we need to compute a running # inventory balance. asserted_accounts = {entry.account for entry in entries if isinstance(entry, Balance)} # Add all children accounts of an asserted account to be calculated as well, # and pre-create these accounts, and only those (we're just being tight to # make sure). asserted_match_list = [account.parent_matcher(account_) for account_ in asserted_accounts] for account_ in getters.get_accounts(entries): if (account_ in asserted_accounts or any(match(account_) for match in asserted_match_list)): realization.get_or_create(real_root, account_) # Get the Open directives for each account. open_close_map = getters.get_account_open_close(entries) for entry in entries: if isinstance(entry, Transaction): # For each of the postings' accounts, update the balance inventory. for posting in entry.postings: real_account = realization.get(real_root, posting.account) # The account will have been created only if we're meant to track it. if real_account is not None: # Note: Always allow negative lots for the purpose of balancing. # This error should show up somewhere else than here. real_account.balance.add_position(posting) elif isinstance(entry, Balance): # Check that the currency of the balance check is one of the allowed # currencies for that account. expected_amount = entry.amount try: open, _ = open_close_map[entry.account] except KeyError: check_errors.append( BalanceError(entry.meta, \"Account '{}' does not exist: \".format(entry.account), entry)) continue if (expected_amount is not None and open and open.currencies and expected_amount.currency not in open.currencies): check_errors.append( BalanceError(entry.meta, \"Invalid currency '{}' for Balance directive: \".format( expected_amount.currency), entry)) # Sum up the current balances for this account and its # sub-accounts. We want to support checks for parent accounts # for the total sum of their subaccounts. # # FIXME: Improve the performance further by computing the balance # for the desired currency only. This won't allow us to cache in # this way but may be faster, if we're not asserting all the # currencies. Furthermore, we could probably avoid recomputing the # balance if a subtree of positions hasn't been invalidated by a new # position added to the realization. Do this. real_account = realization.get(real_root, entry.account) assert real_account is not None, \"Missing {}\".format(entry.account) subtree_balance = realization.compute_balance(real_account, leaf_only=False) # Get only the amount in the desired currency. balance_amount = subtree_balance.get_currency_units(expected_amount.currency) # Check if the amount is within bounds of the expected amount. diff_amount = amount.sub(balance_amount, expected_amount) # Use the specified tolerance or automatically infer it. tolerance = get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: check_errors.append( BalanceError(entry.meta, (\"Balance failed for '{}': \" \"expected {} != accumulated {} ({} {})\").format( entry.account, expected_amount, balance_amount, abs(diff_amount.number), ('too much' if diff_amount.number > 0 else 'too little')), entry)) # Substitute the entry by a failing entry, with the diff_amount # field set on it. I'm not entirely sure that this is the best # of ideas, maybe leaving the original check intact and insert a # new error entry might be more functional or easier to # understand. entry = entry._replace( meta=entry.meta.copy(), diff_amount=diff_amount) new_entries.append(entry) return new_entries, check_errors","title":"check()"},{"location":"api_reference/beancount.ops.html#beancount.ops.balance.get_balance_tolerance","text":"Get the tolerance amount for a single entry. Parameters: balance_entry \u2013 An instance of data.Balance options_map \u2013 An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. Source code in beancount/ops/balance.py def get_balance_tolerance(balance_entry, options_map): \"\"\"Get the tolerance amount for a single entry. Args: balance_entry: An instance of data.Balance options_map: An options dict, as per the parser. Returns: A Decimal, the amount of tolerance implied by the directive. \"\"\" if balance_entry.tolerance is not None: # Use the balance-specific tolerance override if it is provided. tolerance = balance_entry.tolerance else: expo = balance_entry.amount.number.as_tuple().exponent if expo < 0: # Be generous and always allow twice the multiplier on Balance and # Pad because the user creates these and the rounding of those # balances may often be further off than those used within a single # transaction. tolerance = options_map[\"inferred_tolerance_multiplier\"] * 2 tolerance = ONE.scaleb(expo) * tolerance else: tolerance = ZERO return tolerance","title":"get_balance_tolerance()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops","text":"Basic filtering and aggregation operations on lists of entries. This module contains some common basic operations on entries that are complex enough not to belong in core/data.py.","title":"basicops"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.filter_link","text":"Yield all the entries which have the given link. Parameters: link \u2013 A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. Source code in beancount/ops/basicops.py def filter_link(link, entries): \"\"\"Yield all the entries which have the given link. Args: link: A string, the link we are interested in. Yields: Every entry in 'entries' that links to 'link. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.links and link in entry.links): yield entry","title":"filter_link()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.filter_tag","text":"Yield all the entries which have the given tag. Parameters: tag \u2013 A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. Source code in beancount/ops/basicops.py def filter_tag(tag, entries): \"\"\"Yield all the entries which have the given tag. Args: tag: A string, the tag we are interested in. Yields: Every entry in 'entries' that tags to 'tag. \"\"\" for entry in entries: # pylint: disable=bad-continuation if (isinstance(entry, data.Transaction) and entry.tags and tag in entry.tags): yield entry","title":"filter_tag()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.get_common_accounts","text":"Compute the intersection of the accounts on the given entries. Parameters: entries \u2013 A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. Source code in beancount/ops/basicops.py def get_common_accounts(entries): \"\"\"Compute the intersection of the accounts on the given entries. Args: entries: A list of Transaction entries to process. Returns: A set of strings, the names of the common accounts from these entries. \"\"\" assert all(isinstance(entry, data.Transaction) for entry in entries) # If there is a single entry, the common accounts to it is all its accounts. # Note that this also works with no entries (yields an empty set). if len(entries) < 2: if entries: intersection = {posting.account for posting in entries[0].postings} else: intersection = set() else: entries_iter = iter(entries) intersection = set(posting.account for posting in next(entries_iter).postings) for entry in entries_iter: accounts = set(posting.account for posting in entry.postings) intersection &= accounts if not intersection: break return intersection","title":"get_common_accounts()"},{"location":"api_reference/beancount.ops.html#beancount.ops.basicops.group_entries_by_link","text":"Group the list of entries by link. Parameters: entries \u2013 A list of directives/transactions to process. Returns: A dict of link-name to list of entries. Source code in beancount/ops/basicops.py def group_entries_by_link(entries): \"\"\"Group the list of entries by link. Args: entries: A list of directives/transactions to process. Returns: A dict of link-name to list of entries. \"\"\" link_groups = defaultdict(list) for entry in entries: if not (isinstance(entry, data.Transaction) and entry.links): continue for link in entry.links: link_groups[link].append(entry) return link_groups","title":"group_entries_by_link()"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress","text":"Compress multiple entries into a single one. This can be used during import to compress the effective output, for accounts with a large number of similar entries. For example, I had a trading account which would pay out interest every single day. I have no desire to import the full detail of these daily interests, and compressing these interest-only entries to monthly ones made sense. This is the code that was used to carry this out.","title":"compress"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress.compress","text":"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Parameters: entries \u2013 A list of directives. predicate \u2013 A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. Source code in beancount/ops/compress.py def compress(entries, predicate): \"\"\"Compress multiple transactions into single transactions. Replace consecutive sequences of Transaction entries that fulfill the given predicate by a single entry at the date of the last matching entry. 'predicate' is the function that determines if an entry should be compressed. This can be used to simply a list of transactions that are similar and occur frequently. As an example, in a retail FOREX trading account, differential interest of very small amounts is paid every day; it is not relevant to look at the full detail of this interest unless there are other transactions. You can use this to compress it into single entries between other types of transactions. Args: entries: A list of directives. predicate: A callable which accepts an entry and return true if the entry is intended to be compressed. Returns: A list of directives, with compressible transactions replaced by a summary equivalent. \"\"\" new_entries = [] pending = [] for entry in entries: if isinstance(entry, data.Transaction) and predicate(entry): # Save for compressing later. pending.append(entry) else: # Compress and output all the pending entries. if pending: new_entries.append(merge(pending, pending[-1])) pending.clear() # Output the differing entry. new_entries.append(entry) if pending: new_entries.append(merge(pending, pending[-1])) return new_entries","title":"compress()"},{"location":"api_reference/beancount.ops.html#beancount.ops.compress.merge","text":"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Parameters: entries \u2013 A list of directives. prototype_txn \u2013 A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. Source code in beancount/ops/compress.py def merge(entries, prototype_txn): \"\"\"Merge the postings of a list of Transactions into a single one. Merge postings the given entries into a single entry with the Transaction attributes of the prototype. Return the new entry. The combined list of postings are merged if everything about the postings is the same except the number. Args: entries: A list of directives. prototype_txn: A Transaction which is used to create the compressed Transaction instance. Its list of postings is ignored. Returns: A new Transaction instance which contains all the postings from the input entries merged together. \"\"\" # Aggregate the postings together. This is a mapping of numberless postings # to their number of units. postings_map = collections.defaultdict(Decimal) for entry in data.filter_txns(entries): for posting in entry.postings: # We strip the number off the posting to act as an aggregation key. key = data.Posting(posting.account, Amount(None, posting.units.currency), posting.cost, posting.price, posting.flag, None) postings_map[key] += posting.units.number # Create a new transaction with the aggregated postings. new_entry = data.Transaction(prototype_txn.meta, prototype_txn.date, prototype_txn.flag, prototype_txn.payee, prototype_txn.narration, data.EMPTY_SET, data.EMPTY_SET, []) # Sort for at least some stability of output. sorted_items = sorted(postings_map.items(), key=lambda item: (item[0].account, item[0].units.currency, item[1])) # Issue the merged postings. for posting, number in sorted_items: units = Amount(number, posting.units.currency) new_entry.postings.append( data.Posting(posting.account, units, posting.cost, posting.price, posting.flag, posting.meta)) return new_entry","title":"merge()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents","text":"Everything that relates to creating the Document directives.","title":"documents"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError","text":"DocumentError(source, message, entry)","title":"DocumentError"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/documents.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__new__","text":"Create new instance of DocumentError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.DocumentError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/documents.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.find_documents","text":"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Parameters: directory \u2013 A string, the name of the root of the directory hierarchy to be searched. input_filename \u2013 The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only \u2013 A set of valid accounts strings to search for. strict \u2013 A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. Source code in beancount/ops/documents.py def find_documents(directory, input_filename, accounts_only=None, strict=False): \"\"\"Find dated document files under the given directory. If a restricting set of accounts is provided in 'accounts_only', only return entries that correspond to one of the given accounts. Args: directory: A string, the name of the root of the directory hierarchy to be searched. input_filename: The name of the file to be used for the Document directives. This is also used to resolve relative directory names. accounts_only: A set of valid accounts strings to search for. strict: A boolean, set to true if you want to generate errors on documents found in accounts not provided in accounts_only. This is only meaningful if accounts_only is specified. Returns: A list of new Document objects that were created from the files found, and a list of new errors generated. \"\"\" errors = [] # Compute the documents directory name relative to the beancount input # file itself. if not path.isabs(directory): input_directory = path.dirname(input_filename) directory = path.abspath(path.normpath(path.join(input_directory, directory))) # If the directory does not exist, just generate an error and return. if not path.exists(directory): meta = data.new_metadata(input_filename, 0) error = DocumentError( meta, \"Document root '{}' does not exist\".format(directory), None) return ([], [error]) # Walk the hierarchy of files. entries = [] for root, account_name, dirs, files in account.walk(directory): # Look for files that have a dated filename. for filename in files: match = re.match(r'(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d).(.*)', filename) if not match: continue # If a restricting set of accounts was specified, skip document # directives found in accounts with no corresponding account name. if accounts_only is not None and not account_name in accounts_only: if strict: if any(account_name.startswith(account) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in child account {}\".format( filename, account_name), None)) elif any(account.startswith(account_name) for account in accounts_only): errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Document '{}' found in parent account {}\".format( filename, account_name), None)) continue # Create a new directive. meta = data.new_metadata(input_filename, 0) try: date = datetime.date(*map(int, match.group(1, 2, 3))) except ValueError as exc: errors.append(DocumentError( data.new_metadata(input_filename, 0), \"Invalid date on document file '{}': {}\".format( filename, exc), None)) else: entry = data.Document(meta, date, account_name, path.join(root, filename), data.EMPTY_SET, data.EMPTY_SET) entries.append(entry) return (entries, errors)","title":"find_documents()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.process_documents","text":"Check files for document directives and create documents directives automatically. Parameters: entries \u2013 A list of all directives parsed from the file. options_map \u2013 An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. Source code in beancount/ops/documents.py def process_documents(entries, options_map): \"\"\"Check files for document directives and create documents directives automatically. Args: entries: A list of all directives parsed from the file. options_map: An options dict, as is output by the parser. We're using its 'filename' option to figure out relative path to search for documents. Returns: A pair of list of all entries (including new ones), and errors generated during the process of creating document directives. \"\"\" filename = options_map[\"filename\"] # Detect filenames that should convert into entries. autodoc_entries = [] autodoc_errors = [] document_dirs = options_map['documents'] if document_dirs: # Restrict to the list of valid accounts only. accounts = getters.get_accounts(entries) # Accumulate all the entries. for directory in map(path.normpath, document_dirs): new_entries, new_errors = find_documents(directory, filename, accounts) autodoc_entries.extend(new_entries) autodoc_errors.extend(new_errors) # Merge the two lists of entries and errors. Keep the entries sorted. entries.extend(autodoc_entries) entries.sort(key=data.entry_sortkey) return (entries, autodoc_errors)","title":"process_documents()"},{"location":"api_reference/beancount.ops.html#beancount.ops.documents.verify_document_files_exist","text":"Verify that the document entries point to existing files. Parameters: entries \u2013 a list of directives whose documents need to be validated. unused_options_map \u2013 A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. Source code in beancount/ops/documents.py def verify_document_files_exist(entries, unused_options_map): \"\"\"Verify that the document entries point to existing files. Args: entries: a list of directives whose documents need to be validated. unused_options_map: A parser options dict. We're not using it. Returns: The same list of entries, and a list of new errors, if any were encountered. \"\"\" errors = [] for entry in entries: if not isinstance(entry, data.Document): continue if not path.exists(entry.filename): errors.append( DocumentError(entry.meta, 'File does not exist: \"{}\"'.format(entry.filename), entry)) return entries, errors","title":"verify_document_files_exist()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings","text":"Compute final holdings for a list of entries.","title":"holdings"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding","text":"Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date)","title":"Holding"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/holdings.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__new__","text":"Create new instance of Holding(account, number, currency, cost_number, cost_currency, book_value, market_value, price_number, price_date)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.Holding.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/holdings.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.aggregate_holdings_by","text":"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Parameters: keyfun \u2013 A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. Source code in beancount/ops/holdings.py def aggregate_holdings_by(holdings, keyfun): \"\"\"Aggregate holdings by some key. Note that the cost-currency must always be included in the group-key (sums over multiple currency units do not make sense), so it is appended to the sort-key automatically. Args: keyfun: A callable, which returns the key to aggregate by. This key need not include the cost-currency. Returns: A list of aggregated holdings. \"\"\" # Aggregate the groups of holdings. grouped = collections.defaultdict(list) for holding in holdings: key = (keyfun(holding), holding.cost_currency) grouped[key].append(holding) grouped_holdings = (aggregate_holdings_list(key_holdings) for key_holdings in grouped.values()) # We could potentially filter out holdings with zero units here. These types # of holdings might occur on a group with leaked (i.e., non-zero) cost basis # and zero units. However, sometimes are valid merging of multiple # currencies may occur, and the number value will be legitimately set to # ZERO (for various reasons downstream), so we prefer not to ignore the # holding. Callers must be prepared to deal with a holding of ZERO units and # a non-zero cost basis. {0ed05c502e63, b/16} ## nonzero_holdings = (holding ## for holding in grouped_holdings ## if holding.number != ZERO) # Return the holdings in order. return sorted(grouped_holdings, key=lambda holding: (holding.account, holding.currency))","title":"aggregate_holdings_by()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.aggregate_holdings_list","text":"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Parameters: holdings \u2013 A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Exceptions: ValueError \u2013 If multiple cost currencies encountered. Source code in beancount/ops/holdings.py def aggregate_holdings_list(holdings): \"\"\"Aggregate a list of holdings. If there are varying 'account', 'currency' or 'cost_currency' attributes, their values are replaced by '*'. Otherwise they are preserved. Note that all the cost-currency values must be equal in order for aggregations to succeed (without this constraint a sum of units in different currencies has no meaning). Args: holdings: A list of Holding instances. Returns: A single Holding instance, or None, if there are no holdings in the input list. Raises: ValueError: If multiple cost currencies encountered. \"\"\" if not holdings: return None # Note: Holding is a bit overspecified with book and market values. We # recompute them from cost and price numbers here anyhow. units, total_book_value, total_market_value = ZERO, ZERO, ZERO accounts = set() currencies = set() cost_currencies = set() price_dates = set() book_value_seen = False market_value_seen = False for holding in holdings: units += holding.number accounts.add(holding.account) price_dates.add(holding.price_date) currencies.add(holding.currency) cost_currencies.add(holding.cost_currency) if holding.book_value is not None: total_book_value += holding.book_value book_value_seen = True elif holding.cost_number is not None: total_book_value += holding.number * holding.cost_number book_value_seen = True if holding.market_value is not None: total_market_value += holding.market_value market_value_seen = True elif holding.price_number is not None: total_market_value += holding.number * holding.price_number market_value_seen = True if book_value_seen: average_cost = total_book_value / units if units else None else: total_book_value = None average_cost = None if market_value_seen: average_price = total_market_value / units if units else None else: total_market_value = None average_price = None if len(cost_currencies) != 1: raise ValueError(\"Cost currencies are not homogeneous for aggregation: {}\".format( ','.join(map(str, cost_currencies)))) units = units if len(currencies) == 1 else ZERO currency = currencies.pop() if len(currencies) == 1 else '*' cost_currency = cost_currencies.pop() account_ = (accounts.pop() if len(accounts) == 1 else account.commonprefix(accounts)) price_date = price_dates.pop() if len(price_dates) == 1 else None return Holding(account_, units, currency, average_cost, cost_currency, total_book_value, total_market_value, average_price, price_date)","title":"aggregate_holdings_list()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.convert_to_currency","text":"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Parameters: price_map \u2013 A price-map, as built by prices.build_price_map(). target_currency \u2013 The target common currency to convert amounts to. holdings_list \u2013 A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. Source code in beancount/ops/holdings.py def convert_to_currency(price_map, target_currency, holdings_list): \"\"\"Convert the given list of holdings's fields to a common currency. If the rate is not available to convert, leave the fields empty. Args: price_map: A price-map, as built by prices.build_price_map(). target_currency: The target common currency to convert amounts to. holdings_list: A list of holdings.Holding instances. Returns: A modified list of holdings, with the 'extra' field set to the value in 'currency', or None, if it was not possible to convert. \"\"\" # A list of the fields we should convert. convert_fields = ('cost_number', 'book_value', 'market_value', 'price_number') new_holdings = [] for holding in holdings_list: if holding.cost_currency == target_currency: # The holding is already priced in the target currency; do nothing. new_holding = holding else: if holding.cost_currency is None: # There is no cost currency; make the holding priced in its own # units. The price-map should yield a rate of 1.0 and everything # else works out. if holding.currency is None: raise ValueError(\"Invalid currency '{}'\".format(holding.currency)) holding = holding._replace(cost_currency=holding.currency) # Fill in with book and market value as well. if holding.book_value is None: holding = holding._replace(book_value=holding.number) if holding.market_value is None: holding = holding._replace(market_value=holding.number) assert holding.cost_currency, \"Missing cost currency: {}\".format(holding) base_quote = (holding.cost_currency, target_currency) # Get the conversion rate and replace the required numerical # fields.. _, rate = prices.get_latest_price(price_map, base_quote) if rate is not None: new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number, r=rate: number if number is None else number * r, holding) # Ensure we set the new cost currency after conversion. new_holding = new_holding._replace(cost_currency=target_currency) else: # Could not get the rate... clear every field and set the cost # currency to None. This enough marks the holding conversion as # a failure. new_holding = misc_utils.map_namedtuple_attributes( convert_fields, lambda number: None, holding) new_holding = new_holding._replace(cost_currency=None) new_holdings.append(new_holding) return new_holdings","title":"convert_to_currency()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.get_commodities_at_date","text":"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency \u2013 The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). Source code in beancount/ops/holdings.py def get_commodities_at_date(entries, options_map, date=None): \"\"\"Return a list of commodities present at a particular date. This routine fetches the holdings present at a particular date and returns a list of the commodities held in those holdings. This should define the list of price date points required to assess the market value of this portfolio. Notes: * The ticker symbol will be fetched from the corresponding Commodity directive. If there is no ticker symbol defined for a directive or no corresponding Commodity directive, the currency is still included, but 'None' is specified for the symbol. The code that uses this routine should be free to use the currency name to make an attempt to fetch the currency using its name, or to ignore it. * The 'cost-currency' is that which is found on the holdings instance and can be ignored. The 'quote-currency' is that which is declared on the Commodity directive from its 'quote' metadata field. This is used in a routine that fetches prices from a data source on the internet (either from LedgerHub, but you can reuse this in your own script if you build one). Args: entries: A list of directives. date: A datetime.date instance, the date at which to get the list of relevant holdings. Returns: A list of (currency, cost-currency, quote-currency, ticker) tuples, where currency: The Beancount base currency to fetch a price for. cost-currency: The cost-currency of the holdings found at the given date. quote-currency: The currency formally declared as quote currency in the metadata of Commodity directives. ticker: The ticker symbol to use for fetching the price (extracted from the metadata of Commodity directives). \"\"\" # Remove all the entries after the given date, if requested. if date is not None: entries = summarize.truncate(entries, date) # Get the list of holdings at the particular date. holdings_list = get_final_holdings(entries) # Obtain the unique list of currencies we need to fetch. commodities_list = {(holding.currency, holding.cost_currency) for holding in holdings_list} # Add in the associated ticker symbols. commodities_map = getters.get_commodity_map(entries) commodities_symbols_list = [] for currency, cost_currency in sorted(commodities_list): try: commodity_entry = commodities_map[currency] ticker = commodity_entry.meta.get('ticker', None) quote_currency = commodity_entry.meta.get('quote', None) except KeyError: ticker = None quote_currency = None commodities_symbols_list.append( (currency, cost_currency, quote_currency, ticker)) return commodities_symbols_list","title":"get_commodities_at_date()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.get_final_holdings","text":"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Parameters: entries \u2013 A list of directives. included_account_types \u2013 A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map \u2013 A dict of prices, as built by prices.build_price_map(). date \u2013 A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields \u2013 Source code in beancount/ops/holdings.py def get_final_holdings(entries, included_account_types=None, price_map=None, date=None): \"\"\"Get a dictionary of the latest holdings by account. This basically just flattens the balance sheet's final positions, including that of equity accounts. If a 'price_map' is provided, insert price information in the flattened holdings at the latest date, or at the given date, if one is provided. Only the accounts in 'included_account_types' will be included, and this is always called for Assets and Liabilities only. If left unspecified, holdings from all account types will be included, including Equity, Income and Expenses. Args: entries: A list of directives. included_account_types: A sequence of strings, the account types to include in the output. A reasonable example would be ('Assets', 'Liabilities'). If not specified, include all account types. price_map: A dict of prices, as built by prices.build_price_map(). date: A datetime.date instance, the date at which to price the holdings. If left unspecified, we use the latest price information. Returns: A list of dicts, with the following fields: \"\"\" # Remove the entries inserted by unrealized gains/losses. Those entries do # affect asset accounts, and we don't want them to appear in holdings. # # Note: Perhaps it would make sense to generalize this concept of \"inserted # unrealized gains.\" simple_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or entry.flag != flags.FLAG_UNREALIZED)] # Realize the accounts into a tree (because we want the positions by-account). root_account = realization.realize(simple_entries) # For each account, look at the list of positions and build a list. holdings = [] for real_account in sorted(list(realization.iter_children(root_account)), key=lambda ra: ra.account): if included_account_types: # Skip accounts of invalid types, we only want to reflect the requested # account types, typically assets and liabilities. account_type = account_types.get_account_type(real_account.account) if account_type not in included_account_types: continue for pos in real_account.balance.get_positions(): if pos.cost is not None: # Get price information if we have a price_map. market_value = None if price_map is not None: base_quote = (pos.units.currency, pos.cost.currency) price_date, price_number = prices.get_price(price_map, base_quote, date) if price_number is not None: market_value = pos.units.number * price_number else: price_date, price_number = None, None holding = Holding(real_account.account, pos.units.number, pos.units.currency, pos.cost.number, pos.cost.currency, pos.units.number * pos.cost.number, market_value, price_number, price_date) else: holding = Holding(real_account.account, pos.units.number, pos.units.currency, None, pos.units.currency, pos.units.number, pos.units.number, None, None) holdings.append(holding) return holdings","title":"get_final_holdings()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.holding_to_position","text":"Convert the holding to a position. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_position(holding): \"\"\"Convert the holding to a position. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" return position.Position( amount.Amount(holding.number, holding.currency), (position.Cost(holding.cost_number, holding.cost_currency, None, None) if holding.cost_number else None))","title":"holding_to_position()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.holding_to_posting","text":"Convert the holding to an instance of Posting. Parameters: holding \u2013 An instance of Holding. Returns: An instance of Position. Source code in beancount/ops/holdings.py def holding_to_posting(holding): \"\"\"Convert the holding to an instance of Posting. Args: holding: An instance of Holding. Returns: An instance of Position. \"\"\" position_ = holding_to_position(holding) price = (amount.Amount(holding.price_number, holding.cost_currency) if holding.price_number else None) return data.Posting(holding.account, position_.units, position_.cost, price, None, None)","title":"holding_to_posting()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.reduce_relative","text":"Convert the market and book values of the given list of holdings to relative data. Parameters: holdings \u2013 A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. Source code in beancount/ops/holdings.py def reduce_relative(holdings): \"\"\"Convert the market and book values of the given list of holdings to relative data. Args: holdings: A list of Holding instances. Returns: A list of holdings instances with the absolute value fields replaced by fractions of total portfolio. The new list of holdings is sorted by currency, and the relative fractions are also relative to that currency. \"\"\" # Group holdings by value currency. by_currency = collections.defaultdict(list) ordering = {} for index, holding in enumerate(holdings): ordering.setdefault(holding.cost_currency, index) by_currency[holding.cost_currency].append(holding) fractional_holdings = [] for currency in sorted(by_currency, key=ordering.get): currency_holdings = by_currency[currency] # Compute total market value for that currency. total_book_value = ZERO total_market_value = ZERO for holding in currency_holdings: if holding.book_value: total_book_value += holding.book_value if holding.market_value: total_market_value += holding.market_value # Sort the currency's holdings with decreasing values of market value. currency_holdings.sort( key=lambda holding: holding.market_value or ZERO, reverse=True) # Output new holdings with the relevant values replaced. for holding in currency_holdings: fractional_holdings.append( holding._replace(book_value=(holding.book_value / total_book_value if holding.book_value is not None else None), market_value=(holding.market_value / total_market_value if holding.market_value is not None else None))) return fractional_holdings","title":"reduce_relative()"},{"location":"api_reference/beancount.ops.html#beancount.ops.holdings.scale_holding","text":"Scale the values of a holding. Parameters: holding \u2013 An instance of Holding. scale_factor \u2013 A float or Decimal number. Returns: A scaled copy of the holding. Source code in beancount/ops/holdings.py def scale_holding(holding, scale_factor): \"\"\"Scale the values of a holding. Args: holding: An instance of Holding. scale_factor: A float or Decimal number. Returns: A scaled copy of the holding. \"\"\" return Holding( holding.account, holding.number * scale_factor if holding.number else None, holding.currency, holding.cost_number, holding.cost_currency, holding.book_value * scale_factor if holding.book_value else None, holding.market_value * scale_factor if holding.market_value else None, holding.price_number, holding.price_date)","title":"scale_holding()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes","text":"Given a Beancount ledger, compute time intervals where we hold each commodity. This script computes, for each commodity, which time intervals it is required at. This can then be used to identify a list of dates at which we need to fetch prices in order to properly fill the price database.","title":"lifetimes"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.compress_intervals_days","text":"Compress a list of date pairs to ignore short stretches of unused days. Parameters: intervals \u2013 A list of pairs of datetime.date instances. num_days \u2013 An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_intervals_days(intervals, num_days): \"\"\"Compress a list of date pairs to ignore short stretches of unused days. Args: intervals: A list of pairs of datetime.date instances. num_days: An integer, the number of unused days to require for intervals to be distinct, to allow a gap. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" ignore_interval = datetime.timedelta(days=num_days) new_intervals = [] iter_intervals = iter(intervals) last_begin, last_end = next(iter_intervals) for date_begin, date_end in iter_intervals: if date_begin - last_end < ignore_interval: # Compress. last_end = date_end continue new_intervals.append((last_begin, last_end)) last_begin, last_end = date_begin, date_end new_intervals.append((last_begin, last_end)) return new_intervals","title":"compress_intervals_days()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.compress_lifetimes_days","text":"Compress a lifetimes map to ignore short stretches of unused days. Parameters: lifetimes_map \u2013 A dict of currency intervals as returned by get_commodity_lifetimes. num_days \u2013 An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. Source code in beancount/ops/lifetimes.py def compress_lifetimes_days(lifetimes_map, num_days): \"\"\"Compress a lifetimes map to ignore short stretches of unused days. Args: lifetimes_map: A dict of currency intervals as returned by get_commodity_lifetimes. num_days: An integer, the number of unused days to ignore. Returns: A new dict of lifetimes map where some intervals may have been joined. \"\"\" return {currency_pair: compress_intervals_days(intervals, num_days) for currency_pair, intervals in lifetimes_map.items()}","title":"compress_lifetimes_days()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.get_commodity_lifetimes","text":"Given a list of directives, figure out the life of each commodity. Parameters: entries \u2013 A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day after the last date seen. Source code in beancount/ops/lifetimes.py def get_commodity_lifetimes(entries): \"\"\"Given a list of directives, figure out the life of each commodity. Args: entries: A list of directives. Returns: A dict of (currency, cost-currency) commodity strings to lists of (start, end) datetime.date pairs. The dates are inclusive of the day the commodity was seen; the end/last dates are one day _after_ the last date seen. \"\"\" lifetimes = collections.defaultdict(list) # The current set of active commodities. commodities = set() # The current balances across all accounts. balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Process only transaction entries. if not isinstance(entry, data.Transaction): continue # Update the balance of affected accounts and check locally whether that # triggered a change in the set of commodities. commodities_changed = False for posting in entry.postings: balance = balances[posting.account] commodities_before = balance.currency_pairs() balance.add_position(posting) commodities_after = balance.currency_pairs() if commodities_after != commodities_before: commodities_changed = True # If there was a change in one of the affected account's list of # commodities, recompute the total set globally. This should not # occur very frequently. if commodities_changed: new_commodities = set( itertools.chain(*(inv.currency_pairs() for inv in balances.values()))) if new_commodities != commodities: # The new global set of commodities has changed; update our # the dictionary of intervals. for currency in new_commodities - commodities: lifetimes[currency].append((entry.date, None)) for currency in commodities - new_commodities: lifetime = lifetimes[currency] begin_date, end_date = lifetime.pop(-1) assert end_date is None lifetime.append((begin_date, entry.date + ONEDAY)) # Update our current set. commodities = new_commodities return lifetimes","title":"get_commodity_lifetimes()"},{"location":"api_reference/beancount.ops.html#beancount.ops.lifetimes.required_weekly_prices","text":"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Parameters: lifetimes_map \u2013 A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last \u2013 A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). Source code in beancount/ops/lifetimes.py def required_weekly_prices(lifetimes_map, date_last): \"\"\"Enumerate all the commodities and Fridays where the price is required. Given a map of lifetimes for a set of commodities, enumerate all the Fridays for each commodity where it is active. This can be used to connect to a historical price fetcher routine to fill in missing price entries from an existing ledger. Args: lifetimes_map: A dict of currency to active intervals as returned by get_commodity_lifetimes(). date_last: A datetime.date instance, the last date which we're interested in. Returns: Tuples of (date, currency, cost-currency). \"\"\" results = [] for currency_pair, intervals in lifetimes_map.items(): if currency_pair[1] is None: continue for date_begin, date_end in intervals: # Find first Friday before the minimum date. diff_days = 4 - date_begin.weekday() if diff_days > 1: diff_days -= 7 date = date_begin + datetime.timedelta(days=diff_days) # Iterate over all Fridays. if date_end is None: date_end = date_last while date < date_end: results.append((date, currency_pair[0], currency_pair[1])) date += ONE_WEEK return sorted(results)","title":"required_weekly_prices()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad","text":"Automatic padding of gaps between entries.","title":"pad"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError","text":"PadError(source, message, entry)","title":"PadError"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/pad.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__new__","text":"Create new instance of PadError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.PadError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/pad.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.pad.pad","text":"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Parameters: entries \u2013 A list of directives. options_map \u2013 A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. Source code in beancount/ops/pad.py def pad(entries, options_map): \"\"\"Insert transaction entries for to fulfill a subsequent balance check. Synthesize and insert Transaction entries right after Pad entries in order to fulfill checks in the padded accounts. Returns a new list of entries. Note that this doesn't pad across parent-child relationships, it is a very simple kind of pad. (I have found this to be sufficient in practice, and simpler to implement and understand.) Furthermore, this pads for a single currency only, that is, balance checks are specified only for one currency at a time, and pads will only be inserted for those currencies. Args: entries: A list of directives. options_map: A parser options dict. Returns: A new list of directives, with Pad entries inserted, and a list of new errors produced. \"\"\" pad_errors = [] # Find all the pad entries and group them by account. pads = list(misc_utils.filter_type(entries, data.Pad)) pad_dict = misc_utils.groupby(lambda x: x.account, pads) # Partially realize the postings, so we can iterate them by account. by_account = realization.postings_by_account(entries) # A dict of pad -> list of entries to be inserted. new_entries = {id(pad): [] for pad in pads} # Process each account that has a padding group. for account_, pad_list in sorted(pad_dict.items()): # Last encountered / currency active pad entry. active_pad = None # Gather all the postings for the account and its children. postings = [] is_child = account.parent_matcher(account_) for item_account, item_postings in by_account.items(): if is_child(item_account): postings.extend(item_postings) postings.sort(key=data.posting_sortkey) # A set of currencies already padded so far in this account. padded_lots = set() pad_balance = inventory.Inventory() for entry in postings: assert not isinstance(entry, data.Posting) if isinstance(entry, data.TxnPosting): # This is a transaction; update the running balance for this # account. pad_balance.add_position(entry.posting) elif isinstance(entry, data.Pad): if entry.account == account_: # Mark this newly encountered pad as active and allow all lots # to be padded heretofore. active_pad = entry padded_lots = set() elif isinstance(entry, data.Balance): check_amount = entry.amount # Compare the current balance amount to the expected one from # the check entry. IMPORTANT: You need to understand that this # does not check a single position, but rather checks that the # total amount for a particular currency (which itself is # distinct from the cost). balance_amount = pad_balance.get_currency_units(check_amount.currency) diff_amount = amount.sub(balance_amount, check_amount) # Use the specified tolerance or automatically infer it. tolerance = balance.get_balance_tolerance(entry, options_map) if abs(diff_amount.number) > tolerance: # The check fails; we need to pad. # Pad only if pad entry is active and we haven't already # padded that lot since it was last encountered. if active_pad and (check_amount.currency not in padded_lots): # Note: we decide that it's an error to try to pad # positions at cost; we check here that all the existing # positions with that currency have no cost. positions = [pos for pos in pad_balance.get_positions() if pos.units.currency == check_amount.currency] for position_ in positions: if position_.cost is not None: pad_errors.append( PadError(entry.meta, (\"Attempt to pad an entry with cost for \" \"balance: {}\".format(pad_balance)), active_pad)) # Thus our padding lot is without cost by default. diff_position = position.Position.from_amounts( amount.Amount(check_amount.number - balance_amount.number, check_amount.currency)) # Synthesize a new transaction entry for the difference. narration = ('(Padding inserted for Balance of {} for ' 'difference {})').format(check_amount, diff_position) new_entry = data.Transaction( active_pad.meta.copy(), active_pad.date, flags.FLAG_PADDING, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) new_entry.postings.append( data.Posting(active_pad.account, diff_position.units, diff_position.cost, None, None, None)) neg_diff_position = -diff_position new_entry.postings.append( data.Posting(active_pad.source_account, neg_diff_position.units, neg_diff_position.cost, None, None, None)) # Save it for later insertion after the active pad. new_entries[id(active_pad)].append(new_entry) # Fixup the running balance. pos, _ = pad_balance.add_position(diff_position) if pos is not None and pos.is_negative_at_cost(): raise ValueError( \"Position held at cost goes negative: {}\".format(pos)) # Mark this lot as padded. Further checks should not pad this lot. padded_lots.add(check_amount.currency) # Insert the newly created entries right after the pad entries that created them. padded_entries = [] for entry in entries: padded_entries.append(entry) if isinstance(entry, data.Pad): entry_list = new_entries[id(entry)] if entry_list: padded_entries.extend(entry_list) else: # Generate errors on unused pad entries. pad_errors.append( PadError(entry.meta, \"Unused Pad entry\", entry)) return padded_entries, pad_errors","title":"pad()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize","text":"Summarization of entries. This code is used to summarize a sequence of entries (e.g. during a time period) into a few \"opening balance\" entries. This is when computing a balance sheet for a specific time period: we don't want to see the entries from before some period of time, so we fold them into a single transaction per account that has the sum total amount of that account.","title":"summarize"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.balance_by_account","text":"Sum up the balance per account for all entries strictly before 'date'. Parameters: entries \u2013 A list of directives. date \u2013 An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. Source code in beancount/ops/summarize.py def balance_by_account(entries, date=None): \"\"\"Sum up the balance per account for all entries strictly before 'date'. Args: entries: A list of directives. date: An optional datetime.date instance. If provided, stop accumulating on and after this date. This is useful for summarization before a specific date. Returns: A pair of a dict of account string to instance Inventory (the balance of this account before the given date), and the index in the list of entries where the date was encountered. If all entries are located before the cutoff date, an index one beyond the last entry is returned. \"\"\" balances = collections.defaultdict(inventory.Inventory) for index, entry in enumerate(entries): if date and entry.date >= date: break if isinstance(entry, Transaction): for posting in entry.postings: account_balance = balances[posting.account] # Note: We must allow negative lots at cost, because this may be # used to reduce a filtered list of entries which may not # include the entries necessary to keep units at cost always # above zero. The only summation that is guaranteed to be above # zero is if all the entries are being summed together, no # entries are filtered, at least for a particular account's # postings. account_balance.add_position(posting) else: index = len(entries) return balances, index","title":"balance_by_account()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.cap","text":"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Parameters: entries \u2013 A list of directives. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions \u2013 A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. Source code in beancount/ops/summarize.py def cap(entries, account_types, conversion_currency, account_earnings, account_conversions): \"\"\"Transfer net income to equity and insert a final conversion entry. This is used to move and nullify balances from the income and expense accounts to an equity account in order to draw up a balance sheet with a balance of precisely zero. Args: entries: A list of directives. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the equity account to transfer final balances of the income and expense accounts to. account_conversions: A string, the name of the equity account to use as the source for currency conversions. Returns: A modified list of entries, with the income and expense accounts transferred. \"\"\" # Transfer the balances of income and expense accounts as earnings / net # income. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, None, income_statement_account_pred, account_earnings) # Insert final conversion entries. entries = conversions(entries, account_conversions, conversion_currency, None) return entries","title":"cap()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.cap_opt","text":"Close by getting all the parameters from an options map. See cap() for details. Parameters: entries \u2013 See cap(). options_map \u2013 A parser's option_map. Returns: Same as close(). Source code in beancount/ops/summarize.py def cap_opt(entries, options_map): \"\"\"Close by getting all the parameters from an options map. See cap() for details. Args: entries: See cap(). options_map: A parser's option_map. Returns: Same as close(). \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) conversion_currency = options_map['conversion_currency'] return cap(entries, account_types, conversion_currency, *current_accounts)","title":"cap_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clamp","text":"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Parameters: entries \u2013 A list of directive tuples. begin_date \u2013 A datetime.date instance, the beginning of the period. end_date \u2013 A datetime.date instance, one day beyond the end of the period. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def clamp(entries, begin_date, end_date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Filter entries to include only those during a specified time period. Firstly, this method will transfer all balances for the income and expense accounts occurring before the given period begin date to the 'account_earnings' account (earnings before the period, or \"retained earnings\") and summarize all of the transactions before that date against the 'account_opening' account (usually \"opening balances\"). The resulting income and expense accounts should have no transactions (since their balances have been transferred out and summarization of zero balances should not add any transactions). Secondly, all the entries after the period end date will be truncated and a conversion entry will be added for the resulting transactions that reflect changes occurring between the beginning and end of the exercise period. The resulting balance of all account should be empty. Args: entries: A list of directive tuples. begin_date: A datetime.date instance, the beginning of the period. end_date: A datetime.date instance, one day beyond the end of the period. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) entries = transfer_balances(entries, begin_date, income_statement_account_pred, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, begin_date, account_opening) # Truncate the entries after this. entries = truncate(entries, end_date) # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, end_date) return entries, index","title":"clamp()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clamp_opt","text":"Clamp by getting all the parameters from an options map. See clamp() for details. Parameters: entries \u2013 See clamp(). begin_date \u2013 See clamp(). end_date \u2013 See clamp(). options_map \u2013 A parser's option_map. Returns: Same as clamp(). Source code in beancount/ops/summarize.py def clamp_opt(entries, begin_date, end_date, options_map): \"\"\"Clamp by getting all the parameters from an options map. See clamp() for details. Args: entries: See clamp(). begin_date: See clamp(). end_date: See clamp(). options_map: A parser's option_map. Returns: Same as clamp(). \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return clamp(entries, begin_date, end_date, account_types, conversion_currency, *previous_accounts)","title":"clamp_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clear","text":"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types \u2013 An instance of AccountTypes. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. Source code in beancount/ops/summarize.py def clear(entries, date, account_types, account_earnings): \"\"\"Transfer income and expenses balances at the given date to the equity accounts. This method insert entries to zero out balances on income and expenses accounts by transferring them to an equity account. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. account_types: An instance of AccountTypes. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. Returns: A new list of entries is returned, and the index that points to one before the last original transaction before the transfers. \"\"\" index = len(entries) # Transfer income and expenses before the period to equity. income_statement_account_pred = ( lambda account: is_income_statement_account(account, account_types)) new_entries = transfer_balances(entries, date, income_statement_account_pred, account_earnings) return new_entries, index","title":"clear()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.clear_opt","text":"Convenience function to clear() using an options map. Source code in beancount/ops/summarize.py def clear_opt(entries, date, options_map): \"\"\"Convenience function to clear() using an options map. \"\"\" account_types = options.get_account_types(options_map) current_accounts = options.get_current_accounts(options_map) return clear(entries, date, account_types, current_accounts[0])","title":"clear_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.close","text":"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will Remove all entries which occur after 'date', if given. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. Source code in beancount/ops/summarize.py def close(entries, date, conversion_currency, account_conversions): \"\"\"Truncate entries that occur after a particular date and ensure balance. This method essentially removes entries after a date. It truncates the future. To do so, it will 1. Remove all entries which occur after 'date', if given. 2. Insert conversion transactions at the end of the list of entries to ensure that the total balance of all postings sums up to empty. The result is a list of entries with a total balance of zero, with possibly non-zero balances for the income/expense accounts. To produce a final balance sheet, use transfer() to move the net income to the equity accounts. Args: entries: A list of directive tuples. date: A datetime.date instance, one day beyond the end of the period. This date can be optionally left to None in order to close at the end of the list of entries. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to one beyond the last original transaction that was provided. Further entries may have been inserted to normalize conversions and ensure the total balance sums to zero. \"\"\" # Truncate the entries after the date, if a date has been provided. if date is not None: entries = truncate(entries, date) # Keep an index to the truncated list of entries (before conversions). index = len(entries) # Insert a conversions entry to ensure the total balance of all accounts is # flush zero. entries = conversions(entries, account_conversions, conversion_currency, date) return entries, index","title":"close()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.close_opt","text":"Convenience function to close() using an options map. Source code in beancount/ops/summarize.py def close_opt(entries, date, options_map): \"\"\"Convenience function to close() using an options map. \"\"\" conversion_currency = options_map['conversion_currency'] current_accounts = options.get_current_accounts(options_map) return close(entries, date, conversion_currency, current_accounts[1])","title":"close_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.conversions","text":"Insert a conversion entry at date 'date' at the given account. Parameters: entries \u2013 A list of entries. conversion_account \u2013 A string, the account to book against. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. date \u2013 The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. Source code in beancount/ops/summarize.py def conversions(entries, conversion_account, conversion_currency, date=None): \"\"\"Insert a conversion entry at date 'date' at the given account. Args: entries: A list of entries. conversion_account: A string, the account to book against. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. date: The date before which to insert the conversion entry. The new entry will be inserted as the last entry of the date just previous to this date. Returns: A modified list of entries. \"\"\" # Compute the balance at the given date. conversion_balance = interpolate.compute_entries_balance(entries, date=date) # Early exit if there is nothing to do. conversion_cost_balance = conversion_balance.reduce(convert.get_cost) if conversion_cost_balance.is_empty(): return entries # Calculate the index and the date for the new entry. We want to store it as # the last transaction of the day before. if date is not None: index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) last_date = date - datetime.timedelta(days=1) else: index = len(entries) last_date = entries[-1].date meta = data.new_metadata('', -1) narration = 'Conversion for {}'.format(conversion_balance) conversion_entry = Transaction(meta, last_date, flags.FLAG_CONVERSIONS, None, narration, data.EMPTY_SET, data.EMPTY_SET, []) for position in conversion_cost_balance.get_positions(): # Important note: Set the cost to zero here to maintain the balance # invariant. (This is the only single place we cheat on the balance rule # in the entire system and this is necessary; see documentation on # Conversions.) price = amount.Amount(ZERO, conversion_currency) neg_pos = -position conversion_entry.postings.append( data.Posting(conversion_account, neg_pos.units, neg_pos.cost, price, None, None)) # Make a copy of the list of entries and insert the new transaction into it. new_entries = list(entries) new_entries.insert(index, conversion_entry) return new_entries","title":"conversions()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.create_entries_from_balances","text":"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Parameters: balances \u2013 A dict of account name strings to Inventory instances. date \u2013 A datetime.date object, the date at which to create the transaction. source_account \u2013 A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction \u2013 If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta \u2013 A dict to use as metadata for the transactions. flag \u2013 A string, the flag to use for the transactions. narration_template \u2013 A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. Source code in beancount/ops/summarize.py def create_entries_from_balances(balances, date, source_account, direction, meta, flag, narration_template): \"\"\"\"Create a list of entries from a dict of balances. This method creates a list of new entries to transfer the amounts in the 'balances' dict to/from another account specified in 'source_account'. The balancing posting is created with the equivalent at cost. In other words, if you attempt to balance 10 HOOL {500 USD}, this will synthesize a posting with this position on one leg, and with 5000 USD on the 'source_account' leg. Args: balances: A dict of account name strings to Inventory instances. date: A datetime.date object, the date at which to create the transaction. source_account: A string, the name of the account to pull the balances from. This is the magician's hat to pull the rabbit from. direction: If 'direction' is True, the new entries transfer TO the balances account from the source account; otherwise the new entries transfer FROM the balances into the source account. meta: A dict to use as metadata for the transactions. flag: A string, the flag to use for the transactions. narration_template: A format string for creating the narration. It is formatted with 'account' and 'date' replacement variables. Returns: A list of newly synthesizes Transaction entries. \"\"\" new_entries = [] for account, account_balance in sorted(balances.items()): # Don't create new entries where there is no balance. if account_balance.is_empty(): continue narration = narration_template.format(account=account, date=date) if not direction: account_balance = -account_balance postings = [] new_entry = Transaction( meta, date, flag, None, narration, data.EMPTY_SET, data.EMPTY_SET, postings) for position in account_balance.get_positions(): postings.append(data.Posting(account, position.units, position.cost, None, None, None)) cost = -convert.get_cost(position) postings.append(data.Posting(source_account, cost, None, None, None, None)) new_entries.append(new_entry) return new_entries","title":"create_entries_from_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.get_open_entries","text":"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Parameters: entries \u2013 A list of directives. date \u2013 The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. Source code in beancount/ops/summarize.py def get_open_entries(entries, date): \"\"\"Gather the list of active Open entries at date. This returns the list of Open entries that have not been closed at the given date, in the same order they were observed in the document. Args: entries: A list of directives. date: The date at which to look for an open entry. If not specified, will return the entries still open at the latest date. Returns: A list of Open directives. \"\"\" open_entries = {} for index, entry in enumerate(entries): if date is not None and entry.date >= date: break if isinstance(entry, Open): try: ex_index, ex_entry = open_entries[entry.account] if entry.date < ex_entry.date: open_entries[entry.account] = (index, entry) except KeyError: open_entries[entry.account] = (index, entry) elif isinstance(entry, Close): # If there is no corresponding open, don't raise an error. open_entries.pop(entry.account, None) return [entry for (index, entry) in sorted(open_entries.values())]","title":"get_open_entries()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.open","text":"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will Insert conversion transactions at the given open date, then Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Parameters: entries \u2013 A list of directive tuples. date \u2013 A datetime.date instance, the date at which to do this. account_types \u2013 An instance of AccountTypes. conversion_currency \u2013 A string, the transfer currency to use for zero prices on the conversion entry. account_earnings \u2013 A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening \u2013 A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions \u2013 A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. Source code in beancount/ops/summarize.py def open(entries, date, account_types, conversion_currency, account_earnings, account_opening, account_conversions): \"\"\"Summarize entries before a date and transfer income/expenses to equity. This method essentially prepares a list of directives to contain only transactions that occur after a particular date. It truncates the past. To do so, it will 1. Insert conversion transactions at the given open date, then 2. Insert transactions at that date to move accumulated balances from before that date from the income and expenses accounts to an equity account, and finally 3. It removes all the transactions previous to the date and replaces them by opening balances entries to bring the balances to the same amount. The result is a list of entries for which the income and expense accounts are beginning with a balance of zero, and all other accounts begin with a transaction that brings their balance to the expected amount. All the past has been summarized at that point. An index is returned to the first transaction past the balance opening transactions, so you can keep just those in order to render a balance sheet for only the opening balances. Args: entries: A list of directive tuples. date: A datetime.date instance, the date at which to do this. account_types: An instance of AccountTypes. conversion_currency: A string, the transfer currency to use for zero prices on the conversion entry. account_earnings: A string, the name of the account to transfer previous earnings from the income statement accounts to the balance sheet. account_opening: A string, the name of the account in equity to transfer previous balances from, in order to initialize account balances at the beginning of the period. This is typically called an opening balances account. account_conversions: A string, the name of the equity account to book currency conversions against. Returns: A new list of entries is returned, and the index that points to the first original transaction after the beginning date of the period. This index can be used to generate the opening balances report, which is a balance sheet fed with only the summarized entries. \"\"\" # Insert conversion entries. entries = conversions(entries, account_conversions, conversion_currency, date) # Transfer income and expenses before the period to equity. entries, _ = clear(entries, date, account_types, account_earnings) # Summarize all the previous balances, after transferring the income and # expense balances, so all entries for those accounts before the begin date # should now disappear. entries, index = summarize(entries, date, account_opening) return entries, index","title":"open()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.open_opt","text":"Convenience function to open() using an options map. Source code in beancount/ops/summarize.py def open_opt(entries, date, options_map): \"\"\"Convenience function to open() using an options map. \"\"\" account_types = options.get_account_types(options_map) previous_accounts = options.get_previous_accounts(options_map) conversion_currency = options_map['conversion_currency'] return open(entries, date, account_types, conversion_currency, *previous_accounts)","title":"open_opt()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.summarize","text":"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the cutoff date before which to summarize. account_opening \u2013 A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. Source code in beancount/ops/summarize.py def summarize(entries, date, account_opening): \"\"\"Summarize all entries before a date by replacing then with summarization entries. This function replaces the transactions up to (and not including) the given date with a opening balance transactions, one for each account. It returns new entries, all of the transactions before the given date having been replaced by a few summarization entries, one for each account. Notes: - Open entries are preserved for active accounts. - The last relevant price entry for each (base, quote) pair is preserved. - All other entries before the cutoff date are culled. Args: entries: A list of directives. date: A datetime.date instance, the cutoff date before which to summarize. account_opening: A string, the name of the source account to book summarization entries against. Returns: The function returns a list of new entries and the integer index at which the entries on or after the cutoff date begin. \"\"\" # Compute balances at date. balances, index = balance_by_account(entries, date) # We need to insert the entries with a date previous to subsequent checks, # to maintain ensure the open directives show up before any transaction. summarize_date = date - datetime.timedelta(days=1) # Create summarization / opening balance entries. summarizing_entries = create_entries_from_balances( balances, summarize_date, account_opening, True, data.new_metadata('', 0), flags.FLAG_SUMMARIZE, \"Opening balance for '{account}' (Summarization)\") # Insert the last price entry for each commodity from before the date. price_entries = prices.get_last_price_entries(entries, date) # Gather the list of active open entries at date. open_entries = get_open_entries(entries, date) # Compute entries before the date and preserve the entries after the date. before_entries = sorted(open_entries + price_entries + summarizing_entries, key=data.entry_sortkey) after_entries = entries[index:] # Return a new list of entries and the index that points after the entries # were inserted. return (before_entries + after_entries), len(before_entries)","title":"summarize()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.transfer_balances","text":"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Parameters: entries \u2013 A list of directives. date \u2013 A datetime.date instance, the date at which to make the transfer. account_pred \u2013 A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account \u2013 A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. Source code in beancount/ops/summarize.py def transfer_balances(entries, date, account_pred, transfer_account): \"\"\"Synthesize transactions to transfer balances from some accounts at a given date. For all accounts that match the 'account_pred' predicate, create new entries to transfer the balance at the given date from the account to the transfer account. This is used to transfer balances from income and expenses from a previous period to a \"retained earnings\" account. This is accomplished by creating new entries. Note that inserting transfers breaks any following balance checks that are in the transferred accounts. For this reason, all balance assertion entries following the cutoff date for those accounts are removed from the list in output. Args: entries: A list of directives. date: A datetime.date instance, the date at which to make the transfer. account_pred: A predicate function that, given an account string, returns true if the account is meant to be transferred. transfer_account: A string, the name of the source account to be used on the transfer entries to receive balances at the given date. Returns: A new list of entries, with the new transfer entries added in. \"\"\" # Don't bother doing anything if there are no entries. if not entries: return entries # Compute balances at date. balances, index = balance_by_account(entries, date) # Filter out to keep only the accounts we want. transfer_balances = {account: balance for account, balance in balances.items() if account_pred(account)} # We need to insert the entries at the end of the previous day. if date: transfer_date = date - datetime.timedelta(days=1) else: transfer_date = entries[-1].date # Create transfer entries. transfer_entries = create_entries_from_balances( transfer_balances, transfer_date, transfer_account, False, data.new_metadata('', 0), flags.FLAG_TRANSFER, \"Transfer balance for '{account}' (Transfer balance)\") # Remove balance assertions that occur after a transfer on an account that # has been transferred away; they would break. after_entries = [entry for entry in entries[index:] if not (isinstance(entry, balance.Balance) and entry.account in transfer_balances)] # Split the new entries in a new list. return (entries[:index] + transfer_entries + after_entries)","title":"transfer_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.summarize.truncate","text":"Filter out all the entries at and after date. Returns a new list of entries. Parameters: entries \u2013 A sorted list of directives. date \u2013 A datetime.date instance. Returns: A truncated list of directives. Source code in beancount/ops/summarize.py def truncate(entries, date): \"\"\"Filter out all the entries at and after date. Returns a new list of entries. Args: entries: A sorted list of directives. date: A datetime.date instance. Returns: A truncated list of directives. \"\"\" index = bisect_key.bisect_left_with_key(entries, date, key=lambda entry: entry.date) return entries[:index]","title":"truncate()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation","text":"Validation checks. These checks are intended to be run after all the plugins have transformed the list of entries, just before serving them or generating reports from them. The idea is to ensure a reasonable set of invariants and generate errors if those invariants are violated. They are not sanity checks--user data is subject to constraints which are hopefully detected here and which will result in errors trickled up to the user.","title":"validation"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError","text":"ValidationError(source, message, entry)","title":"ValidationError"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/ops/validation.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__new__","text":"Create new instance of ValidationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.ValidationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/ops/validation.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate","text":"Perform all the standard checks on parsed contents. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. log_timings \u2013 An optional function to use for logging the time of individual operations. extra_validations \u2013 A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate(entries, options_map, log_timings=None, extra_validations=None): \"\"\"Perform all the standard checks on parsed contents. Args: entries: A list of directives. unused_options_map: An options map. log_timings: An optional function to use for logging the time of individual operations. extra_validations: A list of extra validation functions to run after loading this list of entries. Returns: A list of new errors, if any were found. \"\"\" validation_tests = VALIDATIONS if extra_validations: validation_tests += extra_validations # Run various validation routines define above. errors = [] for validation_function in validation_tests: with misc_utils.log_time('function: {}'.format(validation_function.__name__), log_timings, indent=2): new_errors = validation_function(entries, options_map) errors.extend(new_errors) return errors","title":"validate()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_active_accounts","text":"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_active_accounts(entries, unused_options_map): \"\"\"Check that all references to accounts occurs on active accounts. We basically check that references to accounts from all directives other than Open and Close occur at dates the open-close interval of that account. This should be good for all of the directive types where we can extract an account name. Note that this is more strict a check than comparing the dates: we actually check that no references to account are made on the same day before the open directive appears for that account. This is a nice property to have, and is supported by our custom sorting routine that will sort open entries before transaction entries, given the same date. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" error_pairs = [] active_set = set() opened_accounts = set() for entry in entries: if isinstance(entry, data.Open): active_set.add(entry.account) opened_accounts.add(entry.account) elif isinstance(entry, data.Close): active_set.discard(entry.account) else: for account in getters.get_entry_accounts(entry): if account not in active_set: # Allow document and note directives that occur after an # account is closed. if (isinstance(entry, ALLOW_AFTER_CLOSE) and account in opened_accounts): continue # Register an error to be logged later, with an appropriate # message. error_pairs.append((account, entry)) # Refine the error message to disambiguate between the case of an account # that has never been seen and one that was simply not active at the time. errors = [] for account, entry in error_pairs: if account in opened_accounts: message = \"Invalid reference to inactive account '{}'\".format(account) else: message = \"Invalid reference to unknown account '{}'\".format(account) errors.append(ValidationError(entry.meta, message, entry)) return errors","title":"validate_active_accounts()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_check_transaction_balances","text":"Check again that all transaction postings balance, as users may have transformed transactions. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_check_transaction_balances(entries, options_map): \"\"\"Check again that all transaction postings balance, as users may have transformed transactions. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Note: this is a bit slow; we could limit our checks to the original # transactions by using the hash function in the loader. errors = [] for entry in entries: if isinstance(entry, Transaction): # IMPORTANT: This validation is _crucial_ and cannot be skipped. # This is where we actually detect and warn on unbalancing # transactions. This _must_ come after the user routines, because # unbalancing input is legal, as those types of transactions may be # \"fixed up\" by a user-plugin. In other words, we want to allow # users to input unbalancing transactions as long as the final # transactions objects that appear on the stream (after processing # the plugins) are balanced. See {9e6c14b51a59}. # # Detect complete sets of postings that have residual balance; residual = interpolate.compute_residual(entry.postings) tolerances = interpolate.infer_tolerances(entry.postings, options_map) if not residual.is_small(tolerances): errors.append( ValidationError(entry.meta, \"Transaction does not balance: {}\".format(residual), entry)) return errors","title":"validate_check_transaction_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_currency_constraints","text":"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_currency_constraints(entries, options_map): \"\"\"Check the currency constraints from account open declarations. Open directives admit an optional list of currencies that specify the only types of commodities that the running inventory for this account may contain. This function checks that all postings are only made in those commodities. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Get all the open entries with currency constraints. open_map = {entry.account: entry for entry in entries if isinstance(entry, Open) and entry.currencies} errors = [] for entry in entries: if not isinstance(entry, Transaction): continue for posting in entry.postings: # Look up the corresponding account's valid currencies; skip the # check if there are none specified. try: open_entry = open_map[posting.account] valid_currencies = open_entry.currencies if not valid_currencies: continue except KeyError: continue # Perform the check. if posting.units.currency not in valid_currencies: errors.append( ValidationError( entry.meta, \"Invalid currency {} for account '{}'\".format( posting.units.currency, posting.account), entry)) return errors","title":"validate_currency_constraints()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_data_types","text":"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_data_types(entries, options_map): \"\"\"Check that all the data types of the attributes of entries are as expected. Users are provided with a means to filter the list of entries. They're able to write code that manipulates those tuple objects without any type constraints. With discipline, this mostly works, but I know better: check, just to make sure. This routine checks all the data types and assumptions on entries. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] for entry in entries: try: data.sanity_check_types( entry, options_map[\"allow_deprecated_none_for_tags_and_links\"]) except AssertionError as exc: errors.append( ValidationError(entry.meta, \"Invalid data types: {}\".format(exc), entry)) return errors","title":"validate_data_types()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_documents_paths","text":"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_documents_paths(entries, options_map): \"\"\"Check that all filenames in resolved Document entries are absolute filenames. The processing of document entries is assumed to result in absolute paths. Relative paths are resolved at the parsing stage and at point we want to make sure we don't have to do any further processing on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" return [ValidationError(entry.meta, \"Invalid relative path for entry\", entry) for entry in entries if (isinstance(entry, Document) and not path.isabs(entry.filename))]","title":"validate_documents_paths()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_duplicate_balances","text":"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_balances(entries, unused_options_map): \"\"\"Check that balance entries occur only once per day. Because we do not support time, and the declaration order of entries is meant to be kept irrelevant, two balance entries with different amounts should not occur in the file. We do allow two identical balance assertions, however, because this may occur during import. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. balance_entries = {} for entry in entries: if not isinstance(entry, data.Balance): continue key = (entry.account, entry.amount.currency, entry.date) try: previous_entry = balance_entries[key] if entry.amount != previous_entry.amount: errors.append( ValidationError( entry.meta, \"Duplicate balance assertion with different amounts\", entry)) except KeyError: balance_entries[key] = entry return errors","title":"validate_duplicate_balances()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_duplicate_commodities","text":"Check that commodity entries are unique for each commodity. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_duplicate_commodities(entries, unused_options_map): \"\"\"Check that commodity entries are unique for each commodity. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] # Mapping of (account, currency, date) to Balance entry. commodity_entries = {} for entry in entries: if not isinstance(entry, data.Commodity): continue key = entry.currency try: previous_entry = commodity_entries[key] if previous_entry: errors.append( ValidationError( entry.meta, \"Duplicate commodity directives for '{}'\".format(key), entry)) except KeyError: commodity_entries[key] = entry return errors","title":"validate_duplicate_commodities()"},{"location":"api_reference/beancount.ops.html#beancount.ops.validation.validate_open_close","text":"Check constraints on open and close directives themselves. This method checks two kinds of constraints: An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. Close directives may only appears if an open directive has been seen previous (chronologically). The date of close directives must be strictly greater than their corresponding open directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/ops/validation.py def validate_open_close(entries, unused_options_map): \"\"\"Check constraints on open and close directives themselves. This method checks two kinds of constraints: 1. An open or a close directive may only show up once for each account. If a duplicate is detected, an error is generated. 2. Close directives may only appears if an open directive has been seen previous (chronologically). 3. The date of close directives must be strictly greater than their corresponding open directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] open_map = {} close_map = {} for entry in entries: if isinstance(entry, Open): if entry.account in open_map: errors.append( ValidationError( entry.meta, \"Duplicate open directive for {}\".format(entry.account), entry)) else: open_map[entry.account] = entry elif isinstance(entry, Close): if entry.account in close_map: errors.append( ValidationError( entry.meta, \"Duplicate close directive for {}\".format(entry.account), entry)) else: try: open_entry = open_map[entry.account] if entry.date <= open_entry.date: errors.append( ValidationError( entry.meta, \"Internal error: closing date for {} \" \"appears before opening date\".format(entry.account), entry)) except KeyError: errors.append( ValidationError( entry.meta, \"Unopened account {} is being closed\".format(entry.account), entry)) close_map[entry.account] = entry return errors","title":"validate_open_close()"},{"location":"api_reference/beancount.parser.html","text":"beancount.parser \uf0c1 Parser module for beancount input files. beancount.parser.booking \uf0c1 Algorithms for 'booking' inventory, that is, the process of finding a matching lot when reducing the content of an inventory. beancount.parser.booking.BookingError ( tuple ) \uf0c1 BookingError(source, message, entry) beancount.parser.booking.BookingError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking.BookingError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BookingError(source, message, entry) beancount.parser.booking.BookingError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking.book(incomplete_entries, options_map) \uf0c1 Book inventory lots and complete all positions with incomplete numbers. Parameters: incomplete_entries \u2013 A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map \u2013 An options dict as produced by the parser. Returns: A pair of entries \u2013 A list of completed entries with all their postings completed. errors: New errors produced during interpolation. Source code in beancount/parser/booking.py def book(incomplete_entries, options_map): \"\"\"Book inventory lots and complete all positions with incomplete numbers. Args: incomplete_entries: A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map: An options dict as produced by the parser. Returns: A pair of entries: A list of completed entries with all their postings completed. errors: New errors produced during interpolation. \"\"\" # Get the list of booking methods for each account. booking_methods = collections.defaultdict(lambda: options_map[\"booking_method\"]) for entry in incomplete_entries: if isinstance(entry, data.Open) and entry.booking: booking_methods[entry.account] = entry.booking # Do the booking here! entries, booking_errors = booking_full.book(incomplete_entries, options_map, booking_methods) # Check for MISSING elements remaining. missing_errors = validate_missing_eliminated(entries, options_map) return entries, (booking_errors + missing_errors) beancount.parser.booking.validate_inventory_booking(entries, unused_options_map, booking_methods) \uf0c1 Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. booking_methods \u2013 A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_inventory_booking(entries, unused_options_map, booking_methods): \"\"\"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Args: entries: A list of directives. unused_options_map: An options map. booking_methods: A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. \"\"\" errors = [] balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: # Update the balance of each posting on its respective account # without allowing booking to a negative position, and if an error # is encountered, catch it and return it. running_balance = balances[posting.account] position_, _ = running_balance.add_position(posting) # Skip this check if the booking method is set to ignore it. if booking_methods.get(posting.account, None) == data.Booking.NONE: continue # Check if the resulting inventory is mixed, which is not # allowed under the STRICT method. if running_balance.is_mixed(): errors.append( BookingError( entry.meta, (\"Reducing position results in inventory with positive \" \"and negative lots: {}\").format(position_), entry)) return errors beancount.parser.booking.validate_missing_eliminated(entries, unused_options_map) \uf0c1 Validate that all the missing bits of postings have been eliminated. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_missing_eliminated(entries, unused_options_map): \"\"\"Validate that all the missing bits of postings have been eliminated. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of errors. \"\"\" errors = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: units = posting.units cost = posting.cost if (MISSING in (units.number, units.currency) or cost is not None and MISSING in (cost.number, cost.currency, cost.date, cost.label)): errors.append( BookingError(entry.meta, \"Transaction has incomplete elements\", entry)) break return errors beancount.parser.booking_full \uf0c1 Full (new) booking implementation. Problem description: Interpolation and booking feed on each other, that is, numbers filled in from interpolation might affect the booking process, and numbers derived from the booking process may help carry out interpolation that would otherwise be under-defined. Here's an example of interpolation helping the booking process: Assume the ante-inventory of Assets:Investments contains two lots of shares of HOOL, one at 100.00 USD and the other at 101.00 USD and apply this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains -200 USD Interpolation is unambiguously able to back out a cost of 100 USD / HOOL, which would then result in an unambiguous booking result. On the other hand, consider this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains Now the interpolation cannot succeed. If the Assets:Investments account is configured to use the FIFO method, the 10 oldest shares would be selected for the cost, and we could then interpolate the capital gains correctly. First observation: The second case is much more frequent than the first, and the first is easily resolved manually by requiring a particular cost be specified. Moreover, in many cases there isn't just a single lot of shares to be reduced from and figuring out the correct set of shares given a target cost is an underspecified problem. Second observation: Booking can only be achieved for inventory reductions, not for augmentations. Therefore, we should carry out booking on inventory reductions and fail early if reduction is undefined there, and leave inventory augmentations with missing numbers undefined, so that interpolation can fill them in at a later stage. Note that one case we'd like to but may not be able to handle is of a reduction with interpolated price, like this: 2015-09-30 * Assets:Investments -10 HOOL {100.00 # USD} Expenses:Commission 9.95 USD Assets:Cash 990.05 USD Therefore we choose to 1) Carry out booking first, on inventory reductions only, and leave inventory augmentations as they are, possibly undefined. The 'cost' attributed of booked postings are converted from CostSpec to Cost. Augmented postings with missing amounts are left as CostSpec instances in order to allow for interpolation of total vs. per-unit amount. 2) Compute interpolations on the resulting postings. Undefined costs for inventory augmentations may be filled in by interpolations at this stage (if possible). 3) Finally, convert the interpolated CostSpec instances to Cost instances. Improving on this algorithm would require running a loop over the booking and interpolation steps until all numbers are resolved or no more inference can occur. We may consider that for later, as an experimental feature. My hunch is that there are so few cases for which this would be useful that we won't bother improving on the algorithm above. beancount.parser.booking_full.CategorizationError ( tuple ) \uf0c1 CategorizationError(source, message, entry) beancount.parser.booking_full.CategorizationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.CategorizationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CategorizationError(source, message, entry) beancount.parser.booking_full.CategorizationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.InterpolationError ( tuple ) \uf0c1 InterpolationError(source, message, entry) beancount.parser.booking_full.InterpolationError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.InterpolationError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of InterpolationError(source, message, entry) beancount.parser.booking_full.InterpolationError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.MissingType ( Enum ) \uf0c1 The type of missing number. beancount.parser.booking_full.ReductionError ( tuple ) \uf0c1 ReductionError(source, message, entry) beancount.parser.booking_full.ReductionError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.ReductionError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ReductionError(source, message, entry) beancount.parser.booking_full.ReductionError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.Refer ( tuple ) \uf0c1 Refer(index, units_currency, cost_currency, price_currency) beancount.parser.booking_full.Refer.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.Refer.__new__(_cls, index, units_currency, cost_currency, price_currency) special staticmethod \uf0c1 Create new instance of Refer(index, units_currency, cost_currency, price_currency) beancount.parser.booking_full.Refer.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.SelfReduxError ( tuple ) \uf0c1 SelfReduxError(source, message, entry) beancount.parser.booking_full.SelfReduxError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_full.SelfReduxError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of SelfReduxError(source, message, entry) beancount.parser.booking_full.SelfReduxError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_full.book(entries, options_map, methods) \uf0c1 Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. Source code in beancount/parser/booking_full.py def book(entries, options_map, methods): \"\"\"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. \"\"\" entries, errors, _ = _book(entries, options_map, methods) return entries, errors beancount.parser.booking_full.book_reductions(entry, group_postings, balances, methods) \uf0c1 Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Parameters: entry \u2013 An instance of Transaction. This is only used to refer to when logging errors. group_postings \u2013 A list of Posting instances for the group. balances \u2013 A dict of account name to inventory contents. methods \u2013 A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings \u2013 A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. Source code in beancount/parser/booking_full.py def book_reductions(entry, group_postings, balances, methods): \"\"\"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. * For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. * For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Args: entry: An instance of Transaction. This is only used to refer to when logging errors. group_postings: A list of Posting instances for the group. balances: A dict of account name to inventory contents. methods: A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings: A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. \"\"\" errors = [] # A local copy of the balances dictionary which is updated just for the # duration of this function's updates, in order to take into account the # cumulative effect of all the postings inferred here local_balances = {} empty = inventory.Inventory() booked_postings = [] for posting in group_postings: # Process a single posting. units = posting.units costspec = posting.cost account = posting.account # Note: We ensure there is no mutation on 'balances' to keep this # function without side-effects. Note that we may be able to optimize # performance later on by giving up this property. # # Also note that if there is no existing balance, then won't be any lot # reduction because none of the postings will be able to match against # any currencies of the balance. previous_balance = balances.get(account, empty) balance = local_balances.setdefault(account, copy.copy(previous_balance)) # Check if this is a lot held at cost. if costspec is None or units.number is MISSING: # This posting is not held at cost; we do nothing. booked_postings.append(posting) else: # This posting is held at cost; figure out if it's a reduction or an # augmentation. method = methods[account] if (method is not Booking.NONE and balance is not None and balance.is_reduced_by(units)): # This posting is a reduction. # Match the positions. cost_number = compute_cost_number(costspec, units) matches = [] for position in balance: # Skip inventory contents of a different currency. if (units.currency and position.units.currency != units.currency): continue # Skip balance positions not held at cost. if position.cost is None: continue if (cost_number is not None and position.cost.number != cost_number): continue if (isinstance(costspec.currency, str) and position.cost.currency != costspec.currency): continue if (costspec.date and position.cost.date != costspec.date): continue if (costspec.label and position.cost.label != costspec.label): continue matches.append(position) # Check for ambiguous matches. if len(matches) == 0: errors.append( ReductionError(entry.meta, 'No position matches \"{}\" against balance {}'.format( posting, balance), entry)) return [], errors # This is irreconcilable, remove these postings. reduction_postings, matched_postings, ambi_errors = ( booking_method.handle_ambiguous_matches(entry, posting, matches, method)) if ambi_errors: errors.extend(ambi_errors) return [], errors # Add the reductions to the resulting list of booked postings. booked_postings.extend(reduction_postings) # Update the local balance in order to avoid matching against # the same postings twice when processing multiple postings in # the same transaction. Note that we only do this for postings # held at cost because the other postings may need interpolation # in order to be resolved properly. for posting in reduction_postings: balance.add_position(posting) else: # This posting is an augmentation. # # Note that we do not convert the CostSpec instances to Cost # instances, because we want to let the subsequent interpolation # process able to interpolate either the cost per-unit or the # total cost, separately. # Put in the date of the parent Transaction if there is no # explicit date specified on the spec. if costspec.date is None: dated_costspec = costspec._replace(date=entry.date) posting = posting._replace(cost=dated_costspec) # FIXME: Insert unique ids for trade tracking; right now this # creates ambiguous matches errors (and it shouldn't). # # Insert a unique label if there isn't one. # if posting.cost is not None and posting.cost.label is None: # posting = posting._replace( # cost=posting.cost._replace(label=unique_label())) booked_postings.append(posting) return booked_postings, errors beancount.parser.booking_full.categorize_by_currency(entry, balances) \uf0c1 Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. If the currency is explicitly specified, we put the posting in that currency's bucket. If not, we have a few methods left to disambiguate the currency: We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Parameters: postings \u2013 A list of incomplete postings to categorize. balances \u2013 A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains \u2013 index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. Source code in beancount/parser/booking_full.py def categorize_by_currency(entry, balances): \"\"\"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. - First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. - If the currency is explicitly specified, we put the posting in that currency's bucket. - If not, we have a few methods left to disambiguate the currency: 1. We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. 2. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Args: postings: A list of incomplete postings to categorize. balances: A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains: index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. \"\"\" errors = [] groups = collections.defaultdict(list) sortdict = {} auto_postings = [] unknown = [] for index, posting in enumerate(entry.postings): units = posting.units cost = posting.cost price = posting.price # Extract and override the currencies locally. units_currency = (units.currency if units is not MISSING and units is not None else None) cost_currency = (cost.currency if cost is not MISSING and cost is not None else None) price_currency = (price.currency if price is not MISSING and price is not None else None) # First we apply the constraint that cost-currency and price-currency # must match, if there is both a cost and a price. This reduces the # space of possibilities somewhat. if cost_currency is MISSING and isinstance(price_currency, str): cost_currency = price_currency if price_currency is MISSING and isinstance(cost_currency, str): price_currency = cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) if units is MISSING and price_currency is None: # Bucket auto-postings separately from unknown. auto_postings.append(refer) else: # Bucket with what we know so far. currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: # If we need to infer the currency, store in unknown. unknown.append(refer) # We look at the remaining postings... if they are all of a single currency, # the posting must be in that currency too. if unknown and len(unknown) == 1 and len(groups) == 1: (index, units_currency, cost_currency, price_currency) = unknown.pop() other_currency = next(iter(groups.keys())) if price_currency is None and cost_currency is None: # Infer to the units currency. units_currency = other_currency else: # Infer to the cost and price currencies. if price_currency is MISSING: price_currency = other_currency if cost_currency is MISSING: cost_currency = other_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) assert currency is not None sortdict.setdefault(currency, index) groups[currency].append(refer) # Finally, try to resolve all the unknown legs using the inventory contents # of each account. for refer in unknown: (index, units_currency, cost_currency, price_currency) = refer posting = entry.postings[index] balance = balances.get(posting.account, None) if balance is None: balance = inventory.Inventory() if units_currency is MISSING: balance_currencies = balance.currencies() if len(balance_currencies) == 1: units_currency = balance_currencies.pop() if cost_currency is MISSING or price_currency is MISSING: balance_cost_currencies = balance.cost_currencies() if len(balance_cost_currencies) == 1: balance_cost_currency = balance_cost_currencies.pop() if price_currency is MISSING: price_currency = balance_cost_currency if cost_currency is MISSING: cost_currency = balance_cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: errors.append( CategorizationError(posting.meta, \"Failed to categorize posting {}\".format(index + 1), entry)) # Fill in missing units currencies if some remain as missing. This may occur # if we used the cost or price to bucket the currency but the units currency # was missing. for currency, refers in groups.items(): for rindex, refer in enumerate(refers): if refer.units_currency is MISSING: posting = entry.postings[refer.index] balance = balances.get(posting.account, None) if balance is None: continue balance_currencies = balance.currencies() if len(balance_currencies) == 1: refers[rindex] = refer._replace(units_currency=balance_currencies.pop()) # Deal with auto-postings. if len(auto_postings) > 1: refer = auto_postings[-1] posting = entry.postings[refer.index] errors.append( CategorizationError(posting.meta, \"You may not have more than one auto-posting per currency\", entry)) auto_postings = auto_postings[0:1] for refer in auto_postings: for currency in groups.keys(): sortdict.setdefault(currency, refer.index) groups[currency].append(Refer(refer.index, currency, None, None)) # Issue error for all currencies which we could not resolve. for currency, refers in groups.items(): for refer in refers: posting = entry.postings[refer.index] for currency, name in [(refer.units_currency, 'units'), (refer.cost_currency, 'cost'), (refer.price_currency, 'price')]: if currency is MISSING: errors.append(CategorizationError( posting.meta, \"Could not resolve {} currency\".format(name), entry)) sorted_groups = sorted(groups.items(), key=lambda item: sortdict[item[0]]) return sorted_groups, errors beancount.parser.booking_full.compute_cost_number(costspec, units) \uf0c1 Given a CostSpec, return the cost number, if possible to compute. Parameters: costspec \u2013 A parsed instance of CostSpec. units \u2013 An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. Source code in beancount/parser/booking_full.py def compute_cost_number(costspec, units): \"\"\"Given a CostSpec, return the cost number, if possible to compute. Args: costspec: A parsed instance of CostSpec. units: An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. \"\"\" number_per = costspec.number_per number_total = costspec.number_total if MISSING in (number_per, number_total): return None if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total units_number = units.number if number_per is not None: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) elif number_per is None: return None else: unit_cost = number_per return unit_cost beancount.parser.booking_full.convert_costspec_to_cost(posting) \uf0c1 Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. Source code in beancount/parser/booking_full.py def convert_costspec_to_cost(posting): \"\"\"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Args: posting: An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. \"\"\" cost = posting.cost if isinstance(cost, position.CostSpec): if cost is not None: units_number = posting.units.number number_per = cost.number_per number_total = cost.number_total if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total if number_per is not MISSING: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) else: unit_cost = number_per new_cost = Cost(unit_cost, cost.currency, cost.date, cost.label) posting = posting._replace(units=posting.units, cost=new_cost) return posting beancount.parser.booking_full.get_bucket_currency(refer) \uf0c1 Given currency references for a posting, return the bucket currency. Parameters: refer \u2013 An instance of Refer. Returns: A currency string. Source code in beancount/parser/booking_full.py def get_bucket_currency(refer): \"\"\"Given currency references for a posting, return the bucket currency. Args: refer: An instance of Refer. Returns: A currency string. \"\"\" currency = None if isinstance(refer.cost_currency, str): currency = refer.cost_currency elif isinstance(refer.price_currency, str): currency = refer.price_currency elif (refer.cost_currency is None and refer.price_currency is None and isinstance(refer.units_currency, str)): currency = refer.units_currency return currency beancount.parser.booking_full.has_self_reduction(postings, methods) \uf0c1 Return true if the postings potentially reduce each other at cost. Parameters: postings \u2013 A list of postings with uninterpolated CostSpec cost instances. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. Source code in beancount/parser/booking_full.py def has_self_reduction(postings, methods): \"\"\"Return true if the postings potentially reduce each other at cost. Args: postings: A list of postings with uninterpolated CostSpec cost instances. methods: A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. \"\"\" # A mapping of (currency, cost-currency) and sign. cost_changes = {} for posting in postings: cost = posting.cost if cost is None: continue if methods[posting.account] is Booking.NONE: continue key = (posting.account, posting.units.currency) sign = 1 if posting.units.number > ZERO else -1 if cost_changes.setdefault(key, sign) != sign: return True return False beancount.parser.booking_full.interpolate_group(postings, balances, currency, tolerances) \uf0c1 Interpolate missing numbers in the set of postings. Parameters: postings \u2013 A list of Posting instances. balances \u2013 A dict of account to its ante-inventory. currency \u2013 The weight currency of this group, used for reporting errors. tolerances \u2013 A dict of currency to tolerance values. Returns: A tuple of postings \u2013 A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) Source code in beancount/parser/booking_full.py def interpolate_group(postings, balances, currency, tolerances): \"\"\"Interpolate missing numbers in the set of postings. Args: postings: A list of Posting instances. balances: A dict of account to its ante-inventory. currency: The weight currency of this group, used for reporting errors. tolerances: A dict of currency to tolerance values. Returns: A tuple of postings: A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) \"\"\" errors = [] # Figure out which type of amount is missing, by creating a list of # incomplete postings and which type of units is missing. incomplete = [] for index, posting in enumerate(postings): units = posting.units cost = posting.cost price = posting.price # Identify incomplete parts of the Posting components. if units.number is MISSING: incomplete.append((MissingType.UNITS, index)) if isinstance(cost, CostSpec): if cost and cost.number_per is MISSING: incomplete.append((MissingType.COST_PER, index)) if cost and cost.number_total is MISSING: incomplete.append((MissingType.COST_TOTAL, index)) else: # Check that a resolved instance of Cost never needs interpolation. # # Note that in theory we could support the interpolation of regular # per-unit costs in these if we wanted to; but because they're all # reducing postings that have been booked earlier, those never need # to be interpolated. if cost is not None: assert isinstance(cost.number, Decimal), ( \"Internal error: cost has no number: {}\".format(cost)) if price and price.number is MISSING: incomplete.append((MissingType.PRICE, index)) # The replacement posting for the incomplete posting of this group. new_posting = None if len(incomplete) == 0: # If there are no missing numbers, just convert the CostSpec to Cost and # return that. out_postings = [convert_costspec_to_cost(posting) for posting in postings] elif len(incomplete) > 1: # If there is more than a single value to be interpolated, generate an # error and return no postings. _, posting_index = incomplete[0] errors.append(InterpolationError( postings[posting_index].meta, \"Too many missing numbers for currency group '{}'\".format(currency), None)) out_postings = [] else: # If there is a single missing number, calculate it and fill it in here. missing, index = incomplete[0] incomplete_posting = postings[index] # Convert augmenting postings' costs from CostSpec to corresponding Cost # instances, except for the incomplete posting. new_postings = [(posting if posting is incomplete_posting else convert_costspec_to_cost(posting)) for posting in postings] # Compute the balance of the other postings. residual = interpolate.compute_residual(posting for posting in new_postings if posting is not incomplete_posting) assert len(residual) < 2, \"Internal error in grouping postings by currencies.\" if not residual.is_empty(): respos = next(iter(residual)) assert respos.cost is None, ( \"Internal error; cost appears in weight calculation.\") assert respos.units.currency == currency, ( \"Internal error; residual different than currency group.\") weight = -respos.units.number weight_currency = respos.units.currency else: weight = ZERO weight_currency = currency if missing == MissingType.UNITS: units = incomplete_posting.units cost = incomplete_posting.cost if cost: # Handle the special case where we only have total cost. if cost.number_per == ZERO: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer per-unit cost only from total\", None)) return postings, errors, True assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") cost_total = cost.number_total or ZERO units_number = (weight - cost_total) / cost.number_per elif incomplete_posting.price: assert incomplete_posting.price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight / incomplete_posting.price.number else: assert units.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight # Quantize the interpolated units if necessary. units_number = interpolate.quantize_with_tolerance(tolerances, units.currency, units_number) if weight != ZERO: new_pos = Position(Amount(units_number, units.currency), cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_PER: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") if units.number != ZERO: number_per = (weight - (cost.number_total or ZERO)) / units.number new_cost = cost._replace(number_per=number_per) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_TOTAL: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") number_total = (weight - cost.number_per * units.number) new_cost = cost._replace(number_total=number_total) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) elif missing == MissingType.PRICE: units = incomplete_posting.units cost = incomplete_posting.cost if cost is not None: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer price for postings with units held at cost\", None)) return postings, errors, True else: price = incomplete_posting.price assert price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") new_price_number = abs(weight / units.number) new_posting = incomplete_posting._replace(price=Amount(new_price_number, price.currency)) else: assert False, \"Internal error; Invalid missing type.\" # Replace the number in the posting. if new_posting is not None: # Set meta-data on the new posting to indicate it was interpolated. if new_posting.meta is None: new_posting = new_posting._replace(meta={}) new_posting.meta[interpolate.AUTOMATIC_META] = True # Convert augmenting posting costs from CostSpec to a corresponding # Cost instance. new_postings[index] = convert_costspec_to_cost(new_posting) else: del new_postings[index] out_postings = new_postings assert all(not isinstance(posting.cost, CostSpec) for posting in out_postings) # Check that units are non-zero and that no cost remains negative; issue an # error if this is the case. for posting in out_postings: if posting.cost is None: continue # If there is a cost, we don't allow either a cost value of zero, # nor a zero number of units. Note that we allow a price of zero as # the only special case allowed (for conversion entries), but never # for costs. if posting.units.number == ZERO: errors.append(InterpolationError( posting.meta, 'Amount is zero: \"{}\"'.format(posting.units), None)) if posting.cost.number < ZERO: errors.append(InterpolationError( posting.meta, 'Cost is negative: \"{}\"'.format(posting.cost), None)) return out_postings, errors, (new_posting is not None) beancount.parser.booking_full.replace_currencies(postings, refer_groups) \uf0c1 Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Parameters: postings \u2013 A list of Posting instances to replace. refer_groups \u2013 A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. Source code in beancount/parser/booking_full.py def replace_currencies(postings, refer_groups): \"\"\"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Args: postings: A list of Posting instances to replace. refer_groups: A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. \"\"\" new_groups = [] for currency, refers in refer_groups: new_postings = [] for refer in sorted(refers, key=lambda r: r.index): posting = postings[refer.index] units = posting.units if units is MISSING or units is None: posting = posting._replace(units=Amount(MISSING, refer.units_currency)) else: replace = False cost = posting.cost price = posting.price if units.currency is MISSING: units = Amount(units.number, refer.units_currency) replace = True if cost and cost.currency is MISSING: cost = cost._replace(currency=refer.cost_currency) replace = True if price and price.currency is MISSING: price = Amount(price.number, refer.price_currency) replace = True if replace: posting = posting._replace(units=units, cost=cost, price=price) new_postings.append(posting) new_groups.append((currency, new_postings)) return new_groups beancount.parser.booking_full.unique_label() \uf0c1 Return a globally unique label for cost entries. Source code in beancount/parser/booking_full.py def unique_label() -> Text: \"Return a globally unique label for cost entries.\" return str(uuid.uuid4()) beancount.parser.booking_method \uf0c1 Implementations of all the particular booking methods. This code is used by the full booking algorithm. beancount.parser.booking_method.AmbiguousMatchError ( tuple ) \uf0c1 AmbiguousMatchError(source, message, entry) beancount.parser.booking_method.AmbiguousMatchError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_method.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.booking_method.AmbiguousMatchError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of AmbiguousMatchError(source, message, entry) beancount.parser.booking_method.AmbiguousMatchError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/booking_method.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.booking_method.booking_method_AVERAGE(entry, posting, matches) \uf0c1 AVERAGE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_AVERAGE(entry, posting, matches): \"\"\"AVERAGE booking method implementation.\"\"\" booked_reductions = [] booked_matches = [] errors = [AmbiguousMatchError(entry.meta, \"AVERAGE method is not supported\", entry)] return booked_reductions, booked_matches, errors, False # FIXME: Future implementation here. # pylint: disable=unreachable if False: # pylint: disable=using-constant-test # DISABLED - This is the code for AVERAGE, which is currently disabled. # If there is more than a single match we need to ultimately merge the # postings. Also, if the reducing posting provides a specific cost, we # need to update the cost basis as well. Both of these cases are carried # out by removing all the matches and readding them later on. if len(matches) == 1 and ( not isinstance(posting.cost.number_per, Decimal) and not isinstance(posting.cost.number_total, Decimal)): # There is no cost. Just reduce the one leg. This should be the # normal case if we always merge augmentations and the user lets # Beancount deal with the cost. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) insufficient = (match_units.number != posting.units.number) else: # Merge the matching postings to a single one. merged_units = inventory.Inventory() merged_cost = inventory.Inventory() for match in matches: merged_units.add_amount(match.units) merged_cost.add_amount(convert.get_weight(match)) if len(merged_units) != 1 or len(merged_cost) != 1: errors.append( AmbiguousMatchError( entry.meta, 'Cannot merge positions in multiple currencies: {}'.format( ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: if (isinstance(posting.cost.number_per, Decimal) or isinstance(posting.cost.number_total, Decimal)): errors.append( AmbiguousMatchError( entry.meta, \"Explicit cost reductions aren't supported yet: {}\".format( position.to_string(posting)), entry)) else: # Insert postings to remove all the matches. booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost, flag=flags.FLAG_MERGING) for match in matches) units = merged_units[0].units date = matches[0].cost.date ## FIXME: Select which one, ## oldest or latest. cost_units = merged_cost[0].units cost = Cost(cost_units.number/units.number, cost_units.currency, date, None) # Insert a posting to refill those with a replacement match. booked_reductions.append( posting._replace(units=units, cost=cost, flag=flags.FLAG_MERGING)) # Now, match the reducing request against this lot. booked_reductions.append( posting._replace(units=posting.units, cost=cost)) insufficient = abs(posting.units.number) > abs(units.number) beancount.parser.booking_method.booking_method_FIFO(entry, posting, matches) \uf0c1 FIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_FIFO(entry, posting, matches): \"\"\"FIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, False) beancount.parser.booking_method.booking_method_LIFO(entry, posting, matches) \uf0c1 LIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_LIFO(entry, posting, matches): \"\"\"LIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, True) beancount.parser.booking_method.booking_method_NONE(entry, posting, matches) \uf0c1 NONE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_NONE(entry, posting, matches): \"\"\"NONE booking method implementation.\"\"\" # This never needs to match against any existing positions... we # disregard the matches, there's never any error. Note that this never # gets called in practice, we want to treat NONE postings as # augmentations. Default behaviour is to return them with their original # CostSpec, and the augmentation code will handle signaling an error if # there is insufficient detail to carry out the conversion to an # instance of Cost. # Note that it's an interesting question whether a reduction on an # account with NONE method which happens to match a single position # ought to be matched against it. We don't allow it for now. return [posting], [], False beancount.parser.booking_method.booking_method_STRICT(entry, posting, matches) \uf0c1 Strict booking method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. Source code in beancount/parser/booking_method.py def booking_method_STRICT(entry, posting, matches): \"\"\"Strict booking method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. \"\"\" booked_reductions = [] booked_matches = [] errors = [] insufficient = False # In strict mode, we require at most a single matching posting. if len(matches) > 1: # If the total requested to reduce matches the sum of all the # ambiguous postings, match against all of them. sum_matches = sum(p.units.number for p in matches) if sum_matches == -posting.units.number: booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost) for match in matches) else: errors.append( AmbiguousMatchError(entry.meta, 'Ambiguous matches for \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: # Replace the posting's units and cost values. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) booked_matches.append(match) insufficient = (match_units.number != posting.units.number) return booked_reductions, booked_matches, errors, insufficient beancount.parser.booking_method.handle_ambiguous_matches(entry, posting, matches, method) \uf0c1 Handle ambiguous matches by dispatching to a particular method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. Source code in beancount/parser/booking_method.py def handle_ambiguous_matches(entry, posting, matches, method): \"\"\"Handle ambiguous matches by dispatching to a particular method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods: A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. \"\"\" assert isinstance(method, Booking), ( \"Invalid type: {}\".format(method)) assert matches, \"Internal error: Invalid call with no matches\" #method = globals()['booking_method_{}'.format(method.name)] method = _BOOKING_METHODS[method] (booked_reductions, booked_matches, errors, insufficient) = method(entry, posting, matches) if insufficient: errors.append( AmbiguousMatchError(entry.meta, 'Not enough lots to reduce \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) return booked_reductions, booked_matches, errors beancount.parser.cmptest \uf0c1 Support utilities for testing scripts. beancount.parser.cmptest.TestError ( Exception ) \uf0c1 Errors within the test implementation itself. These should never occur. beancount.parser.cmptest.assertEqualEntries(expected_entries, actual_entries, failfunc=, allow_incomplete=False) \uf0c1 Compare two lists of entries exactly and print missing entries verbosely if they occur. Parameters: expected_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries \u2013 Same treatment as expected_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertEqualEntries(expected_entries, actual_entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, \"Missing is missing: {}, {}\".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write(\"Present in expected set and not in actual set:\\n\\n\") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') if actual_missing: oss.write(\"Present in actual set and not in expected set:\\n\\n\") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.assertExcludesEntries(subset_entries, entries, failfunc=, allow_incomplete=False) \uf0c1 Check that subset_entries is not included in entries and print extra entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertExcludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is not included in entries and print extra entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) excludes, extra = compare.excludes_entries(subset_entries, entries) if not excludes: assert extra, \"Extra is empty: {}\".format(extra) oss = io.StringIO() if extra: oss.write(\"Extra from from first/excluded set:\\n\\n\") for entry in extra: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.assertIncludesEntries(subset_entries, entries, failfunc=, allow_incomplete=False) \uf0c1 Check that subset_entries is included in entries and print missing entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertIncludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is included in entries and print missing entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) includes, missing = compare.includes_entries(subset_entries, entries) if not includes: assert missing, \"Missing is empty: {}\".format(missing) oss = io.StringIO() if missing: oss.write(\"Missing from from expected set:\\n\\n\") for entry in missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue()) beancount.parser.cmptest.read_string_or_entries(entries_or_str, allow_incomplete=False) \uf0c1 Read a string of entries or just entries. Parameters: entries_or_str \u2013 Either a list of directives, or a string containing directives. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. Source code in beancount/parser/cmptest.py def read_string_or_entries(entries_or_str, allow_incomplete=False): \"\"\"Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. \"\"\" if isinstance(entries_or_str, str): entries, errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) if allow_incomplete: # Do a simplistic local conversion in order to call the comparison. entries = [_local_booking(entry) for entry in entries] else: # Don't accept incomplete entries either. if any(parser.is_entry_incomplete(entry) for entry in entries): raise TestError(\"Entries in assertions may not use interpolation.\") entries, booking_errors = booking.book(entries, options_map) errors = errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError(\"Unexpected errors in expected: {}\".format(oss.getvalue())) else: assert isinstance(entries_or_str, list), \"Expecting list: {}\".format(entries_or_str) entries = entries_or_str return entries beancount.parser.grammar \uf0c1 Builder for Beancount grammar. beancount.parser.grammar.Builder ( LexBuilder ) \uf0c1 A builder used by the lexer and grammar parser as callbacks to create the data objects corresponding to rules parsed from the input file. beancount.parser.grammar.Builder.amount(self, number, currency) \uf0c1 Process an amount grammar rule. Parameters: number \u2013 a Decimal instance, the number of the amount. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. Source code in beancount/parser/grammar.py def amount(self, number, currency): \"\"\"Process an amount grammar rule. Args: number: a Decimal instance, the number of the amount. currency: a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number, currency) return Amount(number, currency) beancount.parser.grammar.Builder.balance(self, filename, lineno, date, account, amount, tolerance, kvlist) \uf0c1 Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to balance. amount \u2013 The expected amount, to be checked. tolerance \u2013 The tolerance number. kvlist \u2013 a list of KeyValue instances. Returns: A new Balance object. Source code in beancount/parser/grammar.py def balance(self, filename, lineno, date, account, amount, tolerance, kvlist): \"\"\"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to balance. amount: The expected amount, to be checked. tolerance: The tolerance number. kvlist: a list of KeyValue instances. Returns: A new Balance object. \"\"\" diff_amount = None meta = new_metadata(filename, lineno, kvlist) return Balance(meta, date, account, amount, tolerance, diff_amount) beancount.parser.grammar.Builder.build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None) \uf0c1 Build a grammar error and appends it to the list of pending errors. Parameters: filename \u2013 The current filename lineno \u2013 The current line number excvalue \u2013 The exception value, or a str, the message of the error. exc_type \u2013 An exception type, if an exception occurred. exc_traceback \u2013 A traceback object. Source code in beancount/parser/grammar.py def build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None): \"\"\"Build a grammar error and appends it to the list of pending errors. Args: filename: The current filename lineno: The current line number excvalue: The exception value, or a str, the message of the error. exc_type: An exception type, if an exception occurred. exc_traceback: A traceback object. \"\"\" if exc_type is not None: assert not isinstance(exc_value, str) strings = traceback.format_exception_only(exc_type, exc_value) tblist = traceback.extract_tb(exc_traceback) filename, lineno, _, __ = tblist[0] message = '{} ({}:{})'.format(strings[0], filename, lineno) else: message = str(exc_value) meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, message, None)) beancount.parser.grammar.Builder.close(self, filename, lineno, date, account, kvlist) \uf0c1 Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def close(self, filename, lineno, date, account, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Close(meta, date, account) beancount.parser.grammar.Builder.commodity(self, filename, lineno, date, currency, kvlist) \uf0c1 Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. currency \u2013 A string, the commodity being declared. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def commodity(self, filename, lineno, date, currency, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. currency: A string, the commodity being declared. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Commodity(meta, date, currency) beancount.parser.grammar.Builder.compound_amount(self, number_per, number_total, currency) \uf0c1 Process an amount grammar rule. Parameters: number_per \u2013 a Decimal instance, the number of the cost per share. number_total \u2013 a Decimal instance, the number of the cost over all shares. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. Source code in beancount/parser/grammar.py def compound_amount(self, number_per, number_total, currency): \"\"\"Process an amount grammar rule. Args: number_per: a Decimal instance, the number of the cost per share. number_total: a Decimal instance, the number of the cost over all shares. currency: a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number_per, currency) self.dcupdate(number_total, currency) # Note that we are not able to reduce the value to a number per-share # here because we only get the number of units in the full lot spec. return CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.Builder.cost_merge(self, _) \uf0c1 Create a 'merge cost' token. Source code in beancount/parser/grammar.py def cost_merge(self, _): \"\"\"Create a 'merge cost' token.\"\"\" return MERGE_COST beancount.parser.grammar.Builder.cost_spec(self, cost_comp_list, is_total) \uf0c1 Process a cost_spec grammar rule. Parameters: cost_comp_list \u2013 A list of CompoundAmount, a datetime.date, or label ID strings. is_total \u2013 Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". Source code in beancount/parser/grammar.py def cost_spec(self, cost_comp_list, is_total): \"\"\"Process a cost_spec grammar rule. Args: cost_comp_list: A list of CompoundAmount, a datetime.date, or label ID strings. is_total: Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". \"\"\" if not cost_comp_list: return CostSpec(MISSING, None, MISSING, None, None, False) assert isinstance(cost_comp_list, list), ( \"Internal error in parser: {}\".format(cost_comp_list)) compound_cost = None date_ = None label = None merge = None for comp in cost_comp_list: if isinstance(comp, CompoundAmount): if compound_cost is None: compound_cost = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate cost: '{}'.\".format(comp), None)) elif isinstance(comp, date): if date_ is None: date_ = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate date: '{}'.\".format(comp), None)) elif comp is MERGE_COST: if merge is None: merge = True self.errors.append( ParserError(self.get_lexer_location(), \"Cost merging is not supported yet\", None)) else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate merge-cost spec\", None)) else: assert isinstance(comp, str), ( \"Currency component is not string: '{}'\".format(comp)) if label is None: label = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate label: '{}'.\".format(comp), None)) # If there was a cost_comp_list, thus a \"{...}\" cost basis spec, you must # indicate that by creating a CompoundAmount(), always. if compound_cost is None: number_per, number_total, currency = MISSING, None, MISSING else: number_per, number_total, currency = compound_cost if is_total: if number_total is not None: self.errors.append( ParserError( self.get_lexer_location(), (\"Per-unit cost may not be specified using total cost \" \"syntax: '{}'; ignoring per-unit cost\").format(compound_cost), None)) # Ignore per-unit number. number_per = ZERO else: # There's a single number specified; interpret it as a total cost. number_total = number_per number_per = ZERO if merge is None: merge = False return CostSpec(number_per, number_total, currency, date_, label, merge) beancount.parser.grammar.Builder.custom(self, filename, lineno, date, dir_type, custom_values, kvlist) \uf0c1 Process a custom directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. dir_type \u2013 A string, a type for the custom directive being parsed. custom_values \u2013 A list of the various tokens seen on the same line. kvlist \u2013 a list of KeyValue instances. Returns: A new Custom object. Source code in beancount/parser/grammar.py def custom(self, filename, lineno, date, dir_type, custom_values, kvlist): \"\"\"Process a custom directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. dir_type: A string, a type for the custom directive being parsed. custom_values: A list of the various tokens seen on the same line. kvlist: a list of KeyValue instances. Returns: A new Custom object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Custom(meta, date, dir_type, custom_values) beancount.parser.grammar.Builder.custom_value(self, value, dtype=None) \uf0c1 Create a custom value object, along with its type. Parameters: value \u2013 One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. Source code in beancount/parser/grammar.py def custom_value(self, value, dtype=None): \"\"\"Create a custom value object, along with its type. Args: value: One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. \"\"\" if dtype is None: dtype = type(value) return ValueType(value, dtype) beancount.parser.grammar.Builder.dcupdate(self, number, currency) \uf0c1 Update the display context. Source code in beancount/parser/grammar.py def dcupdate(self, number, currency): \"\"\"Update the display context.\"\"\" if isinstance(number, Decimal) and currency and currency is not MISSING: self._dcupdate(number, currency) beancount.parser.grammar.Builder.document(self, filename, lineno, date, account, document_filename, tags_links, kvlist) \uf0c1 Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. account \u2013 an Account instance. document_filename \u2013 a str, the name of the document file. tags_links \u2013 The current TagsLinks accumulator. kvlist \u2013 a list of KeyValue instances. Returns: A new Document object. Source code in beancount/parser/grammar.py def document(self, filename, lineno, date, account, document_filename, tags_links, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. account: an Account instance. document_filename: a str, the name of the document file. tags_links: The current TagsLinks accumulator. kvlist: a list of KeyValue instances. Returns: A new Document object. \"\"\" meta = new_metadata(filename, lineno, kvlist) if not path.isabs(document_filename): document_filename = path.abspath(path.join(path.dirname(filename), document_filename)) tags, links = self.finalize_tags_links(tags_links.tags, tags_links.links) return Document(meta, date, account, document_filename, tags, links) beancount.parser.grammar.Builder.event(self, filename, lineno, date, event_type, description, kvlist) \uf0c1 Process an event directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. event_type \u2013 a str, the name of the event type. description \u2013 a str, the event value, the contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Event object. Source code in beancount/parser/grammar.py def event(self, filename, lineno, date, event_type, description, kvlist): \"\"\"Process an event directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. event_type: a str, the name of the event type. description: a str, the event value, the contents. kvlist: a list of KeyValue instances. Returns: A new Event object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Event(meta, date, event_type, description) beancount.parser.grammar.Builder.finalize(self) \uf0c1 Finalize the parser, check for final errors and return the triple. Returns: A triple of entries \u2013 A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. Source code in beancount/parser/grammar.py def finalize(self): \"\"\"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries: A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. \"\"\" # If the user left some tags unbalanced, issue an error. for tag in self.tags: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Unbalanced pushed tag: '{}'\".format(tag), None)) # If the user left some metadata unpopped, issue an error. for key, value_list in self.meta.items(): meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, ( \"Unbalanced metadata key '{}'; leftover metadata '{}'\").format( key, ', '.join(value_list)), None)) # Weave the commas option in the DisplayContext itself, so it propagates # everywhere it is used automatically. self.dcontext.set_commas(self.options['render_commas']) return (self.get_entries(), self.errors, self.get_options()) beancount.parser.grammar.Builder.finalize_tags_links(self, tags, links) \uf0c1 Finally amend tags and links and return final objects to be inserted. Parameters: tags \u2013 A set of tag strings (warning: this gets mutated in-place). links \u2013 A set of link strings. Returns: A sanitized pair of (tags, links). Source code in beancount/parser/grammar.py def finalize_tags_links(self, tags, links): \"\"\"Finally amend tags and links and return final objects to be inserted. Args: tags: A set of tag strings (warning: this gets mutated in-place). links: A set of link strings. Returns: A sanitized pair of (tags, links). \"\"\" if self.tags: tags.update(self.tags) return (frozenset(tags) if tags else EMPTY_SET, frozenset(links) if links else EMPTY_SET) beancount.parser.grammar.Builder.get_entries(self) \uf0c1 Return the accumulated entries. Returns: A list of sorted directives. Source code in beancount/parser/grammar.py def get_entries(self): \"\"\"Return the accumulated entries. Returns: A list of sorted directives. \"\"\" return sorted(self.entries, key=data.entry_sortkey) beancount.parser.grammar.Builder.get_invalid_account(self) \uf0c1 See base class. Source code in beancount/parser/grammar.py def get_invalid_account(self): \"\"\"See base class.\"\"\" return account.join(self.options['name_equity'], 'InvalidAccountName') beancount.parser.grammar.Builder.get_long_string_maxlines(self) \uf0c1 See base class. Source code in beancount/parser/grammar.py def get_long_string_maxlines(self): \"\"\"See base class.\"\"\" return self.options['long_string_maxlines'] beancount.parser.grammar.Builder.get_options(self) \uf0c1 Return the final options map. Returns: A dict of option names to options. Source code in beancount/parser/grammar.py def get_options(self): \"\"\"Return the final options map. Returns: A dict of option names to options. \"\"\" # Build and store the inferred DisplayContext instance. self.options['dcontext'] = self.dcontext # Add the full list of seen commodities. # # IMPORTANT: This is currently where the list of all commodities seen # from the parser lives. The # beancount.core.getters.get_commodities_map() routine uses this to # automatically generate a full list of directives. An alternative would # be to implement a plugin that enforces the generate of these # post-parsing so that they are always guaranteed to live within the # flow of entries. This would allow us to keep all the data in that list # of entries and to avoid depending on the options to store that output. self.options['commodities'] = self.commodities return self.options beancount.parser.grammar.Builder.handle_list(self, object_list, new_object) \uf0c1 Handle a recursive list grammar rule, generically. Parameters: object_list \u2013 the current list of objects. new_object \u2013 the new object to be added. Returns: The new, updated list of objects. Source code in beancount/parser/grammar.py def handle_list(self, object_list, new_object): \"\"\"Handle a recursive list grammar rule, generically. Args: object_list: the current list of objects. new_object: the new object to be added. Returns: The new, updated list of objects. \"\"\" if object_list is None: object_list = [] if new_object is not None: object_list.append(new_object) return object_list beancount.parser.grammar.Builder.include(self, filename, lineno, include_filename) \uf0c1 Process an include directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. include_name \u2013 A string, the name of the file to include. Source code in beancount/parser/grammar.py def include(self, filename, lineno, include_filename): \"\"\"Process an include directive. Args: filename: current filename. lineno: current line number. include_name: A string, the name of the file to include. \"\"\" self.options['include'].append(include_filename) beancount.parser.grammar.Builder.key_value(self, key, value) \uf0c1 Process a document directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account the document relates to. document_filename \u2013 A str, the name of the document file. Returns: A new KeyValue object. Source code in beancount/parser/grammar.py def key_value(self, key, value): \"\"\"Process a document directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account the document relates to. document_filename: A str, the name of the document file. Returns: A new KeyValue object. \"\"\" return KeyValue(key, value) beancount.parser.grammar.Builder.note(self, filename, lineno, date, account, comment, kvlist) \uf0c1 Process a note directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to attach the note to. comment \u2013 A str, the note's comments contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Note object. Source code in beancount/parser/grammar.py def note(self, filename, lineno, date, account, comment, kvlist): \"\"\"Process a note directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to attach the note to. comment: A str, the note's comments contents. kvlist: a list of KeyValue instances. Returns: A new Note object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Note(meta, date, account, comment) beancount.parser.grammar.Builder.open(self, filename, lineno, date, account, currencies, booking_str, kvlist) \uf0c1 Process an open directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. currencies \u2013 A list of constraint currencies. booking_str \u2013 A string, the booking method, or None if none was specified. kvlist \u2013 a list of KeyValue instances. Returns: A new Open object. Source code in beancount/parser/grammar.py def open(self, filename, lineno, date, account, currencies, booking_str, kvlist): \"\"\"Process an open directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. currencies: A list of constraint currencies. booking_str: A string, the booking method, or None if none was specified. kvlist: a list of KeyValue instances. Returns: A new Open object. \"\"\" meta = new_metadata(filename, lineno, kvlist) error = False if booking_str: try: # Note: Somehow the 'in' membership operator is not defined on Enum. booking = Booking[booking_str] except KeyError: # If the per-account method is invalid, set it to the global # default method and continue. booking = self.options['booking_method'] error = True else: booking = None entry = Open(meta, date, account, currencies, booking) if error: self.errors.append(ParserError(meta, \"Invalid booking method: {}\".format(booking_str), entry)) return entry beancount.parser.grammar.Builder.option(self, filename, lineno, key, value) \uf0c1 Process an option directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. key \u2013 option's key (str) value \u2013 option's value Source code in beancount/parser/grammar.py def option(self, filename, lineno, key, value): \"\"\"Process an option directive. Args: filename: current filename. lineno: current line number. key: option's key (str) value: option's value \"\"\" if key not in self.options: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Invalid option: '{}'\".format(key), None)) elif key in options.READ_ONLY_OPTIONS: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Option '{}' may not be set\".format(key), None)) else: option_descriptor = options.OPTIONS[key] # Issue a warning if the option is deprecated. if option_descriptor.deprecated: assert isinstance(option_descriptor.deprecated, str), \"Internal error.\" meta = new_metadata(filename, lineno) self.errors.append( DeprecatedError(meta, option_descriptor.deprecated, None)) # Rename the option if it has an alias. if option_descriptor.alias: key = option_descriptor.alias option_descriptor = options.OPTIONS[key] # Convert the value, if necessary. if option_descriptor.converter: try: value = option_descriptor.converter(value) except ValueError as exc: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Error for option '{}': {}\".format(key, exc), None)) return option = self.options[key] if isinstance(option, list): # Append to a list of values. option.append(value) elif isinstance(option, dict): # Set to a dict of values. if not (isinstance(value, tuple) and len(value) == 2): self.errors.append( ParserError( meta, \"Error for option '{}': {}\".format(key, value), None)) return dict_key, dict_value = value option[dict_key] = dict_value elif isinstance(option, bool): # Convert to a boolean. if not isinstance(value, bool): value = (value.lower() in {'true', 'on'}) or (value == '1') self.options[key] = value else: # Set the value. self.options[key] = value # Refresh the list of valid account regexps as we go along. if key.startswith('name_'): # Update the set of valid account types. self.account_regexp = valid_account_regexp(self.options) elif key == 'insert_pythonpath': # Insert the PYTHONPATH to this file when and only if you # encounter this option. sys.path.insert(0, path.dirname(filename)) beancount.parser.grammar.Builder.pad(self, filename, lineno, date, account, source_account, kvlist) \uf0c1 Process a pad directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to be padded. source_account \u2013 A string, the account to pad from. kvlist \u2013 a list of KeyValue instances. Returns: A new Pad object. Source code in beancount/parser/grammar.py def pad(self, filename, lineno, date, account, source_account, kvlist): \"\"\"Process a pad directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to be padded. source_account: A string, the account to pad from. kvlist: a list of KeyValue instances. Returns: A new Pad object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Pad(meta, date, account, source_account) beancount.parser.grammar.Builder.pipe_deprecated_error(self, filename, lineno) \uf0c1 Issue a 'Pipe deprecated' error. Parameters: filename \u2013 The current filename lineno \u2013 The current line number Source code in beancount/parser/grammar.py def pipe_deprecated_error(self, filename, lineno): \"\"\"Issue a 'Pipe deprecated' error. Args: filename: The current filename lineno: The current line number \"\"\" if self.options['allow_pipe_separator']: return meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, \"Pipe symbol is deprecated.\", None)) beancount.parser.grammar.Builder.plugin(self, filename, lineno, plugin_name, plugin_config) \uf0c1 Process a plugin directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. plugin_name \u2013 A string, the name of the plugin module to import. plugin_config \u2013 A string or None, an optional configuration string to pass in to the plugin module. Source code in beancount/parser/grammar.py def plugin(self, filename, lineno, plugin_name, plugin_config): \"\"\"Process a plugin directive. Args: filename: current filename. lineno: current line number. plugin_name: A string, the name of the plugin module to import. plugin_config: A string or None, an optional configuration string to pass in to the plugin module. \"\"\" self.options['plugin'].append((plugin_name, plugin_config)) beancount.parser.grammar.Builder.popmeta(self, key) \uf0c1 Removed a key off the current set of stacks. Parameters: key \u2013 A string, a key to be removed from the meta dict. Source code in beancount/parser/grammar.py def popmeta(self, key): \"\"\"Removed a key off the current set of stacks. Args: key: A string, a key to be removed from the meta dict. \"\"\" try: if key not in self.meta: raise IndexError value_list = self.meta[key] value_list.pop(-1) if not value_list: self.meta.pop(key) except IndexError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent metadata key: '{}'\".format(key), None)) beancount.parser.grammar.Builder.poptag(self, tag) \uf0c1 Pop a tag off the current set of stacks. Parameters: tag \u2013 A string, a tag to be removed from the current set of tags. Source code in beancount/parser/grammar.py def poptag(self, tag): \"\"\"Pop a tag off the current set of stacks. Args: tag: A string, a tag to be removed from the current set of tags. \"\"\" try: self.tags.remove(tag) except ValueError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent tag: '{}'\".format(tag), None)) beancount.parser.grammar.Builder.posting(self, filename, lineno, account, units, cost, price, istotal, flag) \uf0c1 Process a posting grammar rule. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. account \u2013 A string, the account of the posting. units \u2013 An instance of Amount for the units. cost \u2013 An instance of CostSpec for the cost. price \u2013 Either None, or an instance of Amount that is the cost of the position. istotal \u2013 A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag \u2013 A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. Source code in beancount/parser/grammar.py def posting(self, filename, lineno, account, units, cost, price, istotal, flag): \"\"\"Process a posting grammar rule. Args: filename: the current filename. lineno: the current line number. account: A string, the account of the posting. units: An instance of Amount for the units. cost: An instance of CostSpec for the cost. price: Either None, or an instance of Amount that is the cost of the position. istotal: A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag: A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. \"\"\" meta = new_metadata(filename, lineno) # Prices may not be negative. if price and isinstance(price.number, Decimal) and price.number < ZERO: self.errors.append( ParserError(meta, ( \"Negative prices are not allowed: {} \" \"(see http://furius.ca/beancount/doc/bug-negative-prices \" \"for workaround)\" ).format(price), None)) # Fix it and continue. price = Amount(abs(price.number), price.currency) # If the price is specified for the entire amount, compute the effective # price here and forget about that detail of the input syntax. if istotal: if units.number == ZERO: number = ZERO else: number = price.number if number is not MISSING: number = number/abs(units.number) price = Amount(number, price.currency) # Note: Allow zero prices because we need them for round-trips for # conversion entries. # # if price is not None and price.number == ZERO: # self.errors.append( # ParserError(meta, \"Price is zero: {}\".format(price), None)) # If both cost and price are specified, the currencies must match, or # that is an error. if (cost is not None and price is not None and isinstance(cost.currency, str) and isinstance(price.currency, str) and cost.currency != price.currency): self.errors.append( ParserError(meta, \"Cost and price currencies must match: {} != {}\".format( cost.currency, price.currency), None)) return Posting(account, units, cost, price, chr(flag) if flag else None, meta) beancount.parser.grammar.Builder.price(self, filename, lineno, date, currency, amount, kvlist) \uf0c1 Process a price directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. currency \u2013 the currency to be priced. amount \u2013 an instance of Amount, that is the price of the currency. kvlist \u2013 a list of KeyValue instances. Returns: A new Price object. Source code in beancount/parser/grammar.py def price(self, filename, lineno, date, currency, amount, kvlist): \"\"\"Process a price directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. currency: the currency to be priced. amount: an instance of Amount, that is the price of the currency. kvlist: a list of KeyValue instances. Returns: A new Price object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Price(meta, date, currency, amount) beancount.parser.grammar.Builder.pushmeta(self, key, value) \uf0c1 Set a metadata field on the current key-value pairs to be added to transactions. Parameters: key_value \u2013 A KeyValue instance, to be added to the dict of metadata. Source code in beancount/parser/grammar.py def pushmeta(self, key, value): \"\"\"Set a metadata field on the current key-value pairs to be added to transactions. Args: key_value: A KeyValue instance, to be added to the dict of metadata. \"\"\" self.meta[key].append(value) beancount.parser.grammar.Builder.pushtag(self, tag) \uf0c1 Push a tag on the current set of tags. Note that this does not need to be stack ordered. Parameters: tag \u2013 A string, a tag to be added. Source code in beancount/parser/grammar.py def pushtag(self, tag): \"\"\"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Args: tag: A string, a tag to be added. \"\"\" self.tags.append(tag) beancount.parser.grammar.Builder.query(self, filename, lineno, date, query_name, query_string, kvlist) \uf0c1 Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. query_name \u2013 a str, the name of the query. query_string \u2013 a str, the SQL query itself. kvlist \u2013 a list of KeyValue instances. Returns: A new Query object. Source code in beancount/parser/grammar.py def query(self, filename, lineno, date, query_name, query_string, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. query_name: a str, the name of the query. query_string: a str, the SQL query itself. kvlist: a list of KeyValue instances. Returns: A new Query object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Query(meta, date, query_name, query_string) beancount.parser.grammar.Builder.store_result(self, entries) \uf0c1 Start rule stores the final result here. Parameters: entries \u2013 A list of entries to store. Source code in beancount/parser/grammar.py def store_result(self, entries): \"\"\"Start rule stores the final result here. Args: entries: A list of entries to store. \"\"\" if entries: self.entries = entries beancount.parser.grammar.Builder.tag_link_LINK(self, tags_links, link) \uf0c1 Add a link to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. link \u2013 A string, the new link to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_LINK(self, tags_links, link): \"\"\"Add a link to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. link: A string, the new link to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.links.add(link) return tags_links beancount.parser.grammar.Builder.tag_link_STRING(self, tags_links, string) \uf0c1 Add a string to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. string \u2013 A string, the new string to insert in the list. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_STRING(self, tags_links, string): \"\"\"Add a string to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. string: A string, the new string to insert in the list. Returns: An updated TagsLinks instance. \"\"\" tags_links.strings.append(string) return tags_links beancount.parser.grammar.Builder.tag_link_TAG(self, tags_links, tag) \uf0c1 Add a tag to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. tag \u2013 A string, the new tag to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_TAG(self, tags_links, tag): \"\"\"Add a tag to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. tag: A string, the new tag to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.tags.add(tag) return tags_links beancount.parser.grammar.Builder.tag_link_new(self, _) \uf0c1 Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. Source code in beancount/parser/grammar.py def tag_link_new(self, _): \"\"\"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. \"\"\" return TagsLinks(set(), set()) beancount.parser.grammar.Builder.transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list) \uf0c1 Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. flag \u2013 a str, one-character, the flag associated with this transaction. txn_strings \u2013 A list of strings, possibly empty, possibly longer. tags_links \u2013 A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list \u2013 a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. Source code in beancount/parser/grammar.py def transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list): \"\"\"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Args: filename: the current filename. lineno: the current line number. date: a datetime object. flag: a str, one-character, the flag associated with this transaction. txn_strings: A list of strings, possibly empty, possibly longer. tags_links: A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list: a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. \"\"\" meta = new_metadata(filename, lineno) # Separate postings and key-values. explicit_meta = {} postings = [] tags, links = tags_links.tags, tags_links.links if posting_or_kv_list: last_posting = None for posting_or_kv in posting_or_kv_list: if isinstance(posting_or_kv, Posting): postings.append(posting_or_kv) last_posting = posting_or_kv elif isinstance(posting_or_kv, TagsLinks): if postings: self.errors.append(ParserError( meta, \"Tags or links not allowed after first \" + \"Posting: {}\".format(posting_or_kv), None)) else: tags.update(posting_or_kv.tags) links.update(posting_or_kv.links) else: if last_posting is None: value = explicit_meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate metadata field on entry: {}\".format( posting_or_kv), None)) else: if last_posting.meta is None: last_posting = last_posting._replace(meta={}) postings.pop(-1) postings.append(last_posting) value = last_posting.meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate posting metadata field: {}\".format( posting_or_kv), None)) # Freeze the tags & links or set to default empty values. tags, links = self.finalize_tags_links(tags, links) # Initialize the metadata fields from the set of active values. if self.meta: for key, value_list in self.meta.items(): meta[key] = value_list[-1] # Add on explicitly defined values. if explicit_meta: meta.update(explicit_meta) # Unpack the transaction fields. payee_narration = self.unpack_txn_strings(txn_strings, meta) if payee_narration is None: return None payee, narration = payee_narration # We now allow a single posting when its balance is zero, so we # commented out the check below. If a transaction has a single posting # with a non-zero balance, it'll get caught below in the booking code. # # # Detect when a transaction does not have at least two legs. # if postings is None or len(postings) < 2: # self.errors.append( # ParserError(meta, # \"Transaction with only one posting: {}\".format(postings), # None)) # return None # If there are no postings, make sure we insert a list object. if postings is None: postings = [] # Create the transaction. return Transaction(meta, date, chr(flag), payee, narration, tags, links, postings) beancount.parser.grammar.Builder.unpack_txn_strings(self, txn_strings, meta) \uf0c1 Unpack a tags_links accumulator to its payee and narration fields. Parameters: txn_strings \u2013 A list of strings. meta \u2013 A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. Source code in beancount/parser/grammar.py def unpack_txn_strings(self, txn_strings, meta): \"\"\"Unpack a tags_links accumulator to its payee and narration fields. Args: txn_strings: A list of strings. meta: A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. \"\"\" num_strings = 0 if txn_strings is None else len(txn_strings) if num_strings == 1: payee, narration = None, txn_strings[0] elif num_strings == 2: payee, narration = txn_strings elif num_strings == 0: payee, narration = None, \"\" else: self.errors.append( ParserError(meta, \"Too many strings on transaction description: {}\".format( txn_strings), None)) return None return payee, narration beancount.parser.grammar.CompoundAmount ( tuple ) \uf0c1 CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.CompoundAmount.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.CompoundAmount.__new__(_cls, number_per, number_total, currency) special staticmethod \uf0c1 Create new instance of CompoundAmount(number_per, number_total, currency) beancount.parser.grammar.CompoundAmount.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.DeprecatedError ( tuple ) \uf0c1 DeprecatedError(source, message, entry) beancount.parser.grammar.DeprecatedError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.DeprecatedError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of DeprecatedError(source, message, entry) beancount.parser.grammar.DeprecatedError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.KeyValue ( tuple ) \uf0c1 KeyValue(key, value) beancount.parser.grammar.KeyValue.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.KeyValue.__new__(_cls, key, value) special staticmethod \uf0c1 Create new instance of KeyValue(key, value) beancount.parser.grammar.KeyValue.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ParserError ( tuple ) \uf0c1 ParserError(source, message, entry) beancount.parser.grammar.ParserError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ParserError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ParserError(source, message, entry) beancount.parser.grammar.ParserError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ParserSyntaxError ( tuple ) \uf0c1 ParserSyntaxError(source, message, entry) beancount.parser.grammar.ParserSyntaxError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ParserSyntaxError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ParserSyntaxError(source, message, entry) beancount.parser.grammar.ParserSyntaxError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.TagsLinks ( tuple ) \uf0c1 TagsLinks(tags, links) beancount.parser.grammar.TagsLinks.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.TagsLinks.__new__(_cls, tags, links) special staticmethod \uf0c1 Create new instance of TagsLinks(tags, links) beancount.parser.grammar.TagsLinks.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.ValueType ( tuple ) \uf0c1 ValueType(value, dtype) beancount.parser.grammar.ValueType.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.grammar.ValueType.__new__(_cls, value, dtype) special staticmethod \uf0c1 Create new instance of ValueType(value, dtype) beancount.parser.grammar.ValueType.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.grammar.valid_account_regexp(options) \uf0c1 Build a regexp to validate account names from the options. Parameters: options \u2013 A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. Source code in beancount/parser/grammar.py def valid_account_regexp(options): \"\"\"Build a regexp to validate account names from the options. Args: options: A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. \"\"\" names = map(options.__getitem__, ('name_assets', 'name_liabilities', 'name_equity', 'name_income', 'name_expenses')) # Replace the first term of the account regular expression with the specific # names allowed under the options configuration. This code is kept in sync # with {5672c7270e1e}. return re.compile(\"(?:{})(?:{}{})+\".format('|'.join(names), account.sep, account.ACC_COMP_NAME_RE)) beancount.parser.hashsrc \uf0c1 Compute a hash of the source files in order to warn when the source goes out of date. beancount.parser.hashsrc.check_parser_source_files() \uf0c1 Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). Source code in beancount/parser/hashsrc.py def check_parser_source_files(): \"\"\"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). \"\"\" parser_source_hash = hash_parser_source_files() if parser_source_hash is None: return # pylint: disable=import-outside-toplevel from . import _parser if _parser.SOURCE_HASH and _parser.SOURCE_HASH != parser_source_hash: warnings.warn( (\"The Beancount parser C extension module is out-of-date ('{}' != '{}'). \" \"You need to rebuild.\").format(_parser.SOURCE_HASH, parser_source_hash)) beancount.parser.hashsrc.hash_parser_source_files() \uf0c1 Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. Source code in beancount/parser/hashsrc.py def hash_parser_source_files(): \"\"\"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. \"\"\" md5 = hashlib.md5() for filename in PARSER_SOURCE_FILES: fullname = path.join(path.dirname(__file__), filename) if not path.exists(fullname): return None with open(fullname, 'rb') as file: md5.update(file.read()) # Note: Prepend a character in front of the hash because under Windows MSDEV # removes escapes, and if the hash starts with a number it fails to # recognize this is a string. A small compromise for portability. return md5.hexdigest() beancount.parser.lexer \uf0c1 Beancount syntax lexer. beancount.parser.lexer.LexBuilder \uf0c1 A builder used only for building lexer objects. Attributes: Name Type Description long_string_maxlines_default Number of lines for a string to trigger a warning. This is meant to help users detecting dangling quotes in their source. beancount.parser.lexer.LexBuilder.ACCOUNT(self, account_name) \uf0c1 Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Parameters: account_name \u2013 a str, the valid name of an account. Returns: A string, the name of the account. Source code in beancount/parser/lexer.py def ACCOUNT(self, account_name): \"\"\"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Args: account_name: a str, the valid name of an account. Returns: A string, the name of the account. \"\"\" # Check account name validity. if not self.account_regexp.match(account_name): raise ValueError(\"Invalid account name: {}\".format(account_name)) # Reuse (intern) account strings as much as possible. This potentially # reduces memory usage a fair bit, because these strings are repeated # liberally. return self.accounts.setdefault(account_name, account_name) beancount.parser.lexer.LexBuilder.CURRENCY(self, currency_name) \uf0c1 Process a CURRENCY token. Parameters: currency_name \u2013 the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. Source code in beancount/parser/lexer.py def CURRENCY(self, currency_name): \"\"\"Process a CURRENCY token. Args: currency_name: the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. \"\"\" self.commodities.add(currency_name) return currency_name beancount.parser.lexer.LexBuilder.DATE(self, year, month, day) \uf0c1 Process a DATE token. Parameters: year \u2013 integer year. month \u2013 integer month. day \u2013 integer day Returns: A new datetime object. Source code in beancount/parser/lexer.py def DATE(self, year, month, day): \"\"\"Process a DATE token. Args: year: integer year. month: integer month. day: integer day Returns: A new datetime object. \"\"\" return datetime.date(year, month, day) beancount.parser.lexer.LexBuilder.KEY(self, ident) \uf0c1 Process an identifier token. Parameters: ident \u2013 a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def KEY(self, ident): \"\"\"Process an identifier token. Args: ident: a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return ident beancount.parser.lexer.LexBuilder.LINK(self, link) \uf0c1 Process a LINK token. Parameters: link \u2013 a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def LINK(self, link): \"\"\"Process a LINK token. Args: link: a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return link beancount.parser.lexer.LexBuilder.NUMBER(self, number) \uf0c1 Process a NUMBER token. Convert into Decimal. Parameters: number \u2013 a str, the number to be converted. Returns: A Decimal instance built of the number string. Source code in beancount/parser/lexer.py def NUMBER(self, number): \"\"\"Process a NUMBER token. Convert into Decimal. Args: number: a str, the number to be converted. Returns: A Decimal instance built of the number string. \"\"\" # Note: We don't use D() for efficiency here. # The lexer will only yield valid number strings. if ',' in number: # Extract the integer part and check the commas match the # locale-aware formatted version. This match = re.match(r\"([\\d,]*)(\\.\\d*)?$\", number) if not match: # This path is never taken because the lexer will parse a comma # in the fractional part as two NUMBERs with a COMMA token in # between. self.errors.append( LexerError(self.get_lexer_location(), \"Invalid number format: '{}'\".format(number), None)) else: int_string, float_string = match.groups() reformatted_number = r\"{:,.0f}\".format(int(int_string.replace(\",\", \"\"))) if int_string != reformatted_number: self.errors.append( LexerError(self.get_lexer_location(), \"Invalid commas: '{}'\".format(number), None)) number = number.replace(',', '') return Decimal(number) beancount.parser.lexer.LexBuilder.STRING(self, string) \uf0c1 Process a STRING token. Parameters: string \u2013 the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. Source code in beancount/parser/lexer.py def STRING(self, string): \"\"\"Process a STRING token. Args: string: the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. \"\"\" # If a multiline string, warm over a certain number of lines. if '\\n' in string: num_lines = string.count('\\n') + 1 if num_lines > self.long_string_maxlines_default: # This is just a warning; accept the string anyhow. self.errors.append( LexerError( self.get_lexer_location(), \"String too long ({} lines); possible error\".format(num_lines), None)) return string beancount.parser.lexer.LexBuilder.TAG(self, tag) \uf0c1 Process a TAG token. Parameters: tag \u2013 a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. Source code in beancount/parser/lexer.py def TAG(self, tag): \"\"\"Process a TAG token. Args: tag: a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. \"\"\" return tag beancount.parser.lexer.LexBuilder.build_lexer_error(self, message, exc_type=None) \uf0c1 Build a lexer error and appends it to the list of pending errors. Parameters: message \u2013 The message of the error. exc_type \u2013 An exception type, if an exception occurred. Source code in beancount/parser/lexer.py def build_lexer_error(self, message, exc_type=None): # {0e31aeca3363} \"\"\"Build a lexer error and appends it to the list of pending errors. Args: message: The message of the error. exc_type: An exception type, if an exception occurred. \"\"\" if not isinstance(message, str): message = str(message) if exc_type is not None: message = '{}: {}'.format(exc_type.__name__, message) self.errors.append( LexerError(self.get_lexer_location(), message, None)) beancount.parser.lexer.LexBuilder.get_invalid_account(self) \uf0c1 Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. Source code in beancount/parser/lexer.py def get_invalid_account(self): \"\"\"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. \"\"\" return 'Equity:InvalidAccountName' beancount.parser.lexer.LexerError ( tuple ) \uf0c1 LexerError(source, message, entry) beancount.parser.lexer.LexerError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/lexer.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.lexer.LexerError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LexerError(source, message, entry) beancount.parser.lexer.LexerError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/lexer.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.lexer.lex_iter(file, builder=None, encoding=None) \uf0c1 An iterator that yields all the tokens in the given file. Parameters: file \u2013 A string, the filename to run the lexer on, or a file object. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). Source code in beancount/parser/lexer.py def lex_iter(file, builder=None, encoding=None): \"\"\"An iterator that yields all the tokens in the given file. Args: file: A string, the filename to run the lexer on, or a file object. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). \"\"\" if isinstance(file, str): filename = file else: filename = file.name if builder is None: builder = LexBuilder() _parser.lexer_initialize(filename, builder, encoding) try: while 1: token_tuple = _parser.lexer_next() if token_tuple is None: break yield token_tuple finally: _parser.lexer_finalize() beancount.parser.lexer.lex_iter_string(string, builder=None, encoding=None) \uf0c1 Parse an input string and print the tokens to an output file. Parameters: input_string \u2013 a str or bytes, the contents of the ledger to be parsed. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. Source code in beancount/parser/lexer.py def lex_iter_string(string, builder=None, encoding=None): \"\"\"Parse an input string and print the tokens to an output file. Args: input_string: a str or bytes, the contents of the ledger to be parsed. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. \"\"\" tmp_file = tempfile.NamedTemporaryFile('w' if isinstance(string, str) else 'wb') tmp_file.write(string) tmp_file.flush() # Note: We pass in the file object in order to keep it alive during parsing. return lex_iter(tmp_file, builder, encoding) beancount.parser.options \uf0c1 Declaration of options and their default values. beancount.parser.options.OptDesc ( tuple ) \uf0c1 OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.OptDesc.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.options.OptDesc.__new__(_cls, name, default_value, example_value, converter, deprecated, alias) special staticmethod \uf0c1 Create new instance of OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.OptDesc.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.options.OptGroup ( tuple ) \uf0c1 OptGroup(description, options) beancount.parser.options.OptGroup.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.parser.options.OptGroup.__new__(_cls, description, options) special staticmethod \uf0c1 Create new instance of OptGroup(description, options) beancount.parser.options.OptGroup.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.parser.options.Opt(name, default_value, example_value=, converter=None, deprecated=False, alias=None) \uf0c1 Alternative constructor for OptDesc, with default values. Parameters: name \u2013 See OptDesc. default_value \u2013 See OptDesc. example_value \u2013 See OptDesc. converter \u2013 See OptDesc. deprecated \u2013 See OptDesc. alias \u2013 See OptDesc. Returns: An instance of OptDesc. Source code in beancount/parser/options.py def Opt(name, default_value, example_value=UNSET, converter=None, deprecated=False, alias=None): \"\"\"Alternative constructor for OptDesc, with default values. Args: name: See OptDesc. default_value: See OptDesc. example_value: See OptDesc. converter: See OptDesc. deprecated: See OptDesc. alias: See OptDesc. Returns: An instance of OptDesc. \"\"\" if example_value is UNSET: example_value = default_value return OptDesc(name, default_value, example_value, converter, deprecated, alias) beancount.parser.options.get_account_types(options) \uf0c1 Extract the account type names from the parser's options. Parameters: options \u2013 a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. Source code in beancount/parser/options.py def get_account_types(options): \"\"\"Extract the account type names from the parser's options. Args: options: a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. \"\"\" return account_types.AccountTypes( *[options[key] for key in (\"name_assets\", \"name_liabilities\", \"name_equity\", \"name_income\", \"name_expenses\")]) beancount.parser.options.get_current_accounts(options) \uf0c1 Return account names for the current earnings and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. Source code in beancount/parser/options.py def get_current_accounts(options): \"\"\"Return account names for the current earnings and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. \"\"\" equity = options['name_equity'] account_current_earnings = account.join(equity, options['account_current_earnings']) account_current_conversions = account.join(equity, options['account_current_conversions']) return (account_current_earnings, account_current_conversions) beancount.parser.options.get_previous_accounts(options) \uf0c1 Return account names for the previous earnings, balances and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. Source code in beancount/parser/options.py def get_previous_accounts(options): \"\"\"Return account names for the previous earnings, balances and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. \"\"\" equity = options['name_equity'] account_previous_earnings = account.join(equity, options['account_previous_earnings']) account_previous_balances = account.join(equity, options['account_previous_balances']) account_previous_conversions = account.join(equity, options['account_previous_conversions']) return (account_previous_earnings, account_previous_balances, account_previous_conversions) beancount.parser.options.list_options() \uf0c1 Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. Source code in beancount/parser/options.py def list_options(): \"\"\"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. \"\"\" oss = io.StringIO() for group in PUBLIC_OPTION_GROUPS: for desc in group.options: oss.write('option \"{}\" \"{}\"\\n'.format(desc.name, desc.example_value)) if desc.deprecated: oss.write(textwrap.fill( \"THIS OPTION IS DEPRECATED: {}\".format(desc.deprecated), initial_indent=\" \", subsequent_indent=\" \")) oss.write('\\n\\n') description = ' '.join(line.strip() for line in group.description.strip().splitlines()) oss.write(textwrap.fill(description, initial_indent=' ', subsequent_indent=' ')) oss.write('\\n') if isinstance(desc.default_value, (list, dict, set)): oss.write('\\n') oss.write(' (This option may be supplied multiple times.)\\n') oss.write('\\n\\n') return oss.getvalue() beancount.parser.options.options_validate_booking_method(value) \uf0c1 Validate a booking method name. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_booking_method(value): \"\"\"Validate a booking method name. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" try: return data.Booking[value] except KeyError as exc: raise ValueError(str(exc)) beancount.parser.options.options_validate_boolean(value) \uf0c1 Validate a boolean option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_boolean(value): \"\"\"Validate a boolean option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return value.lower() in ('1', 'true', 'yes') beancount.parser.options.options_validate_plugin(value) \uf0c1 Validate the plugin option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_plugin(value): \"\"\"Validate the plugin option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the 'plugin' option specially: accept an optional # argument from it. NOTE: We will eventually phase this out and # replace it by a dedicated 'plugin' directive. match = re.match('(.*):(.*)', value) if match: plugin_name, plugin_config = match.groups() else: plugin_name, plugin_config = value, None return (plugin_name, plugin_config) beancount.parser.options.options_validate_processing_mode(value) \uf0c1 Validate the options processing mode. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_processing_mode(value): \"\"\"Validate the options processing mode. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" if value not in ('raw', 'default'): raise ValueError(\"Invalid value '{}'\".format(value)) return value beancount.parser.options.options_validate_tolerance(value) \uf0c1 Validate the tolerance option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance(value): \"\"\"Validate the tolerance option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return D(value) beancount.parser.options.options_validate_tolerance_map(value) \uf0c1 Validate an option with a map of currency/tolerance pairs in a string. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance_map(value): \"\"\"Validate an option with a map of currency/tolerance pairs in a string. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the setting of a key-value, whereby the value is a Decimal # representation. match = re.match('(.*):(.*)', value) if not match: raise ValueError(\"Invalid value '{}'\".format(value)) currency, tolerance_str = match.groups() return (currency, D(tolerance_str)) beancount.parser.parser \uf0c1 Beancount syntax parser. IMPORTANT: The parser (and its grammar builder) produces \"incomplete\" Transaction objects. This means that some of the data can be found missing from the output of the parser and some of the data types vary slightly. Missing components are replaced not by None, but by a special constant 'NA' which helps diagnose problems if a user inadvertently attempts to work on an incomplete posting instead of a complete one. Those incomplete entries are then run through the \"booking\" routines which do two things simultaneously: They find matching lots for reducing inventory positions, and They interpolate missing numbers. In doing so they normalize the entries to \"complete\" entries by converting a position/lot's \"cost\" attribute from a CostSpec to a Cost. A Cost is similar to an Amount in that it shares \"number\" and \"currency\" attributes, but also has a label and a lot date. A CostSpec is similar to a Cost, but has all optional data; it consists in a specification for matching against a particular inventory lot. Other parts of a posting may also be missing, not just parts of the cost. Leaving out some parts of the input is used to invoke interpolation, to tell Beancount to automatically compute the missing numbers (if possible). Missing components will be set to the special value \"beancount.core.number.MISSING\" until inventory booking and number interpolation has been completed. The \"MISSING\" value should never appear in completed, loaded transaction postings. For instance, all of the units may be missing: INPUT: Assets:Account posting.units = MISSING Or just the number of the units: INPUT: Assets:Account USD posting.units = Amount(MISSING, \"USD\") You must always specify the currency. If a price annotation is simply absent, it appears as None: INPUT: Assets:Account 2 MXN posting.price = None However, you may indicate that there is a price but have Beancount compute it automatically: INPUT: Assets:Account 2 MXN @ posting.price = Amount(MISSING, MISSING) Indicating the conversion currency is also possible (and recommended): INPUT: Assets:Account 2 MXN @ USD posting.price = Amount(MISSING, \"USD\") If a cost specification is provided, a \"cost\" attribute it set but it does not refer to a Cost instance (as in complete entries) but rather to a CostSpec instance. Some of the fields of a CostSpec may be MISSING if they were not specified in the input. For example: INPUT: Assets:Account 1 HOOL {100 # 5 USD} posting.cost = CostSpec(Decimal(\"100\"), Decimal(\"5\"), \"USD\", None, None, False)) Note how we never consider the label of date override to be MISSING; this is because those inputs are optional: A missing label is simply left unset in the computed Cost, and a missing date override uses the date of the transaction that contains the posting. You can indicate that there is a total number to be filled in like this: INPUT: Assets:Account 1 HOOL {100 # USD} posting.cost = CostSpec(Decimal(\"100\"), MISSING, \"USD\", None, None, False)) This is in contrast to the total value simple not being used: INPUT: Assets:Account 1 HOOL {100 USD} posting.cost = CostSpec(Decimal(\"100\"), None, \"USD\", None, None, False)) Both per-unit and total numbers may be omitted as well, in which case, only the number-per-unit portion of the CostSpec will appear as MISSING: INPUT: Assets:Account 1 HOOL {USD} posting.cost = CostSpec(MISSING, None, \"USD\", None, None, False)) And furthermore, all the cost basis may be missing: INPUT: Assets:Account 1 HOOL {} posting.cost = CostSpec(MISSING, None, MISSING, None, None, False)) If you ask for the lots to be merged, you get this: INPUT: Assets:Account 1 HOOL {*} posting.cost = CostSpec(MISSING, None, MISSING, None, None, True)) The numbers have to be computed by Beancount, so we output this with MISSING values. Of course, you can provide only the non-basis information, like just the date or label: INPUT: Assets:Account 1 HOOL {2015-09-21} posting.cost = CostSpec(MISSING, None, MISSING, date(2015, 9, 21), None, True) See the test beancount.parser.grammar_test.TestIncompleteInputs for examples and corresponding expected values. beancount.parser.parser.is_entry_incomplete(entry) \uf0c1 Detect the presence of elided amounts in Transactions. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_entry_incomplete(entry): \"\"\"Detect the presence of elided amounts in Transactions. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" if isinstance(entry, data.Transaction): if any(is_posting_incomplete(posting) for posting in entry.postings): return True return False beancount.parser.parser.is_posting_incomplete(posting) \uf0c1 Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_posting_incomplete(posting): \"\"\"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" units = posting.units if (units is MISSING or units.number is MISSING or units.currency is MISSING): return True price = posting.price if (price is MISSING or price is not None and (price.number is MISSING or price.currency is MISSING)): return True cost = posting.cost if cost is not None and (cost.number_per is MISSING or cost.number_total is MISSING or cost.currency is MISSING): return True return False beancount.parser.parser.parse_doc(expect_errors=False, allow_incomplete=False) \uf0c1 Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete \u2013 A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. Source code in beancount/parser/parser.py def parse_doc(expect_errors=False, allow_incomplete=False): \"\"\"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete: A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: the function object to be decorated. Returns: A decorated test function. \"\"\" filename = inspect.getfile(fun) lines, lineno = inspect.getsourcelines(fun) # decorator line + function definition line (I realize this is largely # imperfect, but it's only for reporting in our tests) - empty first line # stripped away. lineno += 1 @functools.wraps(fun) def wrapper(self): assert fun.__doc__ is not None, ( \"You need to insert a docstring on {}\".format(fun.__name__)) entries, errors, options_map = parse_string(fun.__doc__, report_filename=filename, report_firstline=lineno, dedent=True) if not allow_incomplete and any(is_entry_incomplete(entry) for entry in entries): self.fail(\"parse_doc() may not use interpolation.\") if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator beancount.parser.parser.parse_file(filename, **kw) \uf0c1 Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: filename \u2013 the name of the file to be parsed. kw \u2013 a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) Source code in beancount/parser/parser.py def parse_file(filename, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: filename: the name of the file to be parsed. kw: a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) \"\"\" abs_filename = path.abspath(filename) if filename else None builder = grammar.Builder(abs_filename) _parser.parse_file(filename, builder, **kw) return builder.finalize() beancount.parser.parser.parse_many(string, level=0) \uf0c1 Parse a string with a snippet of Beancount input and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_many(string, level=0): \"\"\"Parse a string with a snippet of Beancount input and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" # Get the locals in the stack for the callers and produce the final text. frame = inspect.stack()[level+1] varkwds = frame[0].f_locals input_string = textwrap.dedent(string.format(**varkwds)) # Parse entries and check there are no errors. entries, errors, __ = parse_string(input_string) assert not errors return entries beancount.parser.parser.parse_one(string) \uf0c1 Parse a string with single Beancount directive and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_one(string): \"\"\"Parse a string with single Beancount directive and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" entries = parse_many(string, level=1) assert len(entries) == 1 return entries[0] beancount.parser.parser.parse_string(string, report_filename=None, **kw) \uf0c1 Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: string \u2013 A string, the contents to be parsed instead of a file's. report_filename \u2013 A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw \u2013 See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Returns: Same as the output of parse_file(). Source code in beancount/parser/parser.py def parse_string(string, report_filename=None, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: string: A string, the contents to be parsed instead of a file's. report_filename: A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw: See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Return: Same as the output of parse_file(). \"\"\" if kw.pop('dedent', None): string = textwrap.dedent(string) builder = grammar.Builder(report_filename or '') _parser.parse_string(string, builder, report_filename=report_filename, **kw) return builder.finalize() beancount.parser.printer \uf0c1 Conversion from internal data structures to text. beancount.parser.printer.EntryPrinter \uf0c1 A multi-method interface for printing all directive types. Attributes: Name Type Description dcontext An instance of DisplayContext with which to render all the numbers. render_weight A boolean, true if we should render the weight of the postings as a comment, for debugging. min_width_account An integer, the minimum width to leave for the account name. beancount.parser.printer.EntryPrinter.__call__(self, obj) special \uf0c1 Render a directive. Parameters: obj \u2013 The directive to be rendered. Returns: A string, the rendered directive. Source code in beancount/parser/printer.py def __call__(self, obj): \"\"\"Render a directive. Args: obj: The directive to be rendered. Returns: A string, the rendered directive. \"\"\" oss = io.StringIO() method = getattr(self, obj.__class__.__name__) method(obj, oss) return oss.getvalue() beancount.parser.printer.EntryPrinter.render_posting_strings(self, posting) \uf0c1 This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Parameters: posting \u2013 An instance of Posting, the posting to render. Returns: A tuple of flag_account \u2013 A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. Source code in beancount/parser/printer.py def render_posting_strings(self, posting): \"\"\"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Args: posting: An instance of Posting, the posting to render. Returns: A tuple of flag_account: A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. \"\"\" # Render a string of the flag and the account. flag = '{} '.format(posting.flag) if posting.flag else '' flag_account = flag + posting.account # Render a string with the amount and cost and optional price, if # present. Also render a string with the weight. weight_str = '' if isinstance(posting.units, amount.Amount): position_str = position.to_string(posting, self.dformat) # Note: we render weights at maximum precision, for debugging. if posting.cost is None or (isinstance(posting.cost, position.Cost) and isinstance(posting.cost.number, Decimal)): weight_str = str(convert.get_weight(posting)) else: position_str = '' if posting.price is not None: position_str += ' @ {}'.format(posting.price.to_string(self.dformat_max)) return flag_account, position_str, weight_str beancount.parser.printer.EntryPrinter.write_metadata(self, meta, oss, prefix=None) \uf0c1 Write metadata to the file object, excluding filename and line number. Parameters: meta \u2013 A dict that contains the metadata for this directive. oss \u2013 A file object to write to. Source code in beancount/parser/printer.py def write_metadata(self, meta, oss, prefix=None): \"\"\"Write metadata to the file object, excluding filename and line number. Args: meta: A dict that contains the metadata for this directive. oss: A file object to write to. \"\"\" if meta is None: return if prefix is None: prefix = self.prefix for key, value in sorted(meta.items()): if key not in self.META_IGNORE: value_str = None if isinstance(value, str): value_str = '\"{}\"'.format(misc_utils.escape_string(value)) elif isinstance(value, (Decimal, datetime.date, amount.Amount)): value_str = str(value) elif isinstance(value, bool): value_str = 'TRUE' if value else 'FALSE' elif isinstance(value, (dict, inventory.Inventory)): pass # Ignore dicts, don't print them out. elif value is None: value_str = '' # Render null metadata as empty, on purpose. else: raise ValueError(\"Unexpected value: '{!r}'\".format(value)) if value_str is not None: oss.write(\"{}{}: {}\\n\".format(prefix, key, value_str)) beancount.parser.printer.align_position_strings(strings) \uf0c1 A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Parameters: strings \u2013 A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. Source code in beancount/parser/printer.py def align_position_strings(strings): \"\"\"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Args: strings: A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. \"\"\" # Maximum length before the alignment character. max_before = 0 # Maximum length after the alignment character. max_after = 0 # Maximum length of unknown strings. max_unknown = 0 string_items = [] search = re.compile('[A-Z]').search for string in strings: match = search(string) if match: index = match.start() if index != 0: max_before = max(index, max_before) max_after = max(len(string) - index, max_after) string_items.append((index, string)) continue # else max_unknown = max(len(string), max_unknown) string_items.append((None, string)) # Compute formatting string. max_total = max(max_before + max_after, max_unknown) max_after_prime = max_total - max_before fmt = \"{{:>{0}}}{{:{1}}}\".format(max_before, max_after_prime).format fmt_unknown = \"{{:<{0}}}\".format(max_total).format # Align the strings and return them. aligned_strings = [] for index, string in string_items: if index is not None: string = fmt(string[:index], string[index:]) else: string = fmt_unknown(string) aligned_strings.append(string) return aligned_strings, max_total beancount.parser.printer.format_entry(entry, dcontext=None, render_weights=False, prefix=None) \uf0c1 Format an entry into a string in the same input syntax the parser accepts. Parameters: entry \u2013 An entry instance. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. Source code in beancount/parser/printer.py def format_entry(entry, dcontext=None, render_weights=False, prefix=None): \"\"\"Format an entry into a string in the same input syntax the parser accepts. Args: entry: An entry instance. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. \"\"\" return EntryPrinter(dcontext, render_weights, prefix=prefix)(entry) beancount.parser.printer.format_error(error) \uf0c1 Given an error objects, return a formatted string for it. Parameters: error \u2013 a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. Source code in beancount/parser/printer.py def format_error(error): \"\"\"Given an error objects, return a formatted string for it. Args: error: a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. \"\"\" oss = io.StringIO() oss.write('{} {}\\n'.format(render_source(error.source), error.message)) if error.entry is not None: entries = error.entry if isinstance(error.entry, list) else [error.entry] error_string = '\\n'.join(format_entry(entry) for entry in entries) oss.write('\\n') oss.write(textwrap.indent(error_string, ' ')) oss.write('\\n') return oss.getvalue() beancount.parser.printer.print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None) \uf0c1 A convenience function that prints a list of entries to a file. Parameters: entries \u2013 A list of directives. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None): \"\"\"A convenience function that prints a list of entries to a file. Args: entries: A list of directives. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" assert isinstance(entries, list), \"Entries is not a list: {}\".format(entries) output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) if prefix: output.write(prefix) previous_type = type(entries[0]) if entries else None eprinter = EntryPrinter(dcontext, render_weights) for entry in entries: # Insert a newline between transactions and between blocks of directives # of the same type. entry_type = type(entry) if (entry_type in (data.Transaction, data.Commodity) or entry_type is not previous_type): output.write('\\n') previous_type = entry_type string = eprinter(entry) output.write(string) beancount.parser.printer.print_entry(entry, dcontext=None, render_weights=False, file=None) \uf0c1 A convenience function that prints a single entry to a file. Parameters: entry \u2013 A directive entry. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entry(entry, dcontext=None, render_weights=False, file=None): \"\"\"A convenience function that prints a single entry to a file. Args: entry: A directive entry. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) output.write(format_entry(entry, dcontext, render_weights)) output.write('\\n') beancount.parser.printer.print_error(error, file=None) \uf0c1 A convenience function that prints a single error to a file. Parameters: error \u2013 An error object. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_error(error, file=None): \"\"\"A convenience function that prints a single error to a file. Args: error: An error object. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout output.write(format_error(error)) output.write('\\n') beancount.parser.printer.print_errors(errors, file=None, prefix=None) \uf0c1 A convenience function that prints a list of errors to a file. Parameters: errors \u2013 A list of errors. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_errors(errors, file=None, prefix=None): \"\"\"A convenience function that prints a list of errors to a file. Args: errors: A list of errors. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout if prefix: output.write(prefix) for error in errors: output.write(format_error(error)) output.write('\\n') beancount.parser.printer.render_source(meta) \uf0c1 Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Parameters: meta \u2013 A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. Source code in beancount/parser/printer.py def render_source(meta): \"\"\"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Args: meta: A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. \"\"\" return '{}:{:8}'.format(meta['filename'], '{}:'.format(meta['lineno']))","title":"beancount.parser"},{"location":"api_reference/beancount.parser.html#beancountparser","text":"Parser module for beancount input files.","title":"beancount.parser"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking","text":"Algorithms for 'booking' inventory, that is, the process of finding a matching lot when reducing the content of an inventory.","title":"booking"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError","text":"BookingError(source, message, entry)","title":"BookingError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__new__","text":"Create new instance of BookingError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.BookingError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.book","text":"Book inventory lots and complete all positions with incomplete numbers. Parameters: incomplete_entries \u2013 A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map \u2013 An options dict as produced by the parser. Returns: A pair of entries \u2013 A list of completed entries with all their postings completed. errors: New errors produced during interpolation. Source code in beancount/parser/booking.py def book(incomplete_entries, options_map): \"\"\"Book inventory lots and complete all positions with incomplete numbers. Args: incomplete_entries: A list of directives, with some postings possibly left with incomplete amounts as produced by the parser. options_map: An options dict as produced by the parser. Returns: A pair of entries: A list of completed entries with all their postings completed. errors: New errors produced during interpolation. \"\"\" # Get the list of booking methods for each account. booking_methods = collections.defaultdict(lambda: options_map[\"booking_method\"]) for entry in incomplete_entries: if isinstance(entry, data.Open) and entry.booking: booking_methods[entry.account] = entry.booking # Do the booking here! entries, booking_errors = booking_full.book(incomplete_entries, options_map, booking_methods) # Check for MISSING elements remaining. missing_errors = validate_missing_eliminated(entries, options_map) return entries, (booking_errors + missing_errors)","title":"book()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.validate_inventory_booking","text":"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. booking_methods \u2013 A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_inventory_booking(entries, unused_options_map, booking_methods): \"\"\"Validate that no position at cost is allowed to go negative. This routine checks that when a posting reduces a position, existing or not, that the subsequent inventory does not result in a position with a negative number of units. A negative number of units would only be required for short trades of trading spreads on futures, and right now this is not supported. It would not be difficult to support this, however, but we want to be strict about it, because being pedantic about this is otherwise a great way to detect user data entry mistakes. Args: entries: A list of directives. unused_options_map: An options map. booking_methods: A mapping of account name to booking method, accumulated in the main loop. Returns: A list of errors. \"\"\" errors = [] balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: # Update the balance of each posting on its respective account # without allowing booking to a negative position, and if an error # is encountered, catch it and return it. running_balance = balances[posting.account] position_, _ = running_balance.add_position(posting) # Skip this check if the booking method is set to ignore it. if booking_methods.get(posting.account, None) == data.Booking.NONE: continue # Check if the resulting inventory is mixed, which is not # allowed under the STRICT method. if running_balance.is_mixed(): errors.append( BookingError( entry.meta, (\"Reducing position results in inventory with positive \" \"and negative lots: {}\").format(position_), entry)) return errors","title":"validate_inventory_booking()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking.validate_missing_eliminated","text":"Validate that all the missing bits of postings have been eliminated. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of errors. Source code in beancount/parser/booking.py def validate_missing_eliminated(entries, unused_options_map): \"\"\"Validate that all the missing bits of postings have been eliminated. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of errors. \"\"\" errors = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: units = posting.units cost = posting.cost if (MISSING in (units.number, units.currency) or cost is not None and MISSING in (cost.number, cost.currency, cost.date, cost.label)): errors.append( BookingError(entry.meta, \"Transaction has incomplete elements\", entry)) break return errors","title":"validate_missing_eliminated()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full","text":"Full (new) booking implementation. Problem description: Interpolation and booking feed on each other, that is, numbers filled in from interpolation might affect the booking process, and numbers derived from the booking process may help carry out interpolation that would otherwise be under-defined. Here's an example of interpolation helping the booking process: Assume the ante-inventory of Assets:Investments contains two lots of shares of HOOL, one at 100.00 USD and the other at 101.00 USD and apply this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains -200 USD Interpolation is unambiguously able to back out a cost of 100 USD / HOOL, which would then result in an unambiguous booking result. On the other hand, consider this transaction: 2015-09-30 * Assets:Investments -10 HOOL {USD} Assets:Cash 1000 USD Income:Gains Now the interpolation cannot succeed. If the Assets:Investments account is configured to use the FIFO method, the 10 oldest shares would be selected for the cost, and we could then interpolate the capital gains correctly. First observation: The second case is much more frequent than the first, and the first is easily resolved manually by requiring a particular cost be specified. Moreover, in many cases there isn't just a single lot of shares to be reduced from and figuring out the correct set of shares given a target cost is an underspecified problem. Second observation: Booking can only be achieved for inventory reductions, not for augmentations. Therefore, we should carry out booking on inventory reductions and fail early if reduction is undefined there, and leave inventory augmentations with missing numbers undefined, so that interpolation can fill them in at a later stage. Note that one case we'd like to but may not be able to handle is of a reduction with interpolated price, like this: 2015-09-30 * Assets:Investments -10 HOOL {100.00 # USD} Expenses:Commission 9.95 USD Assets:Cash 990.05 USD Therefore we choose to 1) Carry out booking first, on inventory reductions only, and leave inventory augmentations as they are, possibly undefined. The 'cost' attributed of booked postings are converted from CostSpec to Cost. Augmented postings with missing amounts are left as CostSpec instances in order to allow for interpolation of total vs. per-unit amount. 2) Compute interpolations on the resulting postings. Undefined costs for inventory augmentations may be filled in by interpolations at this stage (if possible). 3) Finally, convert the interpolated CostSpec instances to Cost instances. Improving on this algorithm would require running a loop over the booking and interpolation steps until all numbers are resolved or no more inference can occur. We may consider that for later, as an experimental feature. My hunch is that there are so few cases for which this would be useful that we won't bother improving on the algorithm above.","title":"booking_full"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError","text":"CategorizationError(source, message, entry)","title":"CategorizationError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__new__","text":"Create new instance of CategorizationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.CategorizationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError","text":"InterpolationError(source, message, entry)","title":"InterpolationError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__new__","text":"Create new instance of InterpolationError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.InterpolationError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.MissingType","text":"The type of missing number.","title":"MissingType"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError","text":"ReductionError(source, message, entry)","title":"ReductionError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__new__","text":"Create new instance of ReductionError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.ReductionError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer","text":"Refer(index, units_currency, cost_currency, price_currency)","title":"Refer"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__new__","text":"Create new instance of Refer(index, units_currency, cost_currency, price_currency)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.Refer.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError","text":"SelfReduxError(source, message, entry)","title":"SelfReduxError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_full.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__new__","text":"Create new instance of SelfReduxError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.SelfReduxError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_full.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.book","text":"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. Source code in beancount/parser/booking_full.py def book(entries, options_map, methods): \"\"\"Interpolate missing data from the entries using the full historical algorithm. See the internal implementation _book() for details. This method only stripes some of the return values. See _book() for arguments and return values. \"\"\" entries, errors, _ = _book(entries, options_map, methods) return entries, errors","title":"book()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.book_reductions","text":"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Parameters: entry \u2013 An instance of Transaction. This is only used to refer to when logging errors. group_postings \u2013 A list of Posting instances for the group. balances \u2013 A dict of account name to inventory contents. methods \u2013 A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings \u2013 A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. Source code in beancount/parser/booking_full.py def book_reductions(entry, group_postings, balances, methods): \"\"\"Book inventory reductions against the ante-balances. This function accepts a dict of (account, Inventory balance) and for each posting that is a reduction against its inventory, attempts to find a corresponding lot or list of lots to reduce the balance with. * For reducing lots, the CostSpec instance of the posting is replaced by a Cost instance. * For augmenting lots, the CostSpec instance of the posting is left alone, except for its date, which is inherited from the parent Transaction. Args: entry: An instance of Transaction. This is only used to refer to when logging errors. group_postings: A list of Posting instances for the group. balances: A dict of account name to inventory contents. methods: A mapping of account name to their corresponding booking method enum. Returns: A pair of booked_postings: A list of booked postings, with reducing lots resolved against specific position in the corresponding accounts' ante-inventory balances. Note single reducing posting in the input may result in multiple postings in the output. Also note that augmenting postings held-at-cost will still refer to 'cost' instances of CostSpec, left to be interpolated later. errors: A list of errors, if there were any. \"\"\" errors = [] # A local copy of the balances dictionary which is updated just for the # duration of this function's updates, in order to take into account the # cumulative effect of all the postings inferred here local_balances = {} empty = inventory.Inventory() booked_postings = [] for posting in group_postings: # Process a single posting. units = posting.units costspec = posting.cost account = posting.account # Note: We ensure there is no mutation on 'balances' to keep this # function without side-effects. Note that we may be able to optimize # performance later on by giving up this property. # # Also note that if there is no existing balance, then won't be any lot # reduction because none of the postings will be able to match against # any currencies of the balance. previous_balance = balances.get(account, empty) balance = local_balances.setdefault(account, copy.copy(previous_balance)) # Check if this is a lot held at cost. if costspec is None or units.number is MISSING: # This posting is not held at cost; we do nothing. booked_postings.append(posting) else: # This posting is held at cost; figure out if it's a reduction or an # augmentation. method = methods[account] if (method is not Booking.NONE and balance is not None and balance.is_reduced_by(units)): # This posting is a reduction. # Match the positions. cost_number = compute_cost_number(costspec, units) matches = [] for position in balance: # Skip inventory contents of a different currency. if (units.currency and position.units.currency != units.currency): continue # Skip balance positions not held at cost. if position.cost is None: continue if (cost_number is not None and position.cost.number != cost_number): continue if (isinstance(costspec.currency, str) and position.cost.currency != costspec.currency): continue if (costspec.date and position.cost.date != costspec.date): continue if (costspec.label and position.cost.label != costspec.label): continue matches.append(position) # Check for ambiguous matches. if len(matches) == 0: errors.append( ReductionError(entry.meta, 'No position matches \"{}\" against balance {}'.format( posting, balance), entry)) return [], errors # This is irreconcilable, remove these postings. reduction_postings, matched_postings, ambi_errors = ( booking_method.handle_ambiguous_matches(entry, posting, matches, method)) if ambi_errors: errors.extend(ambi_errors) return [], errors # Add the reductions to the resulting list of booked postings. booked_postings.extend(reduction_postings) # Update the local balance in order to avoid matching against # the same postings twice when processing multiple postings in # the same transaction. Note that we only do this for postings # held at cost because the other postings may need interpolation # in order to be resolved properly. for posting in reduction_postings: balance.add_position(posting) else: # This posting is an augmentation. # # Note that we do not convert the CostSpec instances to Cost # instances, because we want to let the subsequent interpolation # process able to interpolate either the cost per-unit or the # total cost, separately. # Put in the date of the parent Transaction if there is no # explicit date specified on the spec. if costspec.date is None: dated_costspec = costspec._replace(date=entry.date) posting = posting._replace(cost=dated_costspec) # FIXME: Insert unique ids for trade tracking; right now this # creates ambiguous matches errors (and it shouldn't). # # Insert a unique label if there isn't one. # if posting.cost is not None and posting.cost.label is None: # posting = posting._replace( # cost=posting.cost._replace(label=unique_label())) booked_postings.append(posting) return booked_postings, errors","title":"book_reductions()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.categorize_by_currency","text":"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. If the currency is explicitly specified, we put the posting in that currency's bucket. If not, we have a few methods left to disambiguate the currency: We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Parameters: postings \u2013 A list of incomplete postings to categorize. balances \u2013 A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains \u2013 index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. Source code in beancount/parser/booking_full.py def categorize_by_currency(entry, balances): \"\"\"Group the postings by the currency they declare. This is used to prepare the postings for the next stages: Interpolation and booking will then be carried out separately on each currency group. At the outset of this routine, we should have distinct groups of currencies without any ambiguities regarding which currency they need to balance against. Here's how this works. - First we apply the constraint that cost-currency and price-currency must match, if there is both a cost and a price. This reduces the space of possibilities somewhat. - If the currency is explicitly specified, we put the posting in that currency's bucket. - If not, we have a few methods left to disambiguate the currency: 1. We look at the remaining postings... if they are all of a single currency, the posting must be in that currency too. 2. If we cannot do that, we inspect the contents of the inventory of the account for the posting. If all the contents are of a single currency, we use that one. Args: postings: A list of incomplete postings to categorize. balances: A dict of currency to inventory contents before the transaction is applied. Returns: A list of (currency string, list of tuples) items describing each postings and its interpolated currencies, and a list of generated errors for currency interpolation. The entry's original postings are left unmodified. Each tuple in the value-list contains: index: The posting index in the original entry. units_currency: The interpolated currency for units. cost_currency: The interpolated currency for cost. price_currency: The interpolated currency for price. \"\"\" errors = [] groups = collections.defaultdict(list) sortdict = {} auto_postings = [] unknown = [] for index, posting in enumerate(entry.postings): units = posting.units cost = posting.cost price = posting.price # Extract and override the currencies locally. units_currency = (units.currency if units is not MISSING and units is not None else None) cost_currency = (cost.currency if cost is not MISSING and cost is not None else None) price_currency = (price.currency if price is not MISSING and price is not None else None) # First we apply the constraint that cost-currency and price-currency # must match, if there is both a cost and a price. This reduces the # space of possibilities somewhat. if cost_currency is MISSING and isinstance(price_currency, str): cost_currency = price_currency if price_currency is MISSING and isinstance(cost_currency, str): price_currency = cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) if units is MISSING and price_currency is None: # Bucket auto-postings separately from unknown. auto_postings.append(refer) else: # Bucket with what we know so far. currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: # If we need to infer the currency, store in unknown. unknown.append(refer) # We look at the remaining postings... if they are all of a single currency, # the posting must be in that currency too. if unknown and len(unknown) == 1 and len(groups) == 1: (index, units_currency, cost_currency, price_currency) = unknown.pop() other_currency = next(iter(groups.keys())) if price_currency is None and cost_currency is None: # Infer to the units currency. units_currency = other_currency else: # Infer to the cost and price currencies. if price_currency is MISSING: price_currency = other_currency if cost_currency is MISSING: cost_currency = other_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) assert currency is not None sortdict.setdefault(currency, index) groups[currency].append(refer) # Finally, try to resolve all the unknown legs using the inventory contents # of each account. for refer in unknown: (index, units_currency, cost_currency, price_currency) = refer posting = entry.postings[index] balance = balances.get(posting.account, None) if balance is None: balance = inventory.Inventory() if units_currency is MISSING: balance_currencies = balance.currencies() if len(balance_currencies) == 1: units_currency = balance_currencies.pop() if cost_currency is MISSING or price_currency is MISSING: balance_cost_currencies = balance.cost_currencies() if len(balance_cost_currencies) == 1: balance_cost_currency = balance_cost_currencies.pop() if price_currency is MISSING: price_currency = balance_cost_currency if cost_currency is MISSING: cost_currency = balance_cost_currency refer = Refer(index, units_currency, cost_currency, price_currency) currency = get_bucket_currency(refer) if currency is not None: sortdict.setdefault(currency, index) groups[currency].append(refer) else: errors.append( CategorizationError(posting.meta, \"Failed to categorize posting {}\".format(index + 1), entry)) # Fill in missing units currencies if some remain as missing. This may occur # if we used the cost or price to bucket the currency but the units currency # was missing. for currency, refers in groups.items(): for rindex, refer in enumerate(refers): if refer.units_currency is MISSING: posting = entry.postings[refer.index] balance = balances.get(posting.account, None) if balance is None: continue balance_currencies = balance.currencies() if len(balance_currencies) == 1: refers[rindex] = refer._replace(units_currency=balance_currencies.pop()) # Deal with auto-postings. if len(auto_postings) > 1: refer = auto_postings[-1] posting = entry.postings[refer.index] errors.append( CategorizationError(posting.meta, \"You may not have more than one auto-posting per currency\", entry)) auto_postings = auto_postings[0:1] for refer in auto_postings: for currency in groups.keys(): sortdict.setdefault(currency, refer.index) groups[currency].append(Refer(refer.index, currency, None, None)) # Issue error for all currencies which we could not resolve. for currency, refers in groups.items(): for refer in refers: posting = entry.postings[refer.index] for currency, name in [(refer.units_currency, 'units'), (refer.cost_currency, 'cost'), (refer.price_currency, 'price')]: if currency is MISSING: errors.append(CategorizationError( posting.meta, \"Could not resolve {} currency\".format(name), entry)) sorted_groups = sorted(groups.items(), key=lambda item: sortdict[item[0]]) return sorted_groups, errors","title":"categorize_by_currency()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.compute_cost_number","text":"Given a CostSpec, return the cost number, if possible to compute. Parameters: costspec \u2013 A parsed instance of CostSpec. units \u2013 An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. Source code in beancount/parser/booking_full.py def compute_cost_number(costspec, units): \"\"\"Given a CostSpec, return the cost number, if possible to compute. Args: costspec: A parsed instance of CostSpec. units: An instance of Amount for the units of the position. Returns: If it is not possible to calculate the cost, return None. Otherwise, returns a Decimal instance, the per-unit cost. \"\"\" number_per = costspec.number_per number_total = costspec.number_total if MISSING in (number_per, number_total): return None if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total units_number = units.number if number_per is not None: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) elif number_per is None: return None else: unit_cost = number_per return unit_cost","title":"compute_cost_number()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.convert_costspec_to_cost","text":"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Parameters: posting \u2013 An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. Source code in beancount/parser/booking_full.py def convert_costspec_to_cost(posting): \"\"\"Convert an instance of CostSpec to Cost, if present on the posting. If the posting has no cost, it itself is just returned. Args: posting: An instance of Posting. Returns: An instance of Posting with a possibly replaced 'cost' attribute. \"\"\" cost = posting.cost if isinstance(cost, position.CostSpec): if cost is not None: units_number = posting.units.number number_per = cost.number_per number_total = cost.number_total if number_total is not None: # Compute the per-unit cost if there is some total cost # component involved. cost_total = number_total if number_per is not MISSING: cost_total += number_per * units_number unit_cost = cost_total / abs(units_number) else: unit_cost = number_per new_cost = Cost(unit_cost, cost.currency, cost.date, cost.label) posting = posting._replace(units=posting.units, cost=new_cost) return posting","title":"convert_costspec_to_cost()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.get_bucket_currency","text":"Given currency references for a posting, return the bucket currency. Parameters: refer \u2013 An instance of Refer. Returns: A currency string. Source code in beancount/parser/booking_full.py def get_bucket_currency(refer): \"\"\"Given currency references for a posting, return the bucket currency. Args: refer: An instance of Refer. Returns: A currency string. \"\"\" currency = None if isinstance(refer.cost_currency, str): currency = refer.cost_currency elif isinstance(refer.price_currency, str): currency = refer.price_currency elif (refer.cost_currency is None and refer.price_currency is None and isinstance(refer.units_currency, str)): currency = refer.units_currency return currency","title":"get_bucket_currency()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.has_self_reduction","text":"Return true if the postings potentially reduce each other at cost. Parameters: postings \u2013 A list of postings with uninterpolated CostSpec cost instances. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. Source code in beancount/parser/booking_full.py def has_self_reduction(postings, methods): \"\"\"Return true if the postings potentially reduce each other at cost. Args: postings: A list of postings with uninterpolated CostSpec cost instances. methods: A mapping of account name to their corresponding booking method. Returns: A boolean, true if there's a potential for self-reduction. \"\"\" # A mapping of (currency, cost-currency) and sign. cost_changes = {} for posting in postings: cost = posting.cost if cost is None: continue if methods[posting.account] is Booking.NONE: continue key = (posting.account, posting.units.currency) sign = 1 if posting.units.number > ZERO else -1 if cost_changes.setdefault(key, sign) != sign: return True return False","title":"has_self_reduction()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.interpolate_group","text":"Interpolate missing numbers in the set of postings. Parameters: postings \u2013 A list of Posting instances. balances \u2013 A dict of account to its ante-inventory. currency \u2013 The weight currency of this group, used for reporting errors. tolerances \u2013 A dict of currency to tolerance values. Returns: A tuple of postings \u2013 A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) Source code in beancount/parser/booking_full.py def interpolate_group(postings, balances, currency, tolerances): \"\"\"Interpolate missing numbers in the set of postings. Args: postings: A list of Posting instances. balances: A dict of account to its ante-inventory. currency: The weight currency of this group, used for reporting errors. tolerances: A dict of currency to tolerance values. Returns: A tuple of postings: A lit of new posting instances. errors: A list of errors generated during interpolation. interpolated: A boolean, true if we did have to interpolate. In the case of an error, this returns the original list of postings, which is still incomplete. If an error is returned, you should probably skip the transaction altogether, or just not include the postings in it. (An alternative behaviour would be to return only the list of valid postings, but that would likely result in an unbalanced transaction. We do it this way by choice.) \"\"\" errors = [] # Figure out which type of amount is missing, by creating a list of # incomplete postings and which type of units is missing. incomplete = [] for index, posting in enumerate(postings): units = posting.units cost = posting.cost price = posting.price # Identify incomplete parts of the Posting components. if units.number is MISSING: incomplete.append((MissingType.UNITS, index)) if isinstance(cost, CostSpec): if cost and cost.number_per is MISSING: incomplete.append((MissingType.COST_PER, index)) if cost and cost.number_total is MISSING: incomplete.append((MissingType.COST_TOTAL, index)) else: # Check that a resolved instance of Cost never needs interpolation. # # Note that in theory we could support the interpolation of regular # per-unit costs in these if we wanted to; but because they're all # reducing postings that have been booked earlier, those never need # to be interpolated. if cost is not None: assert isinstance(cost.number, Decimal), ( \"Internal error: cost has no number: {}\".format(cost)) if price and price.number is MISSING: incomplete.append((MissingType.PRICE, index)) # The replacement posting for the incomplete posting of this group. new_posting = None if len(incomplete) == 0: # If there are no missing numbers, just convert the CostSpec to Cost and # return that. out_postings = [convert_costspec_to_cost(posting) for posting in postings] elif len(incomplete) > 1: # If there is more than a single value to be interpolated, generate an # error and return no postings. _, posting_index = incomplete[0] errors.append(InterpolationError( postings[posting_index].meta, \"Too many missing numbers for currency group '{}'\".format(currency), None)) out_postings = [] else: # If there is a single missing number, calculate it and fill it in here. missing, index = incomplete[0] incomplete_posting = postings[index] # Convert augmenting postings' costs from CostSpec to corresponding Cost # instances, except for the incomplete posting. new_postings = [(posting if posting is incomplete_posting else convert_costspec_to_cost(posting)) for posting in postings] # Compute the balance of the other postings. residual = interpolate.compute_residual(posting for posting in new_postings if posting is not incomplete_posting) assert len(residual) < 2, \"Internal error in grouping postings by currencies.\" if not residual.is_empty(): respos = next(iter(residual)) assert respos.cost is None, ( \"Internal error; cost appears in weight calculation.\") assert respos.units.currency == currency, ( \"Internal error; residual different than currency group.\") weight = -respos.units.number weight_currency = respos.units.currency else: weight = ZERO weight_currency = currency if missing == MissingType.UNITS: units = incomplete_posting.units cost = incomplete_posting.cost if cost: # Handle the special case where we only have total cost. if cost.number_per == ZERO: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer per-unit cost only from total\", None)) return postings, errors, True assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") cost_total = cost.number_total or ZERO units_number = (weight - cost_total) / cost.number_per elif incomplete_posting.price: assert incomplete_posting.price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight / incomplete_posting.price.number else: assert units.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") units_number = weight # Quantize the interpolated units if necessary. units_number = interpolate.quantize_with_tolerance(tolerances, units.currency, units_number) if weight != ZERO: new_pos = Position(Amount(units_number, units.currency), cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_PER: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") if units.number != ZERO: number_per = (weight - (cost.number_total or ZERO)) / units.number new_cost = cost._replace(number_per=number_per) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) else: new_posting = None elif missing == MissingType.COST_TOTAL: units = incomplete_posting.units cost = incomplete_posting.cost assert cost.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") number_total = (weight - cost.number_per * units.number) new_cost = cost._replace(number_total=number_total) new_pos = Position(units, new_cost) new_posting = incomplete_posting._replace(units=new_pos.units, cost=new_pos.cost) elif missing == MissingType.PRICE: units = incomplete_posting.units cost = incomplete_posting.cost if cost is not None: errors.append(InterpolationError( incomplete_posting.meta, \"Cannot infer price for postings with units held at cost\", None)) return postings, errors, True else: price = incomplete_posting.price assert price.currency == weight_currency, ( \"Internal error; residual currency different than missing currency.\") new_price_number = abs(weight / units.number) new_posting = incomplete_posting._replace(price=Amount(new_price_number, price.currency)) else: assert False, \"Internal error; Invalid missing type.\" # Replace the number in the posting. if new_posting is not None: # Set meta-data on the new posting to indicate it was interpolated. if new_posting.meta is None: new_posting = new_posting._replace(meta={}) new_posting.meta[interpolate.AUTOMATIC_META] = True # Convert augmenting posting costs from CostSpec to a corresponding # Cost instance. new_postings[index] = convert_costspec_to_cost(new_posting) else: del new_postings[index] out_postings = new_postings assert all(not isinstance(posting.cost, CostSpec) for posting in out_postings) # Check that units are non-zero and that no cost remains negative; issue an # error if this is the case. for posting in out_postings: if posting.cost is None: continue # If there is a cost, we don't allow either a cost value of zero, # nor a zero number of units. Note that we allow a price of zero as # the only special case allowed (for conversion entries), but never # for costs. if posting.units.number == ZERO: errors.append(InterpolationError( posting.meta, 'Amount is zero: \"{}\"'.format(posting.units), None)) if posting.cost.number < ZERO: errors.append(InterpolationError( posting.meta, 'Cost is negative: \"{}\"'.format(posting.cost), None)) return out_postings, errors, (new_posting is not None)","title":"interpolate_group()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.replace_currencies","text":"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Parameters: postings \u2013 A list of Posting instances to replace. refer_groups \u2013 A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. Source code in beancount/parser/booking_full.py def replace_currencies(postings, refer_groups): \"\"\"Replace resolved currencies in the entry's Postings. This essentially applies the findings of categorize_by_currency() to produce new postings with all currencies resolved. Args: postings: A list of Posting instances to replace. refer_groups: A list of (currency, list of posting references) items as returned by categorize_by_currency(). Returns: A new list of items of (currency, list of Postings), postings for which the currencies have been replaced by their interpolated currency values. \"\"\" new_groups = [] for currency, refers in refer_groups: new_postings = [] for refer in sorted(refers, key=lambda r: r.index): posting = postings[refer.index] units = posting.units if units is MISSING or units is None: posting = posting._replace(units=Amount(MISSING, refer.units_currency)) else: replace = False cost = posting.cost price = posting.price if units.currency is MISSING: units = Amount(units.number, refer.units_currency) replace = True if cost and cost.currency is MISSING: cost = cost._replace(currency=refer.cost_currency) replace = True if price and price.currency is MISSING: price = Amount(price.number, refer.price_currency) replace = True if replace: posting = posting._replace(units=units, cost=cost, price=price) new_postings.append(posting) new_groups.append((currency, new_postings)) return new_groups","title":"replace_currencies()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_full.unique_label","text":"Return a globally unique label for cost entries. Source code in beancount/parser/booking_full.py def unique_label() -> Text: \"Return a globally unique label for cost entries.\" return str(uuid.uuid4())","title":"unique_label()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method","text":"Implementations of all the particular booking methods. This code is used by the full booking algorithm.","title":"booking_method"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError","text":"AmbiguousMatchError(source, message, entry)","title":"AmbiguousMatchError"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/booking_method.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__new__","text":"Create new instance of AmbiguousMatchError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.AmbiguousMatchError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/booking_method.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_AVERAGE","text":"AVERAGE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_AVERAGE(entry, posting, matches): \"\"\"AVERAGE booking method implementation.\"\"\" booked_reductions = [] booked_matches = [] errors = [AmbiguousMatchError(entry.meta, \"AVERAGE method is not supported\", entry)] return booked_reductions, booked_matches, errors, False # FIXME: Future implementation here. # pylint: disable=unreachable if False: # pylint: disable=using-constant-test # DISABLED - This is the code for AVERAGE, which is currently disabled. # If there is more than a single match we need to ultimately merge the # postings. Also, if the reducing posting provides a specific cost, we # need to update the cost basis as well. Both of these cases are carried # out by removing all the matches and readding them later on. if len(matches) == 1 and ( not isinstance(posting.cost.number_per, Decimal) and not isinstance(posting.cost.number_total, Decimal)): # There is no cost. Just reduce the one leg. This should be the # normal case if we always merge augmentations and the user lets # Beancount deal with the cost. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) insufficient = (match_units.number != posting.units.number) else: # Merge the matching postings to a single one. merged_units = inventory.Inventory() merged_cost = inventory.Inventory() for match in matches: merged_units.add_amount(match.units) merged_cost.add_amount(convert.get_weight(match)) if len(merged_units) != 1 or len(merged_cost) != 1: errors.append( AmbiguousMatchError( entry.meta, 'Cannot merge positions in multiple currencies: {}'.format( ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: if (isinstance(posting.cost.number_per, Decimal) or isinstance(posting.cost.number_total, Decimal)): errors.append( AmbiguousMatchError( entry.meta, \"Explicit cost reductions aren't supported yet: {}\".format( position.to_string(posting)), entry)) else: # Insert postings to remove all the matches. booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost, flag=flags.FLAG_MERGING) for match in matches) units = merged_units[0].units date = matches[0].cost.date ## FIXME: Select which one, ## oldest or latest. cost_units = merged_cost[0].units cost = Cost(cost_units.number/units.number, cost_units.currency, date, None) # Insert a posting to refill those with a replacement match. booked_reductions.append( posting._replace(units=units, cost=cost, flag=flags.FLAG_MERGING)) # Now, match the reducing request against this lot. booked_reductions.append( posting._replace(units=posting.units, cost=cost)) insufficient = abs(posting.units.number) > abs(units.number)","title":"booking_method_AVERAGE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_FIFO","text":"FIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_FIFO(entry, posting, matches): \"\"\"FIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, False)","title":"booking_method_FIFO()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_LIFO","text":"LIFO booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_LIFO(entry, posting, matches): \"\"\"LIFO booking method implementation.\"\"\" return _booking_method_xifo(entry, posting, matches, True)","title":"booking_method_LIFO()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_NONE","text":"NONE booking method implementation. Source code in beancount/parser/booking_method.py def booking_method_NONE(entry, posting, matches): \"\"\"NONE booking method implementation.\"\"\" # This never needs to match against any existing positions... we # disregard the matches, there's never any error. Note that this never # gets called in practice, we want to treat NONE postings as # augmentations. Default behaviour is to return them with their original # CostSpec, and the augmentation code will handle signaling an error if # there is insufficient detail to carry out the conversion to an # instance of Cost. # Note that it's an interesting question whether a reduction on an # account with NONE method which happens to match a single position # ought to be matched against it. We don't allow it for now. return [posting], [], False","title":"booking_method_NONE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.booking_method_STRICT","text":"Strict booking method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. Source code in beancount/parser/booking_method.py def booking_method_STRICT(entry, posting, matches): \"\"\"Strict booking method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. Returns: A triple of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. insufficient: A boolean, true if we could not find enough matches to fulfill the reduction. \"\"\" booked_reductions = [] booked_matches = [] errors = [] insufficient = False # In strict mode, we require at most a single matching posting. if len(matches) > 1: # If the total requested to reduce matches the sum of all the # ambiguous postings, match against all of them. sum_matches = sum(p.units.number for p in matches) if sum_matches == -posting.units.number: booked_reductions.extend( posting._replace(units=-match.units, cost=match.cost) for match in matches) else: errors.append( AmbiguousMatchError(entry.meta, 'Ambiguous matches for \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) else: # Replace the posting's units and cost values. match = matches[0] sign = -1 if posting.units.number < ZERO else 1 number = min(abs(match.units.number), abs(posting.units.number)) match_units = Amount(number * sign, match.units.currency) booked_reductions.append(posting._replace(units=match_units, cost=match.cost)) booked_matches.append(match) insufficient = (match_units.number != posting.units.number) return booked_reductions, booked_matches, errors, insufficient","title":"booking_method_STRICT()"},{"location":"api_reference/beancount.parser.html#beancount.parser.booking_method.handle_ambiguous_matches","text":"Handle ambiguous matches by dispatching to a particular method. Parameters: entry \u2013 The parent Transaction instance. posting \u2013 An instance of Posting, the reducing posting which we're attempting to match. matches \u2013 A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods \u2013 A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions \u2013 A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. Source code in beancount/parser/booking_method.py def handle_ambiguous_matches(entry, posting, matches, method): \"\"\"Handle ambiguous matches by dispatching to a particular method. Args: entry: The parent Transaction instance. posting: An instance of Posting, the reducing posting which we're attempting to match. matches: A list of matching Position instances from the ante-inventory. Those positions are known to already match the 'posting' spec. methods: A mapping of account name to their corresponding booking method. Returns: A pair of booked_reductions: A list of matched Posting instances, whose 'cost' attributes are ensured to be of type Cost. errors: A list of errors to be generated. \"\"\" assert isinstance(method, Booking), ( \"Invalid type: {}\".format(method)) assert matches, \"Internal error: Invalid call with no matches\" #method = globals()['booking_method_{}'.format(method.name)] method = _BOOKING_METHODS[method] (booked_reductions, booked_matches, errors, insufficient) = method(entry, posting, matches) if insufficient: errors.append( AmbiguousMatchError(entry.meta, 'Not enough lots to reduce \"{}\": {}'.format( position.to_string(posting), ', '.join(position.to_string(match_posting) for match_posting in matches)), entry)) return booked_reductions, booked_matches, errors","title":"handle_ambiguous_matches()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest","text":"Support utilities for testing scripts.","title":"cmptest"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.TestError","text":"Errors within the test implementation itself. These should never occur.","title":"TestError"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertEqualEntries","text":"Compare two lists of entries exactly and print missing entries verbosely if they occur. Parameters: expected_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries \u2013 Same treatment as expected_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertEqualEntries(expected_entries, actual_entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Compare two lists of entries exactly and print missing entries verbosely if they occur. Args: expected_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. actual_entries: Same treatment as expected_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" expected_entries = read_string_or_entries(expected_entries, allow_incomplete) actual_entries = read_string_or_entries(actual_entries, allow_incomplete) same, expected_missing, actual_missing = compare.compare_entries(expected_entries, actual_entries) if not same: assert expected_missing or actual_missing, \"Missing is missing: {}, {}\".format( expected_missing, actual_missing) oss = io.StringIO() if expected_missing: oss.write(\"Present in expected set and not in actual set:\\n\\n\") for entry in expected_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') if actual_missing: oss.write(\"Present in actual set and not in expected set:\\n\\n\") for entry in actual_missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertEqualEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertExcludesEntries","text":"Check that subset_entries is not included in entries and print extra entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertExcludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is not included in entries and print extra entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) excludes, extra = compare.excludes_entries(subset_entries, entries) if not excludes: assert extra, \"Extra is empty: {}\".format(extra) oss = io.StringIO() if extra: oss.write(\"Extra from from first/excluded set:\\n\\n\") for entry in extra: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertExcludesEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.assertIncludesEntries","text":"Check that subset_entries is included in entries and print missing entries. Parameters: subset_entries \u2013 Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries \u2013 Same treatment as subset_entries, the other list of directives to compare to. failfunc \u2013 A function to call on failure. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/parser/cmptest.py def assertIncludesEntries(subset_entries, entries, failfunc=DEFAULT_FAILFUNC, allow_incomplete=False): \"\"\"Check that subset_entries is included in entries and print missing entries. Args: subset_entries: Either a list of directives or a string, in which case the string is run through beancount.parser.parse_string() and the resulting list is used. entries: Same treatment as subset_entries, the other list of directives to compare to. failfunc: A function to call on failure. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Raises: AssertionError: If the exception fails. \"\"\" subset_entries = read_string_or_entries(subset_entries, allow_incomplete) entries = read_string_or_entries(entries) includes, missing = compare.includes_entries(subset_entries, entries) if not includes: assert missing, \"Missing is empty: {}\".format(missing) oss = io.StringIO() if missing: oss.write(\"Missing from from expected set:\\n\\n\") for entry in missing: oss.write(printer.format_entry(entry)) oss.write('\\n') failfunc(oss.getvalue())","title":"assertIncludesEntries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.cmptest.read_string_or_entries","text":"Read a string of entries or just entries. Parameters: entries_or_str \u2013 Either a list of directives, or a string containing directives. allow_incomplete \u2013 A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. Source code in beancount/parser/cmptest.py def read_string_or_entries(entries_or_str, allow_incomplete=False): \"\"\"Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. \"\"\" if isinstance(entries_or_str, str): entries, errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) if allow_incomplete: # Do a simplistic local conversion in order to call the comparison. entries = [_local_booking(entry) for entry in entries] else: # Don't accept incomplete entries either. if any(parser.is_entry_incomplete(entry) for entry in entries): raise TestError(\"Entries in assertions may not use interpolation.\") entries, booking_errors = booking.book(entries, options_map) errors = errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError(\"Unexpected errors in expected: {}\".format(oss.getvalue())) else: assert isinstance(entries_or_str, list), \"Expecting list: {}\".format(entries_or_str) entries = entries_or_str return entries","title":"read_string_or_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar","text":"Builder for Beancount grammar.","title":"grammar"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder","text":"A builder used by the lexer and grammar parser as callbacks to create the data objects corresponding to rules parsed from the input file.","title":"Builder"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.amount","text":"Process an amount grammar rule. Parameters: number \u2013 a Decimal instance, the number of the amount. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. Source code in beancount/parser/grammar.py def amount(self, number, currency): \"\"\"Process an amount grammar rule. Args: number: a Decimal instance, the number of the amount. currency: a currency object (a str, really, see CURRENCY above) Returns: An instance of Amount. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number, currency) return Amount(number, currency)","title":"amount()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.balance","text":"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to balance. amount \u2013 The expected amount, to be checked. tolerance \u2013 The tolerance number. kvlist \u2013 a list of KeyValue instances. Returns: A new Balance object. Source code in beancount/parser/grammar.py def balance(self, filename, lineno, date, account, amount, tolerance, kvlist): \"\"\"Process an assertion directive. We produce no errors here by default. We replace the failing ones in the routine that does the verification later one, that these have succeeded or failed. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to balance. amount: The expected amount, to be checked. tolerance: The tolerance number. kvlist: a list of KeyValue instances. Returns: A new Balance object. \"\"\" diff_amount = None meta = new_metadata(filename, lineno, kvlist) return Balance(meta, date, account, amount, tolerance, diff_amount)","title":"balance()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.build_grammar_error","text":"Build a grammar error and appends it to the list of pending errors. Parameters: filename \u2013 The current filename lineno \u2013 The current line number excvalue \u2013 The exception value, or a str, the message of the error. exc_type \u2013 An exception type, if an exception occurred. exc_traceback \u2013 A traceback object. Source code in beancount/parser/grammar.py def build_grammar_error(self, filename, lineno, exc_value, exc_type=None, exc_traceback=None): \"\"\"Build a grammar error and appends it to the list of pending errors. Args: filename: The current filename lineno: The current line number excvalue: The exception value, or a str, the message of the error. exc_type: An exception type, if an exception occurred. exc_traceback: A traceback object. \"\"\" if exc_type is not None: assert not isinstance(exc_value, str) strings = traceback.format_exception_only(exc_type, exc_value) tblist = traceback.extract_tb(exc_traceback) filename, lineno, _, __ = tblist[0] message = '{} ({}:{})'.format(strings[0], filename, lineno) else: message = str(exc_value) meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, message, None))","title":"build_grammar_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.close","text":"Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def close(self, filename, lineno, date, account, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Close(meta, date, account)","title":"close()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.commodity","text":"Process a close directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. currency \u2013 A string, the commodity being declared. kvlist \u2013 a list of KeyValue instances. Returns: A new Close object. Source code in beancount/parser/grammar.py def commodity(self, filename, lineno, date, currency, kvlist): \"\"\"Process a close directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. currency: A string, the commodity being declared. kvlist: a list of KeyValue instances. Returns: A new Close object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Commodity(meta, date, currency)","title":"commodity()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.compound_amount","text":"Process an amount grammar rule. Parameters: number_per \u2013 a Decimal instance, the number of the cost per share. number_total \u2013 a Decimal instance, the number of the cost over all shares. currency \u2013 a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. Source code in beancount/parser/grammar.py def compound_amount(self, number_per, number_total, currency): \"\"\"Process an amount grammar rule. Args: number_per: a Decimal instance, the number of the cost per share. number_total: a Decimal instance, the number of the cost over all shares. currency: a currency object (a str, really, see CURRENCY above) Returns: A triple of (Decimal, Decimal, currency string) to be processed further when creating the final per-unit cost number. \"\"\" # Update the mapping that stores the parsed precisions. # Note: This is relatively slow, adds about 70ms because of number.as_tuple(). self.dcupdate(number_per, currency) self.dcupdate(number_total, currency) # Note that we are not able to reduce the value to a number per-share # here because we only get the number of units in the full lot spec. return CompoundAmount(number_per, number_total, currency)","title":"compound_amount()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.cost_merge","text":"Create a 'merge cost' token. Source code in beancount/parser/grammar.py def cost_merge(self, _): \"\"\"Create a 'merge cost' token.\"\"\" return MERGE_COST","title":"cost_merge()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.cost_spec","text":"Process a cost_spec grammar rule. Parameters: cost_comp_list \u2013 A list of CompoundAmount, a datetime.date, or label ID strings. is_total \u2013 Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". Source code in beancount/parser/grammar.py def cost_spec(self, cost_comp_list, is_total): \"\"\"Process a cost_spec grammar rule. Args: cost_comp_list: A list of CompoundAmount, a datetime.date, or label ID strings. is_total: Assume only the total cost is specified; reject the # syntax, that is, no compound amounts may be specified. This is used to support the {{...}} syntax. Returns: A cost-info tuple of CompoundAmount, lot date and label string. Any of these may be set to a sentinel indicating \"unset\". \"\"\" if not cost_comp_list: return CostSpec(MISSING, None, MISSING, None, None, False) assert isinstance(cost_comp_list, list), ( \"Internal error in parser: {}\".format(cost_comp_list)) compound_cost = None date_ = None label = None merge = None for comp in cost_comp_list: if isinstance(comp, CompoundAmount): if compound_cost is None: compound_cost = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate cost: '{}'.\".format(comp), None)) elif isinstance(comp, date): if date_ is None: date_ = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate date: '{}'.\".format(comp), None)) elif comp is MERGE_COST: if merge is None: merge = True self.errors.append( ParserError(self.get_lexer_location(), \"Cost merging is not supported yet\", None)) else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate merge-cost spec\", None)) else: assert isinstance(comp, str), ( \"Currency component is not string: '{}'\".format(comp)) if label is None: label = comp else: self.errors.append( ParserError(self.get_lexer_location(), \"Duplicate label: '{}'.\".format(comp), None)) # If there was a cost_comp_list, thus a \"{...}\" cost basis spec, you must # indicate that by creating a CompoundAmount(), always. if compound_cost is None: number_per, number_total, currency = MISSING, None, MISSING else: number_per, number_total, currency = compound_cost if is_total: if number_total is not None: self.errors.append( ParserError( self.get_lexer_location(), (\"Per-unit cost may not be specified using total cost \" \"syntax: '{}'; ignoring per-unit cost\").format(compound_cost), None)) # Ignore per-unit number. number_per = ZERO else: # There's a single number specified; interpret it as a total cost. number_total = number_per number_per = ZERO if merge is None: merge = False return CostSpec(number_per, number_total, currency, date_, label, merge)","title":"cost_spec()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.custom","text":"Process a custom directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. dir_type \u2013 A string, a type for the custom directive being parsed. custom_values \u2013 A list of the various tokens seen on the same line. kvlist \u2013 a list of KeyValue instances. Returns: A new Custom object. Source code in beancount/parser/grammar.py def custom(self, filename, lineno, date, dir_type, custom_values, kvlist): \"\"\"Process a custom directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. dir_type: A string, a type for the custom directive being parsed. custom_values: A list of the various tokens seen on the same line. kvlist: a list of KeyValue instances. Returns: A new Custom object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Custom(meta, date, dir_type, custom_values)","title":"custom()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.custom_value","text":"Create a custom value object, along with its type. Parameters: value \u2013 One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. Source code in beancount/parser/grammar.py def custom_value(self, value, dtype=None): \"\"\"Create a custom value object, along with its type. Args: value: One of the accepted custom values. Returns: A pair of (value, dtype) where 'dtype' is the datatype is that of the value. \"\"\" if dtype is None: dtype = type(value) return ValueType(value, dtype)","title":"custom_value()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.dcupdate","text":"Update the display context. Source code in beancount/parser/grammar.py def dcupdate(self, number, currency): \"\"\"Update the display context.\"\"\" if isinstance(number, Decimal) and currency and currency is not MISSING: self._dcupdate(number, currency)","title":"dcupdate()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.document","text":"Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. account \u2013 an Account instance. document_filename \u2013 a str, the name of the document file. tags_links \u2013 The current TagsLinks accumulator. kvlist \u2013 a list of KeyValue instances. Returns: A new Document object. Source code in beancount/parser/grammar.py def document(self, filename, lineno, date, account, document_filename, tags_links, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. account: an Account instance. document_filename: a str, the name of the document file. tags_links: The current TagsLinks accumulator. kvlist: a list of KeyValue instances. Returns: A new Document object. \"\"\" meta = new_metadata(filename, lineno, kvlist) if not path.isabs(document_filename): document_filename = path.abspath(path.join(path.dirname(filename), document_filename)) tags, links = self.finalize_tags_links(tags_links.tags, tags_links.links) return Document(meta, date, account, document_filename, tags, links)","title":"document()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.event","text":"Process an event directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. event_type \u2013 a str, the name of the event type. description \u2013 a str, the event value, the contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Event object. Source code in beancount/parser/grammar.py def event(self, filename, lineno, date, event_type, description, kvlist): \"\"\"Process an event directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. event_type: a str, the name of the event type. description: a str, the event value, the contents. kvlist: a list of KeyValue instances. Returns: A new Event object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Event(meta, date, event_type, description)","title":"event()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.finalize","text":"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries \u2013 A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. Source code in beancount/parser/grammar.py def finalize(self): \"\"\"Finalize the parser, check for final errors and return the triple. Returns: A triple of entries: A list of parsed directives, which may need completion. errors: A list of errors, hopefully empty. options_map: A dict of options. \"\"\" # If the user left some tags unbalanced, issue an error. for tag in self.tags: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Unbalanced pushed tag: '{}'\".format(tag), None)) # If the user left some metadata unpopped, issue an error. for key, value_list in self.meta.items(): meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, ( \"Unbalanced metadata key '{}'; leftover metadata '{}'\").format( key, ', '.join(value_list)), None)) # Weave the commas option in the DisplayContext itself, so it propagates # everywhere it is used automatically. self.dcontext.set_commas(self.options['render_commas']) return (self.get_entries(), self.errors, self.get_options())","title":"finalize()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.finalize_tags_links","text":"Finally amend tags and links and return final objects to be inserted. Parameters: tags \u2013 A set of tag strings (warning: this gets mutated in-place). links \u2013 A set of link strings. Returns: A sanitized pair of (tags, links). Source code in beancount/parser/grammar.py def finalize_tags_links(self, tags, links): \"\"\"Finally amend tags and links and return final objects to be inserted. Args: tags: A set of tag strings (warning: this gets mutated in-place). links: A set of link strings. Returns: A sanitized pair of (tags, links). \"\"\" if self.tags: tags.update(self.tags) return (frozenset(tags) if tags else EMPTY_SET, frozenset(links) if links else EMPTY_SET)","title":"finalize_tags_links()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_entries","text":"Return the accumulated entries. Returns: A list of sorted directives. Source code in beancount/parser/grammar.py def get_entries(self): \"\"\"Return the accumulated entries. Returns: A list of sorted directives. \"\"\" return sorted(self.entries, key=data.entry_sortkey)","title":"get_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_invalid_account","text":"See base class. Source code in beancount/parser/grammar.py def get_invalid_account(self): \"\"\"See base class.\"\"\" return account.join(self.options['name_equity'], 'InvalidAccountName')","title":"get_invalid_account()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_long_string_maxlines","text":"See base class. Source code in beancount/parser/grammar.py def get_long_string_maxlines(self): \"\"\"See base class.\"\"\" return self.options['long_string_maxlines']","title":"get_long_string_maxlines()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.get_options","text":"Return the final options map. Returns: A dict of option names to options. Source code in beancount/parser/grammar.py def get_options(self): \"\"\"Return the final options map. Returns: A dict of option names to options. \"\"\" # Build and store the inferred DisplayContext instance. self.options['dcontext'] = self.dcontext # Add the full list of seen commodities. # # IMPORTANT: This is currently where the list of all commodities seen # from the parser lives. The # beancount.core.getters.get_commodities_map() routine uses this to # automatically generate a full list of directives. An alternative would # be to implement a plugin that enforces the generate of these # post-parsing so that they are always guaranteed to live within the # flow of entries. This would allow us to keep all the data in that list # of entries and to avoid depending on the options to store that output. self.options['commodities'] = self.commodities return self.options","title":"get_options()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.handle_list","text":"Handle a recursive list grammar rule, generically. Parameters: object_list \u2013 the current list of objects. new_object \u2013 the new object to be added. Returns: The new, updated list of objects. Source code in beancount/parser/grammar.py def handle_list(self, object_list, new_object): \"\"\"Handle a recursive list grammar rule, generically. Args: object_list: the current list of objects. new_object: the new object to be added. Returns: The new, updated list of objects. \"\"\" if object_list is None: object_list = [] if new_object is not None: object_list.append(new_object) return object_list","title":"handle_list()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.include","text":"Process an include directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. include_name \u2013 A string, the name of the file to include. Source code in beancount/parser/grammar.py def include(self, filename, lineno, include_filename): \"\"\"Process an include directive. Args: filename: current filename. lineno: current line number. include_name: A string, the name of the file to include. \"\"\" self.options['include'].append(include_filename)","title":"include()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.key_value","text":"Process a document directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account the document relates to. document_filename \u2013 A str, the name of the document file. Returns: A new KeyValue object. Source code in beancount/parser/grammar.py def key_value(self, key, value): \"\"\"Process a document directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account the document relates to. document_filename: A str, the name of the document file. Returns: A new KeyValue object. \"\"\" return KeyValue(key, value)","title":"key_value()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.note","text":"Process a note directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to attach the note to. comment \u2013 A str, the note's comments contents. kvlist \u2013 a list of KeyValue instances. Returns: A new Note object. Source code in beancount/parser/grammar.py def note(self, filename, lineno, date, account, comment, kvlist): \"\"\"Process a note directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to attach the note to. comment: A str, the note's comments contents. kvlist: a list of KeyValue instances. Returns: A new Note object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Note(meta, date, account, comment)","title":"note()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.open","text":"Process an open directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the name of the account. currencies \u2013 A list of constraint currencies. booking_str \u2013 A string, the booking method, or None if none was specified. kvlist \u2013 a list of KeyValue instances. Returns: A new Open object. Source code in beancount/parser/grammar.py def open(self, filename, lineno, date, account, currencies, booking_str, kvlist): \"\"\"Process an open directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the name of the account. currencies: A list of constraint currencies. booking_str: A string, the booking method, or None if none was specified. kvlist: a list of KeyValue instances. Returns: A new Open object. \"\"\" meta = new_metadata(filename, lineno, kvlist) error = False if booking_str: try: # Note: Somehow the 'in' membership operator is not defined on Enum. booking = Booking[booking_str] except KeyError: # If the per-account method is invalid, set it to the global # default method and continue. booking = self.options['booking_method'] error = True else: booking = None entry = Open(meta, date, account, currencies, booking) if error: self.errors.append(ParserError(meta, \"Invalid booking method: {}\".format(booking_str), entry)) return entry","title":"open()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.option","text":"Process an option directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. key \u2013 option's key (str) value \u2013 option's value Source code in beancount/parser/grammar.py def option(self, filename, lineno, key, value): \"\"\"Process an option directive. Args: filename: current filename. lineno: current line number. key: option's key (str) value: option's value \"\"\" if key not in self.options: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Invalid option: '{}'\".format(key), None)) elif key in options.READ_ONLY_OPTIONS: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Option '{}' may not be set\".format(key), None)) else: option_descriptor = options.OPTIONS[key] # Issue a warning if the option is deprecated. if option_descriptor.deprecated: assert isinstance(option_descriptor.deprecated, str), \"Internal error.\" meta = new_metadata(filename, lineno) self.errors.append( DeprecatedError(meta, option_descriptor.deprecated, None)) # Rename the option if it has an alias. if option_descriptor.alias: key = option_descriptor.alias option_descriptor = options.OPTIONS[key] # Convert the value, if necessary. if option_descriptor.converter: try: value = option_descriptor.converter(value) except ValueError as exc: meta = new_metadata(filename, lineno) self.errors.append( ParserError(meta, \"Error for option '{}': {}\".format(key, exc), None)) return option = self.options[key] if isinstance(option, list): # Append to a list of values. option.append(value) elif isinstance(option, dict): # Set to a dict of values. if not (isinstance(value, tuple) and len(value) == 2): self.errors.append( ParserError( meta, \"Error for option '{}': {}\".format(key, value), None)) return dict_key, dict_value = value option[dict_key] = dict_value elif isinstance(option, bool): # Convert to a boolean. if not isinstance(value, bool): value = (value.lower() in {'true', 'on'}) or (value == '1') self.options[key] = value else: # Set the value. self.options[key] = value # Refresh the list of valid account regexps as we go along. if key.startswith('name_'): # Update the set of valid account types. self.account_regexp = valid_account_regexp(self.options) elif key == 'insert_pythonpath': # Insert the PYTHONPATH to this file when and only if you # encounter this option. sys.path.insert(0, path.dirname(filename))","title":"option()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pad","text":"Process a pad directive. Parameters: filename \u2013 The current filename. lineno \u2013 The current line number. date \u2013 A datetime object. account \u2013 A string, the account to be padded. source_account \u2013 A string, the account to pad from. kvlist \u2013 a list of KeyValue instances. Returns: A new Pad object. Source code in beancount/parser/grammar.py def pad(self, filename, lineno, date, account, source_account, kvlist): \"\"\"Process a pad directive. Args: filename: The current filename. lineno: The current line number. date: A datetime object. account: A string, the account to be padded. source_account: A string, the account to pad from. kvlist: a list of KeyValue instances. Returns: A new Pad object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Pad(meta, date, account, source_account)","title":"pad()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pipe_deprecated_error","text":"Issue a 'Pipe deprecated' error. Parameters: filename \u2013 The current filename lineno \u2013 The current line number Source code in beancount/parser/grammar.py def pipe_deprecated_error(self, filename, lineno): \"\"\"Issue a 'Pipe deprecated' error. Args: filename: The current filename lineno: The current line number \"\"\" if self.options['allow_pipe_separator']: return meta = new_metadata(filename, lineno) self.errors.append( ParserSyntaxError(meta, \"Pipe symbol is deprecated.\", None))","title":"pipe_deprecated_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.plugin","text":"Process a plugin directive. Parameters: filename \u2013 current filename. lineno \u2013 current line number. plugin_name \u2013 A string, the name of the plugin module to import. plugin_config \u2013 A string or None, an optional configuration string to pass in to the plugin module. Source code in beancount/parser/grammar.py def plugin(self, filename, lineno, plugin_name, plugin_config): \"\"\"Process a plugin directive. Args: filename: current filename. lineno: current line number. plugin_name: A string, the name of the plugin module to import. plugin_config: A string or None, an optional configuration string to pass in to the plugin module. \"\"\" self.options['plugin'].append((plugin_name, plugin_config))","title":"plugin()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.popmeta","text":"Removed a key off the current set of stacks. Parameters: key \u2013 A string, a key to be removed from the meta dict. Source code in beancount/parser/grammar.py def popmeta(self, key): \"\"\"Removed a key off the current set of stacks. Args: key: A string, a key to be removed from the meta dict. \"\"\" try: if key not in self.meta: raise IndexError value_list = self.meta[key] value_list.pop(-1) if not value_list: self.meta.pop(key) except IndexError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent metadata key: '{}'\".format(key), None))","title":"popmeta()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.poptag","text":"Pop a tag off the current set of stacks. Parameters: tag \u2013 A string, a tag to be removed from the current set of tags. Source code in beancount/parser/grammar.py def poptag(self, tag): \"\"\"Pop a tag off the current set of stacks. Args: tag: A string, a tag to be removed from the current set of tags. \"\"\" try: self.tags.remove(tag) except ValueError: meta = new_metadata(self.options['filename'], 0) self.errors.append( ParserError(meta, \"Attempting to pop absent tag: '{}'\".format(tag), None))","title":"poptag()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.posting","text":"Process a posting grammar rule. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. account \u2013 A string, the account of the posting. units \u2013 An instance of Amount for the units. cost \u2013 An instance of CostSpec for the cost. price \u2013 Either None, or an instance of Amount that is the cost of the position. istotal \u2013 A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag \u2013 A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. Source code in beancount/parser/grammar.py def posting(self, filename, lineno, account, units, cost, price, istotal, flag): \"\"\"Process a posting grammar rule. Args: filename: the current filename. lineno: the current line number. account: A string, the account of the posting. units: An instance of Amount for the units. cost: An instance of CostSpec for the cost. price: Either None, or an instance of Amount that is the cost of the position. istotal: A bool, True if the price is for the total amount being parsed, or False if the price is for each lot of the position. flag: A string, one-character, the flag associated with this posting. Returns: A new Posting object, with no parent entry. \"\"\" meta = new_metadata(filename, lineno) # Prices may not be negative. if price and isinstance(price.number, Decimal) and price.number < ZERO: self.errors.append( ParserError(meta, ( \"Negative prices are not allowed: {} \" \"(see http://furius.ca/beancount/doc/bug-negative-prices \" \"for workaround)\" ).format(price), None)) # Fix it and continue. price = Amount(abs(price.number), price.currency) # If the price is specified for the entire amount, compute the effective # price here and forget about that detail of the input syntax. if istotal: if units.number == ZERO: number = ZERO else: number = price.number if number is not MISSING: number = number/abs(units.number) price = Amount(number, price.currency) # Note: Allow zero prices because we need them for round-trips for # conversion entries. # # if price is not None and price.number == ZERO: # self.errors.append( # ParserError(meta, \"Price is zero: {}\".format(price), None)) # If both cost and price are specified, the currencies must match, or # that is an error. if (cost is not None and price is not None and isinstance(cost.currency, str) and isinstance(price.currency, str) and cost.currency != price.currency): self.errors.append( ParserError(meta, \"Cost and price currencies must match: {} != {}\".format( cost.currency, price.currency), None)) return Posting(account, units, cost, price, chr(flag) if flag else None, meta)","title":"posting()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.price","text":"Process a price directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. currency \u2013 the currency to be priced. amount \u2013 an instance of Amount, that is the price of the currency. kvlist \u2013 a list of KeyValue instances. Returns: A new Price object. Source code in beancount/parser/grammar.py def price(self, filename, lineno, date, currency, amount, kvlist): \"\"\"Process a price directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. currency: the currency to be priced. amount: an instance of Amount, that is the price of the currency. kvlist: a list of KeyValue instances. Returns: A new Price object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Price(meta, date, currency, amount)","title":"price()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pushmeta","text":"Set a metadata field on the current key-value pairs to be added to transactions. Parameters: key_value \u2013 A KeyValue instance, to be added to the dict of metadata. Source code in beancount/parser/grammar.py def pushmeta(self, key, value): \"\"\"Set a metadata field on the current key-value pairs to be added to transactions. Args: key_value: A KeyValue instance, to be added to the dict of metadata. \"\"\" self.meta[key].append(value)","title":"pushmeta()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.pushtag","text":"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Parameters: tag \u2013 A string, a tag to be added. Source code in beancount/parser/grammar.py def pushtag(self, tag): \"\"\"Push a tag on the current set of tags. Note that this does not need to be stack ordered. Args: tag: A string, a tag to be added. \"\"\" self.tags.append(tag)","title":"pushtag()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.query","text":"Process a document directive. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. query_name \u2013 a str, the name of the query. query_string \u2013 a str, the SQL query itself. kvlist \u2013 a list of KeyValue instances. Returns: A new Query object. Source code in beancount/parser/grammar.py def query(self, filename, lineno, date, query_name, query_string, kvlist): \"\"\"Process a document directive. Args: filename: the current filename. lineno: the current line number. date: a datetime object. query_name: a str, the name of the query. query_string: a str, the SQL query itself. kvlist: a list of KeyValue instances. Returns: A new Query object. \"\"\" meta = new_metadata(filename, lineno, kvlist) return Query(meta, date, query_name, query_string)","title":"query()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.store_result","text":"Start rule stores the final result here. Parameters: entries \u2013 A list of entries to store. Source code in beancount/parser/grammar.py def store_result(self, entries): \"\"\"Start rule stores the final result here. Args: entries: A list of entries to store. \"\"\" if entries: self.entries = entries","title":"store_result()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_LINK","text":"Add a link to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. link \u2013 A string, the new link to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_LINK(self, tags_links, link): \"\"\"Add a link to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. link: A string, the new link to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.links.add(link) return tags_links","title":"tag_link_LINK()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_STRING","text":"Add a string to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. string \u2013 A string, the new string to insert in the list. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_STRING(self, tags_links, string): \"\"\"Add a string to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. string: A string, the new string to insert in the list. Returns: An updated TagsLinks instance. \"\"\" tags_links.strings.append(string) return tags_links","title":"tag_link_STRING()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_TAG","text":"Add a tag to the TagsLinks accumulator. Parameters: tags_links \u2013 The current TagsLinks accumulator. tag \u2013 A string, the new tag to insert. Returns: An updated TagsLinks instance. Source code in beancount/parser/grammar.py def tag_link_TAG(self, tags_links, tag): \"\"\"Add a tag to the TagsLinks accumulator. Args: tags_links: The current TagsLinks accumulator. tag: A string, the new tag to insert. Returns: An updated TagsLinks instance. \"\"\" tags_links.tags.add(tag) return tags_links","title":"tag_link_TAG()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.tag_link_new","text":"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. Source code in beancount/parser/grammar.py def tag_link_new(self, _): \"\"\"Create a new TagsLinks instance. Returns: An instance of TagsLinks, initialized with expected attributes. \"\"\" return TagsLinks(set(), set())","title":"tag_link_new()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.transaction","text":"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Parameters: filename \u2013 the current filename. lineno \u2013 the current line number. date \u2013 a datetime object. flag \u2013 a str, one-character, the flag associated with this transaction. txn_strings \u2013 A list of strings, possibly empty, possibly longer. tags_links \u2013 A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list \u2013 a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. Source code in beancount/parser/grammar.py def transaction(self, filename, lineno, date, flag, txn_strings, tags_links, posting_or_kv_list): \"\"\"Process a transaction directive. All the postings of the transaction are available at this point, and so the the transaction is balanced here, incomplete postings are completed with the appropriate position, and errors are being accumulated on the builder to be reported later on. This is the main routine that takes up most of the parsing time; be very careful with modifications here, they have an impact on performance. Args: filename: the current filename. lineno: the current line number. date: a datetime object. flag: a str, one-character, the flag associated with this transaction. txn_strings: A list of strings, possibly empty, possibly longer. tags_links: A TagsLinks namedtuple of tags, and/or links. posting_or_kv_list: a list of Posting or KeyValue instances, to be inserted in this transaction, or None, if no postings have been declared. Returns: A new Transaction object. \"\"\" meta = new_metadata(filename, lineno) # Separate postings and key-values. explicit_meta = {} postings = [] tags, links = tags_links.tags, tags_links.links if posting_or_kv_list: last_posting = None for posting_or_kv in posting_or_kv_list: if isinstance(posting_or_kv, Posting): postings.append(posting_or_kv) last_posting = posting_or_kv elif isinstance(posting_or_kv, TagsLinks): if postings: self.errors.append(ParserError( meta, \"Tags or links not allowed after first \" + \"Posting: {}\".format(posting_or_kv), None)) else: tags.update(posting_or_kv.tags) links.update(posting_or_kv.links) else: if last_posting is None: value = explicit_meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate metadata field on entry: {}\".format( posting_or_kv), None)) else: if last_posting.meta is None: last_posting = last_posting._replace(meta={}) postings.pop(-1) postings.append(last_posting) value = last_posting.meta.setdefault(posting_or_kv.key, posting_or_kv.value) if value is not posting_or_kv.value: self.errors.append(ParserError( meta, \"Duplicate posting metadata field: {}\".format( posting_or_kv), None)) # Freeze the tags & links or set to default empty values. tags, links = self.finalize_tags_links(tags, links) # Initialize the metadata fields from the set of active values. if self.meta: for key, value_list in self.meta.items(): meta[key] = value_list[-1] # Add on explicitly defined values. if explicit_meta: meta.update(explicit_meta) # Unpack the transaction fields. payee_narration = self.unpack_txn_strings(txn_strings, meta) if payee_narration is None: return None payee, narration = payee_narration # We now allow a single posting when its balance is zero, so we # commented out the check below. If a transaction has a single posting # with a non-zero balance, it'll get caught below in the booking code. # # # Detect when a transaction does not have at least two legs. # if postings is None or len(postings) < 2: # self.errors.append( # ParserError(meta, # \"Transaction with only one posting: {}\".format(postings), # None)) # return None # If there are no postings, make sure we insert a list object. if postings is None: postings = [] # Create the transaction. return Transaction(meta, date, chr(flag), payee, narration, tags, links, postings)","title":"transaction()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.Builder.unpack_txn_strings","text":"Unpack a tags_links accumulator to its payee and narration fields. Parameters: txn_strings \u2013 A list of strings. meta \u2013 A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. Source code in beancount/parser/grammar.py def unpack_txn_strings(self, txn_strings, meta): \"\"\"Unpack a tags_links accumulator to its payee and narration fields. Args: txn_strings: A list of strings. meta: A metadata dict for errors generated in this routine. Returns: A pair of (payee, narration) strings or None objects, or None, if there was an error. \"\"\" num_strings = 0 if txn_strings is None else len(txn_strings) if num_strings == 1: payee, narration = None, txn_strings[0] elif num_strings == 2: payee, narration = txn_strings elif num_strings == 0: payee, narration = None, \"\" else: self.errors.append( ParserError(meta, \"Too many strings on transaction description: {}\".format( txn_strings), None)) return None return payee, narration","title":"unpack_txn_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount","text":"CompoundAmount(number_per, number_total, currency)","title":"CompoundAmount"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__new__","text":"Create new instance of CompoundAmount(number_per, number_total, currency)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.CompoundAmount.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError","text":"DeprecatedError(source, message, entry)","title":"DeprecatedError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__new__","text":"Create new instance of DeprecatedError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.DeprecatedError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue","text":"KeyValue(key, value)","title":"KeyValue"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__new__","text":"Create new instance of KeyValue(key, value)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.KeyValue.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError","text":"ParserError(source, message, entry)","title":"ParserError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__new__","text":"Create new instance of ParserError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError","text":"ParserSyntaxError(source, message, entry)","title":"ParserSyntaxError"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__new__","text":"Create new instance of ParserSyntaxError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ParserSyntaxError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks","text":"TagsLinks(tags, links)","title":"TagsLinks"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__new__","text":"Create new instance of TagsLinks(tags, links)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.TagsLinks.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType","text":"ValueType(value, dtype)","title":"ValueType"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/grammar.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__new__","text":"Create new instance of ValueType(value, dtype)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.ValueType.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/grammar.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.grammar.valid_account_regexp","text":"Build a regexp to validate account names from the options. Parameters: options \u2013 A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. Source code in beancount/parser/grammar.py def valid_account_regexp(options): \"\"\"Build a regexp to validate account names from the options. Args: options: A dict of options, as per beancount.parser.options. Returns: A string, a regular expression that will match all account names. \"\"\" names = map(options.__getitem__, ('name_assets', 'name_liabilities', 'name_equity', 'name_income', 'name_expenses')) # Replace the first term of the account regular expression with the specific # names allowed under the options configuration. This code is kept in sync # with {5672c7270e1e}. return re.compile(\"(?:{})(?:{}{})+\".format('|'.join(names), account.sep, account.ACC_COMP_NAME_RE))","title":"valid_account_regexp()"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc","text":"Compute a hash of the source files in order to warn when the source goes out of date.","title":"hashsrc"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc.check_parser_source_files","text":"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). Source code in beancount/parser/hashsrc.py def check_parser_source_files(): \"\"\"Check the extension module's source hash and issue a warning if the current source differs from that of the module. If the source files aren't located in the Python source directory, ignore the warning, we're probably running this from an installed based, in which case we don't need to check anything (this check is useful only for people running directly from source). \"\"\" parser_source_hash = hash_parser_source_files() if parser_source_hash is None: return # pylint: disable=import-outside-toplevel from . import _parser if _parser.SOURCE_HASH and _parser.SOURCE_HASH != parser_source_hash: warnings.warn( (\"The Beancount parser C extension module is out-of-date ('{}' != '{}'). \" \"You need to rebuild.\").format(_parser.SOURCE_HASH, parser_source_hash))","title":"check_parser_source_files()"},{"location":"api_reference/beancount.parser.html#beancount.parser.hashsrc.hash_parser_source_files","text":"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. Source code in beancount/parser/hashsrc.py def hash_parser_source_files(): \"\"\"Compute a unique hash of the parser's Python code in order to bake that into the extension module. This is used at load-time to verify that the extension module and the corresponding Python codes match each other. If not, it issues a warning that you should rebuild your extension module. Returns: A string, the hexadecimal unique hash of relevant source code that should trigger a recompilation. \"\"\" md5 = hashlib.md5() for filename in PARSER_SOURCE_FILES: fullname = path.join(path.dirname(__file__), filename) if not path.exists(fullname): return None with open(fullname, 'rb') as file: md5.update(file.read()) # Note: Prepend a character in front of the hash because under Windows MSDEV # removes escapes, and if the hash starts with a number it fails to # recognize this is a string. A small compromise for portability. return md5.hexdigest()","title":"hash_parser_source_files()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer","text":"Beancount syntax lexer.","title":"lexer"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder","text":"A builder used only for building lexer objects. Attributes: Name Type Description long_string_maxlines_default Number of lines for a string to trigger a warning. This is meant to help users detecting dangling quotes in their source.","title":"LexBuilder"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.ACCOUNT","text":"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Parameters: account_name \u2013 a str, the valid name of an account. Returns: A string, the name of the account. Source code in beancount/parser/lexer.py def ACCOUNT(self, account_name): \"\"\"Process an ACCOUNT token. This function attempts to reuse an existing account if one exists, otherwise creates one on-demand. Args: account_name: a str, the valid name of an account. Returns: A string, the name of the account. \"\"\" # Check account name validity. if not self.account_regexp.match(account_name): raise ValueError(\"Invalid account name: {}\".format(account_name)) # Reuse (intern) account strings as much as possible. This potentially # reduces memory usage a fair bit, because these strings are repeated # liberally. return self.accounts.setdefault(account_name, account_name)","title":"ACCOUNT()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.CURRENCY","text":"Process a CURRENCY token. Parameters: currency_name \u2013 the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. Source code in beancount/parser/lexer.py def CURRENCY(self, currency_name): \"\"\"Process a CURRENCY token. Args: currency_name: the name of the currency. Returns: A new currency object; for now, these are simply represented as the currency name. \"\"\" self.commodities.add(currency_name) return currency_name","title":"CURRENCY()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.DATE","text":"Process a DATE token. Parameters: year \u2013 integer year. month \u2013 integer month. day \u2013 integer day Returns: A new datetime object. Source code in beancount/parser/lexer.py def DATE(self, year, month, day): \"\"\"Process a DATE token. Args: year: integer year. month: integer month. day: integer day Returns: A new datetime object. \"\"\" return datetime.date(year, month, day)","title":"DATE()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.KEY","text":"Process an identifier token. Parameters: ident \u2013 a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def KEY(self, ident): \"\"\"Process an identifier token. Args: ident: a str, the name of the key string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return ident","title":"KEY()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.LINK","text":"Process a LINK token. Parameters: link \u2013 a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. Source code in beancount/parser/lexer.py def LINK(self, link): \"\"\"Process a LINK token. Args: link: a str, the name of the string. Returns: The link string itself. For now we don't need to represent this by an object. \"\"\" return link","title":"LINK()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.NUMBER","text":"Process a NUMBER token. Convert into Decimal. Parameters: number \u2013 a str, the number to be converted. Returns: A Decimal instance built of the number string. Source code in beancount/parser/lexer.py def NUMBER(self, number): \"\"\"Process a NUMBER token. Convert into Decimal. Args: number: a str, the number to be converted. Returns: A Decimal instance built of the number string. \"\"\" # Note: We don't use D() for efficiency here. # The lexer will only yield valid number strings. if ',' in number: # Extract the integer part and check the commas match the # locale-aware formatted version. This match = re.match(r\"([\\d,]*)(\\.\\d*)?$\", number) if not match: # This path is never taken because the lexer will parse a comma # in the fractional part as two NUMBERs with a COMMA token in # between. self.errors.append( LexerError(self.get_lexer_location(), \"Invalid number format: '{}'\".format(number), None)) else: int_string, float_string = match.groups() reformatted_number = r\"{:,.0f}\".format(int(int_string.replace(\",\", \"\"))) if int_string != reformatted_number: self.errors.append( LexerError(self.get_lexer_location(), \"Invalid commas: '{}'\".format(number), None)) number = number.replace(',', '') return Decimal(number)","title":"NUMBER()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.STRING","text":"Process a STRING token. Parameters: string \u2013 the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. Source code in beancount/parser/lexer.py def STRING(self, string): \"\"\"Process a STRING token. Args: string: the string to process. Returns: The string. Nothing to be done or cleaned up. Eventually we might do some decoding here. \"\"\" # If a multiline string, warm over a certain number of lines. if '\\n' in string: num_lines = string.count('\\n') + 1 if num_lines > self.long_string_maxlines_default: # This is just a warning; accept the string anyhow. self.errors.append( LexerError( self.get_lexer_location(), \"String too long ({} lines); possible error\".format(num_lines), None)) return string","title":"STRING()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.TAG","text":"Process a TAG token. Parameters: tag \u2013 a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. Source code in beancount/parser/lexer.py def TAG(self, tag): \"\"\"Process a TAG token. Args: tag: a str, the tag to be processed. Returns: The tag string itself. For now we don't need an object to represent those; keeping it simple. \"\"\" return tag","title":"TAG()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.build_lexer_error","text":"Build a lexer error and appends it to the list of pending errors. Parameters: message \u2013 The message of the error. exc_type \u2013 An exception type, if an exception occurred. Source code in beancount/parser/lexer.py def build_lexer_error(self, message, exc_type=None): # {0e31aeca3363} \"\"\"Build a lexer error and appends it to the list of pending errors. Args: message: The message of the error. exc_type: An exception type, if an exception occurred. \"\"\" if not isinstance(message, str): message = str(message) if exc_type is not None: message = '{}: {}'.format(exc_type.__name__, message) self.errors.append( LexerError(self.get_lexer_location(), message, None))","title":"build_lexer_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexBuilder.get_invalid_account","text":"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. Source code in beancount/parser/lexer.py def get_invalid_account(self): \"\"\"Return the name of an invalid account placeholder. When an account name is not deemed a valid one, replace it by this account name. This can be overridden by the parser to take into account the options. Returns: A string, the name of the root/type for invalid account names. \"\"\" return 'Equity:InvalidAccountName'","title":"get_invalid_account()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError","text":"LexerError(source, message, entry)","title":"LexerError"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/lexer.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__new__","text":"Create new instance of LexerError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.LexerError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/lexer.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.lex_iter","text":"An iterator that yields all the tokens in the given file. Parameters: file \u2013 A string, the filename to run the lexer on, or a file object. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). Source code in beancount/parser/lexer.py def lex_iter(file, builder=None, encoding=None): \"\"\"An iterator that yields all the tokens in the given file. Args: file: A string, the filename to run the lexer on, or a file object. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Yields: Tuples of the token (a string), the matched text (a string), and the line no (an integer). \"\"\" if isinstance(file, str): filename = file else: filename = file.name if builder is None: builder = LexBuilder() _parser.lexer_initialize(filename, builder, encoding) try: while 1: token_tuple = _parser.lexer_next() if token_tuple is None: break yield token_tuple finally: _parser.lexer_finalize()","title":"lex_iter()"},{"location":"api_reference/beancount.parser.html#beancount.parser.lexer.lex_iter_string","text":"Parse an input string and print the tokens to an output file. Parameters: input_string \u2013 a str or bytes, the contents of the ledger to be parsed. builder \u2013 A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding \u2013 A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. Source code in beancount/parser/lexer.py def lex_iter_string(string, builder=None, encoding=None): \"\"\"Parse an input string and print the tokens to an output file. Args: input_string: a str or bytes, the contents of the ledger to be parsed. builder: A builder of your choice. If not specified, a LexBuilder is used and discarded (along with its errors). encoding: A string (or None), the default encoding to use for strings. Returns: A iterator on the string. See lex_iter() for details. \"\"\" tmp_file = tempfile.NamedTemporaryFile('w' if isinstance(string, str) else 'wb') tmp_file.write(string) tmp_file.flush() # Note: We pass in the file object in order to keep it alive during parsing. return lex_iter(tmp_file, builder, encoding)","title":"lex_iter_string()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options","text":"Declaration of options and their default values.","title":"options"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc","text":"OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"OptDesc"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__new__","text":"Create new instance of OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptDesc.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup","text":"OptGroup(description, options)","title":"OptGroup"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/parser/options.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__new__","text":"Create new instance of OptGroup(description, options)","title":"__new__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.OptGroup.__repr__","text":"Return a nicely formatted representation string Source code in beancount/parser/options.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.Opt","text":"Alternative constructor for OptDesc, with default values. Parameters: name \u2013 See OptDesc. default_value \u2013 See OptDesc. example_value \u2013 See OptDesc. converter \u2013 See OptDesc. deprecated \u2013 See OptDesc. alias \u2013 See OptDesc. Returns: An instance of OptDesc. Source code in beancount/parser/options.py def Opt(name, default_value, example_value=UNSET, converter=None, deprecated=False, alias=None): \"\"\"Alternative constructor for OptDesc, with default values. Args: name: See OptDesc. default_value: See OptDesc. example_value: See OptDesc. converter: See OptDesc. deprecated: See OptDesc. alias: See OptDesc. Returns: An instance of OptDesc. \"\"\" if example_value is UNSET: example_value = default_value return OptDesc(name, default_value, example_value, converter, deprecated, alias)","title":"Opt()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_account_types","text":"Extract the account type names from the parser's options. Parameters: options \u2013 a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. Source code in beancount/parser/options.py def get_account_types(options): \"\"\"Extract the account type names from the parser's options. Args: options: a dict of ledger options. Returns: An instance of AccountTypes, that contains all the prefixes. \"\"\" return account_types.AccountTypes( *[options[key] for key in (\"name_assets\", \"name_liabilities\", \"name_equity\", \"name_income\", \"name_expenses\")])","title":"get_account_types()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_current_accounts","text":"Return account names for the current earnings and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. Source code in beancount/parser/options.py def get_current_accounts(options): \"\"\"Return account names for the current earnings and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 2 account objects, one for booking current earnings, and one for current conversions. \"\"\" equity = options['name_equity'] account_current_earnings = account.join(equity, options['account_current_earnings']) account_current_conversions = account.join(equity, options['account_current_conversions']) return (account_current_earnings, account_current_conversions)","title":"get_current_accounts()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.get_previous_accounts","text":"Return account names for the previous earnings, balances and conversion accounts. Parameters: options \u2013 a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. Source code in beancount/parser/options.py def get_previous_accounts(options): \"\"\"Return account names for the previous earnings, balances and conversion accounts. Args: options: a dict of ledger options. Returns: A tuple of 3 account objects, for booking previous earnings, previous balances, and previous conversions. \"\"\" equity = options['name_equity'] account_previous_earnings = account.join(equity, options['account_previous_earnings']) account_previous_balances = account.join(equity, options['account_previous_balances']) account_previous_conversions = account.join(equity, options['account_previous_conversions']) return (account_previous_earnings, account_previous_balances, account_previous_conversions)","title":"get_previous_accounts()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.list_options","text":"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. Source code in beancount/parser/options.py def list_options(): \"\"\"Produce a formatted text of the available options and their description. Returns: A string, formatted nicely to be printed in 80 columns. \"\"\" oss = io.StringIO() for group in PUBLIC_OPTION_GROUPS: for desc in group.options: oss.write('option \"{}\" \"{}\"\\n'.format(desc.name, desc.example_value)) if desc.deprecated: oss.write(textwrap.fill( \"THIS OPTION IS DEPRECATED: {}\".format(desc.deprecated), initial_indent=\" \", subsequent_indent=\" \")) oss.write('\\n\\n') description = ' '.join(line.strip() for line in group.description.strip().splitlines()) oss.write(textwrap.fill(description, initial_indent=' ', subsequent_indent=' ')) oss.write('\\n') if isinstance(desc.default_value, (list, dict, set)): oss.write('\\n') oss.write(' (This option may be supplied multiple times.)\\n') oss.write('\\n\\n') return oss.getvalue()","title":"list_options()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_booking_method","text":"Validate a booking method name. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_booking_method(value): \"\"\"Validate a booking method name. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" try: return data.Booking[value] except KeyError as exc: raise ValueError(str(exc))","title":"options_validate_booking_method()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_boolean","text":"Validate a boolean option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_boolean(value): \"\"\"Validate a boolean option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return value.lower() in ('1', 'true', 'yes')","title":"options_validate_boolean()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_plugin","text":"Validate the plugin option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_plugin(value): \"\"\"Validate the plugin option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the 'plugin' option specially: accept an optional # argument from it. NOTE: We will eventually phase this out and # replace it by a dedicated 'plugin' directive. match = re.match('(.*):(.*)', value) if match: plugin_name, plugin_config = match.groups() else: plugin_name, plugin_config = value, None return (plugin_name, plugin_config)","title":"options_validate_plugin()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_processing_mode","text":"Validate the options processing mode. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_processing_mode(value): \"\"\"Validate the options processing mode. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" if value not in ('raw', 'default'): raise ValueError(\"Invalid value '{}'\".format(value)) return value","title":"options_validate_processing_mode()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_tolerance","text":"Validate the tolerance option. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance(value): \"\"\"Validate the tolerance option. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" return D(value)","title":"options_validate_tolerance()"},{"location":"api_reference/beancount.parser.html#beancount.parser.options.options_validate_tolerance_map","text":"Validate an option with a map of currency/tolerance pairs in a string. Parameters: value \u2013 A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Exceptions: ValueError \u2013 If the value is invalid. Source code in beancount/parser/options.py def options_validate_tolerance_map(value): \"\"\"Validate an option with a map of currency/tolerance pairs in a string. Args: value: A string, the value provided as option. Returns: The new value, converted, if the conversion is successful. Raises: ValueError: If the value is invalid. \"\"\" # Process the setting of a key-value, whereby the value is a Decimal # representation. match = re.match('(.*):(.*)', value) if not match: raise ValueError(\"Invalid value '{}'\".format(value)) currency, tolerance_str = match.groups() return (currency, D(tolerance_str))","title":"options_validate_tolerance_map()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser","text":"Beancount syntax parser. IMPORTANT: The parser (and its grammar builder) produces \"incomplete\" Transaction objects. This means that some of the data can be found missing from the output of the parser and some of the data types vary slightly. Missing components are replaced not by None, but by a special constant 'NA' which helps diagnose problems if a user inadvertently attempts to work on an incomplete posting instead of a complete one. Those incomplete entries are then run through the \"booking\" routines which do two things simultaneously: They find matching lots for reducing inventory positions, and They interpolate missing numbers. In doing so they normalize the entries to \"complete\" entries by converting a position/lot's \"cost\" attribute from a CostSpec to a Cost. A Cost is similar to an Amount in that it shares \"number\" and \"currency\" attributes, but also has a label and a lot date. A CostSpec is similar to a Cost, but has all optional data; it consists in a specification for matching against a particular inventory lot. Other parts of a posting may also be missing, not just parts of the cost. Leaving out some parts of the input is used to invoke interpolation, to tell Beancount to automatically compute the missing numbers (if possible). Missing components will be set to the special value \"beancount.core.number.MISSING\" until inventory booking and number interpolation has been completed. The \"MISSING\" value should never appear in completed, loaded transaction postings. For instance, all of the units may be missing: INPUT: Assets:Account posting.units = MISSING Or just the number of the units: INPUT: Assets:Account USD posting.units = Amount(MISSING, \"USD\") You must always specify the currency. If a price annotation is simply absent, it appears as None: INPUT: Assets:Account 2 MXN posting.price = None However, you may indicate that there is a price but have Beancount compute it automatically: INPUT: Assets:Account 2 MXN @ posting.price = Amount(MISSING, MISSING) Indicating the conversion currency is also possible (and recommended): INPUT: Assets:Account 2 MXN @ USD posting.price = Amount(MISSING, \"USD\") If a cost specification is provided, a \"cost\" attribute it set but it does not refer to a Cost instance (as in complete entries) but rather to a CostSpec instance. Some of the fields of a CostSpec may be MISSING if they were not specified in the input. For example: INPUT: Assets:Account 1 HOOL {100 # 5 USD} posting.cost = CostSpec(Decimal(\"100\"), Decimal(\"5\"), \"USD\", None, None, False)) Note how we never consider the label of date override to be MISSING; this is because those inputs are optional: A missing label is simply left unset in the computed Cost, and a missing date override uses the date of the transaction that contains the posting. You can indicate that there is a total number to be filled in like this: INPUT: Assets:Account 1 HOOL {100 # USD} posting.cost = CostSpec(Decimal(\"100\"), MISSING, \"USD\", None, None, False)) This is in contrast to the total value simple not being used: INPUT: Assets:Account 1 HOOL {100 USD} posting.cost = CostSpec(Decimal(\"100\"), None, \"USD\", None, None, False)) Both per-unit and total numbers may be omitted as well, in which case, only the number-per-unit portion of the CostSpec will appear as MISSING: INPUT: Assets:Account 1 HOOL {USD} posting.cost = CostSpec(MISSING, None, \"USD\", None, None, False)) And furthermore, all the cost basis may be missing: INPUT: Assets:Account 1 HOOL {} posting.cost = CostSpec(MISSING, None, MISSING, None, None, False)) If you ask for the lots to be merged, you get this: INPUT: Assets:Account 1 HOOL {*} posting.cost = CostSpec(MISSING, None, MISSING, None, None, True)) The numbers have to be computed by Beancount, so we output this with MISSING values. Of course, you can provide only the non-basis information, like just the date or label: INPUT: Assets:Account 1 HOOL {2015-09-21} posting.cost = CostSpec(MISSING, None, MISSING, date(2015, 9, 21), None, True) See the test beancount.parser.grammar_test.TestIncompleteInputs for examples and corresponding expected values.","title":"parser"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.is_entry_incomplete","text":"Detect the presence of elided amounts in Transactions. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_entry_incomplete(entry): \"\"\"Detect the presence of elided amounts in Transactions. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" if isinstance(entry, data.Transaction): if any(is_posting_incomplete(posting) for posting in entry.postings): return True return False","title":"is_entry_incomplete()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.is_posting_incomplete","text":"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Parameters: entries \u2013 A directive. Returns: A boolean, true if there are some missing portions of any postings found. Source code in beancount/parser/parser.py def is_posting_incomplete(posting): \"\"\"Detect the presence of any elided amounts in a Posting. If any of the possible amounts are missing, this returns True. Args: entries: A directive. Returns: A boolean, true if there are some missing portions of any postings found. \"\"\" units = posting.units if (units is MISSING or units.number is MISSING or units.currency is MISSING): return True price = posting.price if (price is MISSING or price is not None and (price.number is MISSING or price.currency is MISSING)): return True cost = posting.cost if cost is not None and (cost.number_per is MISSING or cost.number_total is MISSING or cost.currency is MISSING): return True return False","title":"is_posting_incomplete()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_doc","text":"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Parameters: expect_errors \u2013 A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete \u2013 A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. Source code in beancount/parser/parser.py def parse_doc(expect_errors=False, allow_incomplete=False): \"\"\"Factory of decorators that parse the function's docstring as an argument. Note that the decorators thus generated only run the parser on the tests, not the loader, so is no validation, balance checks, nor plugins applied to the parsed text. Args: expect_errors: A boolean or None, with the following semantics, True: Expect errors and fail if there are none. False: Expect no errors and fail if there are some. None: Do nothing, no check. allow_incomplete: A boolean, if true, allow incomplete input. Otherwise barf if the input would require interpolation. The default value is set not to allow it because we want to minimize the features tests depend on. Returns: A decorator for test functions. \"\"\" def decorator(fun): \"\"\"A decorator that parses the function's docstring as an argument. Args: fun: the function object to be decorated. Returns: A decorated test function. \"\"\" filename = inspect.getfile(fun) lines, lineno = inspect.getsourcelines(fun) # decorator line + function definition line (I realize this is largely # imperfect, but it's only for reporting in our tests) - empty first line # stripped away. lineno += 1 @functools.wraps(fun) def wrapper(self): assert fun.__doc__ is not None, ( \"You need to insert a docstring on {}\".format(fun.__name__)) entries, errors, options_map = parse_string(fun.__doc__, report_filename=filename, report_firstline=lineno, dedent=True) if not allow_incomplete and any(is_entry_incomplete(entry) for entry in entries): self.fail(\"parse_doc() may not use interpolation.\") if expect_errors is not None: if expect_errors is False and errors: oss = io.StringIO() printer.print_errors(errors, file=oss) self.fail(\"Unexpected errors found:\\n{}\".format(oss.getvalue())) elif expect_errors is True and not errors: self.fail(\"Expected errors, none found:\") return fun(self, entries, errors, options_map) wrapper.__input__ = wrapper.__doc__ wrapper.__doc__ = None return wrapper return decorator","title":"parse_doc()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_file","text":"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: filename \u2013 the name of the file to be parsed. kw \u2013 a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) Source code in beancount/parser/parser.py def parse_file(filename, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: filename: the name of the file to be parsed. kw: a dict of keywords to be applied to the C parser. Returns: A tuple of ( list of entries parsed in the file, list of errors that were encountered during parsing, and a dict of the option values that were parsed from the file.) \"\"\" abs_filename = path.abspath(filename) if filename else None builder = grammar.Builder(abs_filename) _parser.parse_file(filename, builder, **kw) return builder.finalize()","title":"parse_file()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_many","text":"Parse a string with a snippet of Beancount input and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_many(string, level=0): \"\"\"Parse a string with a snippet of Beancount input and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" # Get the locals in the stack for the callers and produce the final text. frame = inspect.stack()[level+1] varkwds = frame[0].f_locals input_string = textwrap.dedent(string.format(**varkwds)) # Parse entries and check there are no errors. entries, errors, __ = parse_string(input_string) assert not errors return entries","title":"parse_many()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_one","text":"Parse a string with single Beancount directive and replace vars from caller. Parameters: string \u2013 A string with some Beancount input. level \u2013 The number of extra stacks to ignore. Returns: A list of entries. Exceptions: AssertionError \u2013 If there are any errors. Source code in beancount/parser/parser.py def parse_one(string): \"\"\"Parse a string with single Beancount directive and replace vars from caller. Args: string: A string with some Beancount input. level: The number of extra stacks to ignore. Returns: A list of entries. Raises: AssertionError: If there are any errors. \"\"\" entries = parse_many(string, level=1) assert len(entries) == 1 return entries[0]","title":"parse_one()"},{"location":"api_reference/beancount.parser.html#beancount.parser.parser.parse_string","text":"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Parameters: string \u2013 A string, the contents to be parsed instead of a file's. report_filename \u2013 A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw \u2013 See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Returns: Same as the output of parse_file(). Source code in beancount/parser/parser.py def parse_string(string, report_filename=None, **kw): \"\"\"Parse a beancount input file and return Ledger with the list of transactions and tree of accounts. Args: string: A string, the contents to be parsed instead of a file's. report_filename: A string, the source filename from which this string has been extracted, if any. This is stored in the metadata of the parsed entries. **kw: See parse.c. This function parses out 'dedent' which removes whitespace from the front of the text (default is False). Return: Same as the output of parse_file(). \"\"\" if kw.pop('dedent', None): string = textwrap.dedent(string) builder = grammar.Builder(report_filename or '') _parser.parse_string(string, builder, report_filename=report_filename, **kw) return builder.finalize()","title":"parse_string()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer","text":"Conversion from internal data structures to text.","title":"printer"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter","text":"A multi-method interface for printing all directive types. Attributes: Name Type Description dcontext An instance of DisplayContext with which to render all the numbers. render_weight A boolean, true if we should render the weight of the postings as a comment, for debugging. min_width_account An integer, the minimum width to leave for the account name.","title":"EntryPrinter"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.__call__","text":"Render a directive. Parameters: obj \u2013 The directive to be rendered. Returns: A string, the rendered directive. Source code in beancount/parser/printer.py def __call__(self, obj): \"\"\"Render a directive. Args: obj: The directive to be rendered. Returns: A string, the rendered directive. \"\"\" oss = io.StringIO() method = getattr(self, obj.__class__.__name__) method(obj, oss) return oss.getvalue()","title":"__call__()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.render_posting_strings","text":"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Parameters: posting \u2013 An instance of Posting, the posting to render. Returns: A tuple of flag_account \u2013 A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. Source code in beancount/parser/printer.py def render_posting_strings(self, posting): \"\"\"This renders the three components of a posting: the account and its optional posting flag, the position, and finally, the weight of the position. The purpose is to align these in the caller. Args: posting: An instance of Posting, the posting to render. Returns: A tuple of flag_account: A string, the account name including the flag. position_str: A string, the rendered position string. weight_str: A string, the rendered weight of the posting. \"\"\" # Render a string of the flag and the account. flag = '{} '.format(posting.flag) if posting.flag else '' flag_account = flag + posting.account # Render a string with the amount and cost and optional price, if # present. Also render a string with the weight. weight_str = '' if isinstance(posting.units, amount.Amount): position_str = position.to_string(posting, self.dformat) # Note: we render weights at maximum precision, for debugging. if posting.cost is None or (isinstance(posting.cost, position.Cost) and isinstance(posting.cost.number, Decimal)): weight_str = str(convert.get_weight(posting)) else: position_str = '' if posting.price is not None: position_str += ' @ {}'.format(posting.price.to_string(self.dformat_max)) return flag_account, position_str, weight_str","title":"render_posting_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.EntryPrinter.write_metadata","text":"Write metadata to the file object, excluding filename and line number. Parameters: meta \u2013 A dict that contains the metadata for this directive. oss \u2013 A file object to write to. Source code in beancount/parser/printer.py def write_metadata(self, meta, oss, prefix=None): \"\"\"Write metadata to the file object, excluding filename and line number. Args: meta: A dict that contains the metadata for this directive. oss: A file object to write to. \"\"\" if meta is None: return if prefix is None: prefix = self.prefix for key, value in sorted(meta.items()): if key not in self.META_IGNORE: value_str = None if isinstance(value, str): value_str = '\"{}\"'.format(misc_utils.escape_string(value)) elif isinstance(value, (Decimal, datetime.date, amount.Amount)): value_str = str(value) elif isinstance(value, bool): value_str = 'TRUE' if value else 'FALSE' elif isinstance(value, (dict, inventory.Inventory)): pass # Ignore dicts, don't print them out. elif value is None: value_str = '' # Render null metadata as empty, on purpose. else: raise ValueError(\"Unexpected value: '{!r}'\".format(value)) if value_str is not None: oss.write(\"{}{}: {}\\n\".format(prefix, key, value_str))","title":"write_metadata()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.align_position_strings","text":"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Parameters: strings \u2013 A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. Source code in beancount/parser/printer.py def align_position_strings(strings): \"\"\"A helper used to align rendered amounts positions to their first currency character (an uppercase letter). This class accepts a list of rendered positions and calculates the necessary width to render them stacked in a column so that the first currency word aligns. It does not go beyond that (further currencies, e.g. for the price or cost, are not aligned). This is perhaps best explained with an example. The following positions will be aligned around the column marked with '^': 45 HOOL {504.30 USD} 4 HOOL {504.30 USD, 2014-11-11} 9.95 USD -22473.32 CAD @ 1.10 USD ^ Strings without a currency character will be rendered flush left. Args: strings: A list of rendered position or amount strings. Returns: A pair of a list of aligned strings and the width of the aligned strings. \"\"\" # Maximum length before the alignment character. max_before = 0 # Maximum length after the alignment character. max_after = 0 # Maximum length of unknown strings. max_unknown = 0 string_items = [] search = re.compile('[A-Z]').search for string in strings: match = search(string) if match: index = match.start() if index != 0: max_before = max(index, max_before) max_after = max(len(string) - index, max_after) string_items.append((index, string)) continue # else max_unknown = max(len(string), max_unknown) string_items.append((None, string)) # Compute formatting string. max_total = max(max_before + max_after, max_unknown) max_after_prime = max_total - max_before fmt = \"{{:>{0}}}{{:{1}}}\".format(max_before, max_after_prime).format fmt_unknown = \"{{:<{0}}}\".format(max_total).format # Align the strings and return them. aligned_strings = [] for index, string in string_items: if index is not None: string = fmt(string[:index], string[index:]) else: string = fmt_unknown(string) aligned_strings.append(string) return aligned_strings, max_total","title":"align_position_strings()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.format_entry","text":"Format an entry into a string in the same input syntax the parser accepts. Parameters: entry \u2013 An entry instance. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. Source code in beancount/parser/printer.py def format_entry(entry, dcontext=None, render_weights=False, prefix=None): \"\"\"Format an entry into a string in the same input syntax the parser accepts. Args: entry: An entry instance. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. Returns: A string, the formatted entry. \"\"\" return EntryPrinter(dcontext, render_weights, prefix=prefix)(entry)","title":"format_entry()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.format_error","text":"Given an error objects, return a formatted string for it. Parameters: error \u2013 a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. Source code in beancount/parser/printer.py def format_error(error): \"\"\"Given an error objects, return a formatted string for it. Args: error: a namedtuple objects representing an error. It has to have an 'entry' attribute that may be either a single directive object or a list of directive objects. Returns: A string, the errors rendered. \"\"\" oss = io.StringIO() oss.write('{} {}\\n'.format(render_source(error.source), error.message)) if error.entry is not None: entries = error.entry if isinstance(error.entry, list) else [error.entry] error_string = '\\n'.join(format_entry(entry) for entry in entries) oss.write('\\n') oss.write(textwrap.indent(error_string, ' ')) oss.write('\\n') return oss.getvalue()","title":"format_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_entries","text":"A convenience function that prints a list of entries to a file. Parameters: entries \u2013 A list of directives. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entries(entries, dcontext=None, render_weights=False, file=None, prefix=None): \"\"\"A convenience function that prints a list of entries to a file. Args: entries: A list of directives. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" assert isinstance(entries, list), \"Entries is not a list: {}\".format(entries) output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) if prefix: output.write(prefix) previous_type = type(entries[0]) if entries else None eprinter = EntryPrinter(dcontext, render_weights) for entry in entries: # Insert a newline between transactions and between blocks of directives # of the same type. entry_type = type(entry) if (entry_type in (data.Transaction, data.Commodity) or entry_type is not previous_type): output.write('\\n') previous_type = entry_type string = eprinter(entry) output.write(string)","title":"print_entries()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_entry","text":"A convenience function that prints a single entry to a file. Parameters: entry \u2013 A directive entry. dcontext \u2013 An instance of DisplayContext used to format the numbers. render_weights \u2013 A boolean, true to render the weights for debugging. file \u2013 An optional file object to write the entries to. Source code in beancount/parser/printer.py def print_entry(entry, dcontext=None, render_weights=False, file=None): \"\"\"A convenience function that prints a single entry to a file. Args: entry: A directive entry. dcontext: An instance of DisplayContext used to format the numbers. render_weights: A boolean, true to render the weights for debugging. file: An optional file object to write the entries to. \"\"\" output = file or (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) output.write(format_entry(entry, dcontext, render_weights)) output.write('\\n')","title":"print_entry()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_error","text":"A convenience function that prints a single error to a file. Parameters: error \u2013 An error object. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_error(error, file=None): \"\"\"A convenience function that prints a single error to a file. Args: error: An error object. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout output.write(format_error(error)) output.write('\\n')","title":"print_error()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.print_errors","text":"A convenience function that prints a list of errors to a file. Parameters: errors \u2013 A list of errors. file \u2013 An optional file object to write the errors to. Source code in beancount/parser/printer.py def print_errors(errors, file=None, prefix=None): \"\"\"A convenience function that prints a list of errors to a file. Args: errors: A list of errors. file: An optional file object to write the errors to. \"\"\" output = file or sys.stdout if prefix: output.write(prefix) for error in errors: output.write(format_error(error)) output.write('\\n')","title":"print_errors()"},{"location":"api_reference/beancount.parser.html#beancount.parser.printer.render_source","text":"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Parameters: meta \u2013 A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. Source code in beancount/parser/printer.py def render_source(meta): \"\"\"Render the source for errors in a way that it will be both detected by Emacs and align and rendered nicely. Args: meta: A dict with the metadata. Returns: A string, rendered to be interpretable as a message location for Emacs or other editors. \"\"\" return '{}:{:8}'.format(meta['filename'], '{}:'.format(meta['lineno']))","title":"render_source()"},{"location":"api_reference/beancount.plugins.html","text":"beancount.plugins \uf0c1 Example plugins for filtering transactions. These are various examples of how to filter entries in various creative ways. IMPORTANT: These are not meant to be complete features, rather just experiments in problem-solving using Beancount, work-in-progress that can be selectively installed via a --plugin option, or one-offs to answer questions on the mailing-list. beancount.plugins.auto \uf0c1 A plugin of plugins which triggers are all the automatic and lax plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. You can just import the \"auto\" plugin. Put that in a macro. Also see: the 'pedantic' plugin. beancount.plugins.auto_accounts \uf0c1 This module automatically inserts Open directives for accounts not opened (at the date of the first entry) and automatically removes open directives for unused accounts. This can be used as a convenience for doing demos, or when setting up your initial transactions, as an intermediate step. beancount.plugins.auto_accounts.auto_insert_open(entries, unused_options_map) \uf0c1 Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/auto_accounts.py def auto_insert_open(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" opened_accounts = {entry.account for entry in entries if isinstance(entry, data.Open)} new_entries = [] accounts_first, _ = getters.get_accounts_use_map(entries) for index, (account, date_first_used) in enumerate(sorted(accounts_first.items())): if account not in opened_accounts: meta = data.new_metadata('', index) new_entries.append(data.Open(meta, date_first_used, account, None, None)) if new_entries: new_entries.extend(entries) new_entries.sort(key=data.entry_sortkey) else: new_entries = entries return new_entries, [] beancount.plugins.book_conversions \uf0c1 A plugin that automatically converts postings at price to postings held at cost, applying an automatic booking algorithm in assigning the cost bases and matching lots. This plugin restricts itself to applying these transformations within a particular account, which you provide. For each of those accounts, it also requires a corresponding Income account to book the profit/loss of reducing lots (i.e., sales): plugin \"beancount.plugins.book_conversions\" \"Assets:Bitcoin,Income:Bitcoin\" Then, simply input the transactions with price conversion. We use \"Bitcoins\" in this example, converting Bitcoin purchases that were carried out as currency into maintaining these with cost basis, for tax reporting purposes: 2015-09-04 * \"Buy some bitcoins\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.333507 BTC @ 230.76 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.345747 BTC @ 230.11 USD 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -6.000000 BTC @ 230.50 USD Expenses:Something The result is that cost bases are inserted on augmenting lots: 2015-09-04 * \"Buy some bitcoins\" Assets:Bitcoin 4.333507 BTC {230.76 USD} @ 230.76 USD Assets:Bank -1000.00 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bitcoin 4.345747 BTC {230.11 USD} @ 230.11 USD Assets:Bank -1000.00 USD While on reducing lots, matching FIFO lots are automatically found and the corresponding cost basis added: 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -4.333507 BTC {230.76 USD} @ 230.50 USD Assets:Bitcoin -1.666493 BTC {230.11 USD} @ 230.50 USD Income:Bitcoin 0.47677955 USD Expenses:Something 1383.00000000 USD Note that multiple lots were required to fulfill the sale quantity here. As in this example, this may result in multiple lots being created for a single one. Finally, Beancount will eventually support booking methods built-in, but this is a quick method that shows how to hack your own booking method via transformations of the postings that run in a plugin. Implementation notes: This code uses the FIFO method only for now. However, it would be very easy to customize it to provide other booking methods, e.g. LIFO, or otherwise. This will be added eventually, and I'm hoping to reuse the same inventory abstractions that will be used to implement the fallback booking methods from the booking proposal review (http://furius.ca/beancount/doc/proposal-booking). Instead of keeping a list of (Position, Transaction) pairs for the pending FIFO lots, we really ought to use a beancount.core.inventory.Inventory instance. However, the class does not contain sufficient data to carry out FIFO booking at the moment. A newer implementation, living in the \"booking\" branch, does, and will be used in the future. This code assumes that a positive number of units is an augmenting lot and a reducing one has a negative number of units, though we never call them that way on purpose (to eventually allow this code to handle short positions). This is not strictly true; however, we would need an Inventory in order to figrue this out. This will be done in the future and is not difficult to do. IMPORTANT: This plugin was developed before the booking methods (FIFO, LIFO, and others) were fully implemented in Beancount. It was built to answer a question on the mailing-list about FIFO booking. You probably don't need to use them anymore. Always prefer to use the native syntax instead of this. beancount.plugins.book_conversions.BookConversionError ( tuple ) \uf0c1 BookConversionError(source, message, entry) beancount.plugins.book_conversions.BookConversionError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.book_conversions.BookConversionError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of BookConversionError(source, message, entry) beancount.plugins.book_conversions.BookConversionError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.book_conversions.ConfigError ( tuple ) \uf0c1 ConfigError(source, message, entry) beancount.plugins.book_conversions.ConfigError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.book_conversions.ConfigError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ConfigError(source, message, entry) beancount.plugins.book_conversions.ConfigError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.book_conversions.augment_inventory(pending_lots, posting, entry, eindex) \uf0c1 Add the lots from the given posting to the running inventory. Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. entry \u2013 The parent transaction. eindex \u2013 The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. Source code in beancount/plugins/book_conversions.py def augment_inventory(pending_lots, posting, entry, eindex): \"\"\"Add the lots from the given posting to the running inventory. Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. entry: The parent transaction. eindex: The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. \"\"\" number = posting.units.number new_posting = posting._replace( units=copy.copy(posting.units), cost=position.Cost(posting.price.number, posting.price.currency, entry.date, None)) pending_lots.append(([number], new_posting, eindex)) return new_posting beancount.plugins.book_conversions.book_price_conversions(entries, assets_account, income_account) \uf0c1 Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Parameters: entries \u2013 A list of entry instances. assets_account \u2013 An account string, the name of the account to process. income_account \u2013 An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. Source code in beancount/plugins/book_conversions.py def book_price_conversions(entries, assets_account, income_account): \"\"\"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Args: entries: A list of entry instances. assets_account: An account string, the name of the account to process. income_account: An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. \"\"\" # Pairs of (Position, Transaction) instances used to match augmenting # entries with reducing ones. pending_lots = [] # A list of pairs of matching (augmenting, reducing) postings. all_matches = [] new_entries = [] errors = [] for eindex, entry in enumerate(entries): # Figure out if this transaction has postings in Bitcoins without a cost. # The purpose of this plugin is to fixup those. if isinstance(entry, data.Transaction) and any(is_matching(posting, assets_account) for posting in entry.postings): # Segregate the reducing lots, augmenting lots and other lots. augmenting, reducing, other = [], [], [] for pindex, posting in enumerate(entry.postings): if is_matching(posting, assets_account): out = augmenting if posting.units.number >= ZERO else reducing else: out = other out.append(posting) # We will create a replacement list of postings with costs filled # in, possibly more than the original list, to account for the # different lots. new_postings = [] # Convert all the augmenting postings to cost basis. for posting in augmenting: new_postings.append(augment_inventory(pending_lots, posting, entry, eindex)) # Then process reducing postings. if reducing: # Process all the reducing postings, booking them to matching lots. pnl = inventory.Inventory() for posting in reducing: rpostings, matches, posting_pnl, new_errors = ( reduce_inventory(pending_lots, posting, eindex)) new_postings.extend(rpostings) all_matches.extend(matches) errors.extend(new_errors) pnl.add_amount(amount.Amount(posting_pnl, posting.price.currency)) # If some reducing lots were seen in this transaction, insert an # Income leg to absorb the P/L. We need to do this for each currency # which incurred P/L. if not pnl.is_empty(): for pos in pnl: meta = data.new_metadata('', 0) new_postings.append( data.Posting(income_account, -pos.units, None, None, None, meta)) # Third, add back all the other unrelated legs in. for posting in other: new_postings.append(posting) # Create a replacement entry. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Add matching metadata to all matching postings. mod_matches = link_entries_with_metadata(new_entries, all_matches) # Resolve the indexes to their possibly modified Transaction instances. matches = [(data.TxnPosting(new_entries[aug_index], aug_posting), data.TxnPosting(new_entries[red_index], red_posting)) for (aug_index, aug_posting), (red_index, red_posting) in mod_matches] return new_entries, errors, matches beancount.plugins.book_conversions.book_price_conversions_plugin(entries, options_map, config) \uf0c1 Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. config \u2013 A string, in \",\" format. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. Source code in beancount/plugins/book_conversions.py def book_price_conversions_plugin(entries, options_map, config): \"\"\"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. config: A string, in \",\" format. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. \"\"\" # The expected configuration is two account names, separated by whitespace. errors = [] try: assets_account, income_account = re.split(r'[,; \\t]', config) if not account.is_valid(assets_account) or not account.is_valid(income_account): raise ValueError(\"Invalid account string\") except ValueError as exc: errors.append( ConfigError( None, ('Invalid configuration: \"{}\": {}, skipping booking').format(config, exc), None)) return entries, errors new_entries, errors, _ = book_price_conversions(entries, assets_account, income_account) return new_entries, errors beancount.plugins.book_conversions.extract_trades(entries) \uf0c1 Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Parameters: entries \u2013 The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). Source code in beancount/plugins/book_conversions.py def extract_trades(entries): \"\"\"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Args: entries: The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). \"\"\" trade_map = collections.defaultdict(list) for index, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue for posting in entry.postings: links_str = posting.meta.get(META, None) if links_str: links = links_str.split(',') for link in links: trade_map[link].append((index, entry, posting)) # Sort matches according to the index of the first entry, drop the index # used for doing this, and convert the objects to tuples.. trades = [(data.TxnPosting(augmenting[1], augmenting[2]), data.TxnPosting(reducing[1], reducing[2])) for augmenting, reducing in sorted(trade_map.values())] # Sanity check. for matches in trades: assert len(matches) == 2 return trades beancount.plugins.book_conversions.is_matching(posting, account) \uf0c1 \"Identify if the given posting is one to be booked. Parameters: posting \u2013 An instance of a Posting. account \u2013 The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. Source code in beancount/plugins/book_conversions.py def is_matching(posting, account): \"\"\"\"Identify if the given posting is one to be booked. Args: posting: An instance of a Posting. account: The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. \"\"\" return (posting.account == account and posting.cost is None and posting.price is not None) beancount.plugins.book_conversions.link_entries_with_metadata(entries, all_matches) \uf0c1 Modify the entries in-place to add matching links to postings. Parameters: entries \u2013 The list of entries to modify. all_matches \u2013 A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. Source code in beancount/plugins/book_conversions.py def link_entries_with_metadata(entries, all_matches): \"\"\"Modify the entries in-place to add matching links to postings. Args: entries: The list of entries to modify. all_matches: A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. \"\"\" # Allocate trade names and compute a map of posting to trade names. link_map = collections.defaultdict(list) for (aug_index, aug_posting), (red_index, red_posting) in all_matches: link = 'trade-{}'.format(str(uuid.uuid4()).split('-')[-1]) link_map[id(aug_posting)].append(link) link_map[id(red_posting)].append(link) # Modify the postings. postings_repl_map = {} for entry in entries: if isinstance(entry, data.Transaction): for index, posting in enumerate(entry.postings): links = link_map.pop(id(posting), None) if links: new_posting = posting._replace(meta=posting.meta.copy()) new_posting.meta[META] = ','.join(links) entry.postings[index] = new_posting postings_repl_map[id(posting)] = new_posting # Just a sanity check. assert not link_map, \"Internal error: not all matches found.\" # Return a list of the modified postings (mapping the old matches to the # newly created ones). return [((aug_index, postings_repl_map[id(aug_posting)]), (red_index, postings_repl_map[id(red_posting)])) for (aug_index, aug_posting), (red_index, red_posting) in all_matches] beancount.plugins.book_conversions.main() \uf0c1 Extract trades from metadata-annotated postings and report on them. Source code in beancount/plugins/book_conversions.py def main(): \"\"\"Extract trades from metadata-annotated postings and report on them. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output', action='store', help=\"Filename to output results to (default goes to stdout)\") oparser.add_argument('-f', '--format', default='text', choices=['text', 'csv'], help=\"Output format to render to (text, csv)\") args = parser.parse_args() # Load the input file. entries, errors, options_map = loader.load_file(args.filename) # Get the list of trades. trades = extract_trades(entries) # Produce a table of all the trades. columns = ('units currency cost_currency ' 'buy_date buy_price sell_date sell_price pnl').split() header = ['Units', 'Currency', 'Cost Currency', 'Buy Date', 'Buy Price', 'Sell Date', 'Sell Price', 'P/L'] body = [] for aug, red in trades: units = -red.posting.units.number buy_price = aug.posting.price.number sell_price = red.posting.price.number pnl = (units * (sell_price - buy_price)).quantize(buy_price) body.append([ -red.posting.units.number, red.posting.units.currency, red.posting.price.currency, aug.txn.date.isoformat(), buy_price, red.txn.date.isoformat(), sell_price, pnl ]) trades_table = table.Table(columns, header, body) # Render the table as text or CSV. outfile = open(args.output, 'w') if args.output else sys.stdout table.render_table(trades_table, outfile, args.format) beancount.plugins.book_conversions.reduce_inventory(pending_lots, posting, eindex) \uf0c1 Match a reducing posting against a list of lots (using FIFO order). Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. eindex \u2013 The index of the parent transaction housing this posting. Returns: A tuple of postings \u2013 A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. Source code in beancount/plugins/book_conversions.py def reduce_inventory(pending_lots, posting, eindex): \"\"\"Match a reducing posting against a list of lots (using FIFO order). Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. eindex: The index of the parent transaction housing this posting. Returns: A tuple of postings: A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. \"\"\" new_postings = [] matches = [] pnl = ZERO errors = [] match_number = -posting.units.number match_currency = posting.units.currency cost_currency = posting.price.currency while match_number != ZERO: # Find the first lot with matching currency. for fnumber, fposting, findex in pending_lots: funits = fposting.units fcost = fposting.cost if (funits.currency == match_currency and fcost and fcost.currency == cost_currency): assert fnumber[0] > ZERO, \"Internal error, zero lot\" break else: errors.append( BookConversionError(posting.meta, \"Could not match position {}\".format(posting), None)) break # Reduce the pending lots. number = min(match_number, fnumber[0]) cost = fcost match_number -= number fnumber[0] -= number if fnumber[0] == ZERO: pending_lots.pop(0) # Add a corresponding posting. rposting = posting._replace( units=amount.Amount(-number, posting.units.currency), cost=copy.copy(cost)) new_postings.append(rposting) # Update the P/L. pnl += number * (posting.price.number - cost.number) # Add to the list of matches. matches.append(((findex, fposting), (eindex, rposting))) return new_postings, matches, pnl, errors beancount.plugins.check_average_cost \uf0c1 A plugin that ensures cost basis is preserved in unbooked transactions. This is intended to be used in accounts using the \"NONE\" booking method, to manually ensure that the sum total of the cost basis of reducing legs matches the average of what's in the account inventory. This is a partial first step toward implementing the \"AVERAGE\" booking method. In other words, this plugins provides assertions that will constrain you to approximate what the \"AVERAGE\" booking method will do, manually, and not to leak too much cost basis through unmatching bookings without checks. (Note the contrived context here: Ideally the \"NONE\" booking method would simply not exist.) beancount.plugins.check_average_cost.MatchBasisError ( tuple ) \uf0c1 MatchBasisError(source, message, entry) beancount.plugins.check_average_cost.MatchBasisError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_average_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.check_average_cost.MatchBasisError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of MatchBasisError(source, message, entry) beancount.plugins.check_average_cost.MatchBasisError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/check_average_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.check_average_cost.validate_average_cost(entries, options_map, config_str=None) \uf0c1 Check that reducing legs on unbooked postings are near the average cost basis. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 The configuration as a string version of a float. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_average_cost.py def validate_average_cost(entries, options_map, config_str=None): \"\"\"Check that reducing legs on unbooked postings are near the average cost basis. Args: entries: A list of directives. unused_options_map: An options map. config_str: The configuration as a string version of a float. Returns: A list of new errors, if any were found. \"\"\" # Initialize tolerance bounds. if config_str and config_str.strip(): # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, float): raise RuntimeError(\"Invalid configuration for check_average_cost: \" \"must be a float\") tolerance = config_obj else: tolerance = DEFAULT_TOLERANCE min_tolerance = D(1 - tolerance) max_tolerance = D(1 + tolerance) errors = [] ocmap = getters.get_account_open_close(entries) balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, Transaction): for posting in entry.postings: dopen = ocmap.get(posting.account, None) # Only process accounts with a NONE booking value. if dopen and dopen[0] and dopen[0].booking == Booking.NONE: balance = balances[(posting.account, posting.units.currency, posting.cost.currency if posting.cost else None)] if posting.units.number < ZERO: average = balance.average().get_only_position() if average is not None: number = average.cost.number min_valid = number * min_tolerance max_valid = number * max_tolerance if not (min_valid <= posting.cost.number <= max_valid): errors.append( MatchBasisError( entry.meta, (\"Cost basis on reducing posting is too far from \" \"the average cost ({} vs. {})\".format( posting.cost.number, average.cost.number)), entry)) balance.add_position(posting) return entries, errors beancount.plugins.check_closing \uf0c1 A plugin that automatically inserts a balance check on a tagged closing posting. Some postings are known to the user to be \"closing trades\", which means that the resulting position of the instrument just after the trade should be zero. For instance, this is the case for most ordinary options trading, only one lot of a particular instrument is held, and eventually expires or gets sold off in its entirely. One would like to confirm that, and the way to do this in Beancount is to insert a balance check. This plugin allows you to do that more simply, by inserting metadata. For example, this transaction: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD closing: TRUE Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL Would expand into the following two directives: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL 2018-02-17 balance Assets:US:Brokerage:Main:Options 0 QQQ180216C160 Insert the closing line when you know you're closing the position. beancount.plugins.check_closing.check_closing(entries, options_map) \uf0c1 Expand 'closing' metadata to a zero balance check. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_closing.py def check_closing(entries, options_map): \"\"\"Expand 'closing' metadata to a zero balance check. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.meta and posting.meta.get('closing', False): # Remove the metadata. meta = posting.meta.copy() del meta['closing'] entry = entry._replace(meta=meta) # Insert a balance. date = entry.date + datetime.timedelta(days=1) balance = data.Balance(data.new_metadata(\"\", 0), date, posting.account, amount.Amount(ZERO, posting.units.currency), None, None) new_entries.append(balance) new_entries.append(entry) return new_entries, [] beancount.plugins.check_commodity \uf0c1 A plugin that verifies that all seen commodities have a Commodity directive. This is useful if you're a bit pedantic and like to make sure that you're declared attributes for each of the commodities you use. It's useful if you use the portfolio export, for example. beancount.plugins.check_commodity.CheckCommodityError ( tuple ) \uf0c1 CheckCommodityError(source, message, entry) beancount.plugins.check_commodity.CheckCommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_commodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.check_commodity.CheckCommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CheckCommodityError(source, message, entry) beancount.plugins.check_commodity.CheckCommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/check_commodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.check_commodity.validate_commodity_directives(entries, options_map) \uf0c1 Find all commodities used and ensure they have a corresponding Commodity directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_commodity.py def validate_commodity_directives(entries, options_map): \"\"\"Find all commodities used and ensure they have a corresponding Commodity directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" commodities_used = options_map['commodities'] errors = [] meta = data.new_metadata('', 0) commodity_map = getters.get_commodity_map(entries, create_missing=False) for currency in commodities_used: commodity_entry = commodity_map.get(currency, None) if commodity_entry is None: errors.append( CheckCommodityError( meta, \"Missing Commodity directive for '{}'\".format(currency), None)) return entries, errors beancount.plugins.coherent_cost \uf0c1 This plugin validates that currencies held at cost aren't ever converted at price and vice-versa. This is usually the case, and using it will prevent users from making the mistake of selling a lot without specifying it via its cost basis. beancount.plugins.coherent_cost.CoherentCostError ( tuple ) \uf0c1 CoherentCostError(source, message, entry) beancount.plugins.coherent_cost.CoherentCostError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/coherent_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.coherent_cost.CoherentCostError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CoherentCostError(source, message, entry) beancount.plugins.coherent_cost.CoherentCostError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/coherent_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.coherent_cost.validate_coherent_cost(entries, unused_options_map) \uf0c1 Check that all currencies are either used at cost or not at all, but never both. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/coherent_cost.py def validate_coherent_cost(entries, unused_options_map): \"\"\"Check that all currencies are either used at cost or not at all, but never both. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] with_cost = {} without_cost = {} for entry in data.filter_txns(entries): for posting in entry.postings: target_set = without_cost if posting.cost is None else with_cost currency = posting.units.currency target_set.setdefault(currency, entry) for currency in set(with_cost) & set(without_cost): errors.append( CoherentCostError( without_cost[currency].meta, \"Currency '{}' is used both with and without cost\".format(currency), with_cost[currency])) # Note: We really ought to include both of the first transactions here. return entries, errors beancount.plugins.commodity_attr \uf0c1 A plugin that asserts that all Commodity directives have a particular attribute and that it is part of a set of enum values. The configuration must be a mapping of attribute name to list of valid values, like this: plugin \"beancount.plugins.commodity_attr\" \"{ 'sector': ['Technology', 'Financials', 'Energy'], 'name': None, }\" The plugin issues an error if a Commodity directive is missing the attribute, or if the attribute value is not in the valid set. If you'd like to just ensure the attribute is set, set the list of valid values to None, as in the 'name' attribute in the example above. beancount.plugins.commodity_attr.CommodityError ( tuple ) \uf0c1 CommodityError(source, message, entry) beancount.plugins.commodity_attr.CommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.commodity_attr.CommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of CommodityError(source, message, entry) beancount.plugins.commodity_attr.CommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.commodity_attr.ConfigError ( tuple ) \uf0c1 ConfigError(source, message, entry) beancount.plugins.commodity_attr.ConfigError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.commodity_attr.ConfigError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ConfigError(source, message, entry) beancount.plugins.commodity_attr.ConfigError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.commodity_attr.validate_commodity_attr(entries, unused_options_map, config_str) \uf0c1 Check that all Commodity directives have a valid attribute. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 A configuration string. Returns: A list of new errors, if any were found. Source code in beancount/plugins/commodity_attr.py def validate_commodity_attr(entries, unused_options_map, config_str): \"\"\"Check that all Commodity directives have a valid attribute. Args: entries: A list of directives. unused_options_map: An options map. config_str: A configuration string. Returns: A list of new errors, if any were found. \"\"\" errors = [] # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): errors.append(ConfigError( data.new_metadata('', 0), \"Invalid configuration for commodity_attr plugin; skipping.\", None)) return entries, errors validmap = {attr: frozenset(values) if values is not None else None for attr, values in config_obj.items()} for entry in entries: if not isinstance(entry, data.Commodity): continue for attr, values in validmap.items(): value = entry.meta.get(attr, None) if value is None: errors.append(CommodityError( entry.meta, \"Missing attribute '{}' for Commodity directive {}\".format( attr, entry.currency), None)) continue if values and value not in values: errors.append(CommodityError( entry.meta, \"Invalid attribute '{}' for Commodity\".format(value) + \" directive {}; valid options: {}\".format( entry.currency, ', '.join(values)), None)) return entries, errors beancount.plugins.currency_accounts \uf0c1 An implementation of currency accounts. This is an automatic implementation of the method described here: https://www.mathstat.dal.ca/~selinger/accounting/tutorial.html You enable it just like this: plugin \"beancount.plugins.currency_accounts\" \"Equity:CurrencyAccounts\" Accounts will be automatically created under the given base account, with the currency name appended to it, e.g., Equity:CurrencyAccounts:CAD Equity:CurrencyAccounts:USD etc., where used. You can have a look at the account balances with a query like this: bean-query $L \"select account, sum(position), convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts' \" The sum total of the converted amounts should be a number not too large: bean-query $L \"select convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts'\" WARNING: This is a prototype. Note the FIXMEs in the code below, which indicate some potential problems. beancount.plugins.currency_accounts.get_neutralizing_postings(curmap, base_account, new_accounts) \uf0c1 Process an entry. Parameters: curmap \u2013 A dict of currency to a list of Postings of this transaction. base_account \u2013 A string, the root account name to insert. new_accounts \u2013 A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. Source code in beancount/plugins/currency_accounts.py def get_neutralizing_postings(curmap, base_account, new_accounts): \"\"\"Process an entry. Args: curmap: A dict of currency to a list of Postings of this transaction. base_account: A string, the root account name to insert. new_accounts: A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. \"\"\" new_postings = [] for currency, postings in curmap.items(): # Compute the per-currency balance. inv = inventory.Inventory() for posting in postings: inv.add_amount(convert.get_cost(posting)) if inv.is_empty(): new_postings.extend(postings) continue # Re-insert original postings and remove price conversions. # # Note: This may cause problems if the implicit_prices plugin is # configured to run after this one, or if you need the price annotations # for some scripting or serious work. # # FIXME: We need to handle these important cases (they're not frivolous, # this is a prototype), probably by inserting some exceptions with # collaborating code in the booking (e.g. insert some metadata that # disables price conversions on those postings). # # FIXME(2): Ouch! Some of the residual seeps through here, where there # are more than a single currency block. This needs fixing too. You can # easily mitigate some of this to some extent, by excluding transactions # which don't have any price conversion in them. for pos in postings: if pos.price is not None: pos = pos._replace(price=None) new_postings.append(pos) # Insert the currency trading accounts postings. amount = inv.get_only_position().units acc = account.join(base_account, currency) new_accounts.add(acc) new_postings.append( Posting(acc, -amount, None, None, None, None)) return new_postings beancount.plugins.currency_accounts.group_postings_by_weight_currency(entry) \uf0c1 Return where this entry might require adjustment. Source code in beancount/plugins/currency_accounts.py def group_postings_by_weight_currency(entry: Transaction): \"\"\"Return where this entry might require adjustment.\"\"\" curmap = collections.defaultdict(list) has_price = False for posting in entry.postings: currency = posting.units.currency if posting.cost: currency = posting.cost.currency if posting.price: assert posting.price.currency == currency elif posting.price: has_price = True currency = posting.price.currency if posting.price: has_price = True curmap[currency].append(posting) return curmap, has_price beancount.plugins.currency_accounts.insert_currency_trading_postings(entries, options_map, config) \uf0c1 Insert currency trading postings. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The base account name for currency trading accounts. Returns: A list of new errors, if any were found. Source code in beancount/plugins/currency_accounts.py def insert_currency_trading_postings(entries, options_map, config): \"\"\"Insert currency trading postings. Args: entries: A list of directives. unused_options_map: An options map. config: The base account name for currency trading accounts. Returns: A list of new errors, if any were found. \"\"\" base_account = config.strip() if not account.is_valid(base_account): base_account = DEFAULT_BASE_ACCOUNT errors = [] new_entries = [] new_accounts = set() for entry in entries: if isinstance(entry, Transaction): curmap, has_price = group_postings_by_weight_currency(entry) if has_price and len(curmap) > 1: new_postings = get_neutralizing_postings( curmap, base_account, new_accounts) entry = entry._replace(postings=new_postings) if META_PROCESSED: entry.meta[META_PROCESSED] = True new_entries.append(entry) earliest_date = entries[0].date open_entries = [ data.Open(data.new_metadata('', index), earliest_date, acc, None, None) for index, acc in enumerate(sorted(new_accounts))] return open_entries + new_entries, errors beancount.plugins.divert_expenses \uf0c1 For tagged transactions, convert expenses to a single account. This plugin allows you to select a tag and it automatically converts all the Expenses postings to use a single account. For example, with this input: plugin \"divert_expenses\" \"['kid', 'Expenses:Child']\" 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Food:Grocery 10.27 USD It will output: 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Child 10.27 USD You can limit the diversion to one posting only, like this: 2018-05-05 * \"CVS/PHARMACY\" \"\" #kai Liabilities:CreditCard -66.38 USD Expenses:Pharmacy 21.00 USD ;; Vitamins for Kai Expenses:Pharmacy 45.38 USD divert: FALSE See unit test for details. See this thread for context: https://docs.google.com/drawings/d/18fTrrGlmz0jFbfcGGHTffbdRwbmST8r9_3O26Dd1Xww/edit?usp=sharing beancount.plugins.divert_expenses.divert_expenses(entries, options_map, config_str) \uf0c1 Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. options_map \u2013 A parser options dict. config_str \u2013 A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. Source code in beancount/plugins/divert_expenses.py def divert_expenses(entries, options_map, config_str): \"\"\"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. options_map: A parser options dict. config_str: A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. \"\"\" # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") tag = config_obj['tag'] replacement_account = config_obj['account'] acctypes = options.get_account_types(options_map) new_entries = [] errors = [] for entry in entries: if isinstance(entry, Transaction) and tag in entry.tags: entry = replace_diverted_accounts(entry, replacement_account, acctypes) new_entries.append(entry) return new_entries, errors beancount.plugins.divert_expenses.replace_diverted_accounts(entry, replacement_account, acctypes) \uf0c1 Replace the Expenses accounts from the entry. Parameters: entry \u2013 A Transaction directive. replacement_account \u2013 A string, the account to use for replacement. acctypes \u2013 An AccountTypes instance. Returns: A possibly entry directive. Source code in beancount/plugins/divert_expenses.py def replace_diverted_accounts(entry, replacement_account, acctypes): \"\"\"Replace the Expenses accounts from the entry. Args: entry: A Transaction directive. replacement_account: A string, the account to use for replacement. acctypes: An AccountTypes instance. Returns: A possibly entry directive. \"\"\" new_postings = [] for posting in entry.postings: divert = posting.meta.get('divert', None) if posting.meta else None if (divert is True or ( divert is None and account_types.is_account_type(acctypes.expenses, posting.account))): posting = posting._replace(account=replacement_account, meta={'diverted_account': posting.account}) new_postings.append(posting) return entry._replace(postings=new_postings) beancount.plugins.exclude_tag \uf0c1 Exclude #virtual tags. This is used to demonstrate excluding a set of transactions from a particular tag. In this example module, the tag name is fixed, but if we integrated this we could provide a way to choose which tags to exclude. This is simply just another mechanism for selecting a subset of transactions. See discussion here for details: https://groups.google.com/d/msg/ledger-cli/N8Slh2t45K0/aAz0i3Be4LYJ beancount.plugins.exclude_tag.exclude_tag(entries, options_map) \uf0c1 Select all transactions that do not have a #virtual tag. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/exclude_tag.py def exclude_tag(entries, options_map): \"\"\"Select all transactions that do not have a #virtual tag. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" filtered_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or not entry.tags or EXCLUDED_TAG not in entry.tags)] return (filtered_entries, []) beancount.plugins.fill_account \uf0c1 Insert an posting with a default account when there is only a single posting. This is convenient to use in files which have mostly expenses, such as during a trip. Set the name of the default account to fill in as an option. beancount.plugins.fill_account.FillAccountError ( tuple ) \uf0c1 FillAccountError(source, message, entry) beancount.plugins.fill_account.FillAccountError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fill_account.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.fill_account.FillAccountError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of FillAccountError(source, message, entry) beancount.plugins.fill_account.FillAccountError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/fill_account.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.fill_account.fill_account(entries, unused_options_map, insert_account) \uf0c1 Insert an posting with a default account when there is only a single posting. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 A parser options dict. insert_account \u2013 A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/fill_account.py def fill_account(entries, unused_options_map, insert_account): \"\"\"Insert an posting with a default account when there is only a single posting. Args: entries: A list of directives. unused_options_map: A parser options dict. insert_account: A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" if not account.is_valid(insert_account): return entries, [ FillAccountError(data.new_metadata('', 0), \"Invalid account name: '{}'\".format(insert_account), None)] new_entries = [] for entry in entries: if isinstance(entry, data.Transaction) and len(entry.postings) == 1: inv = inventory.Inventory() for posting in entry.postings: if posting.cost is None: inv.add_amount(posting.units) else: inv.add_amount(convert.get_cost(posting)) inv.reduce(convert.get_units) new_postings = list(entry.postings) for pos in inv: new_postings.append(data.Posting(insert_account, -pos.units, None, None, None, None)) entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, [] beancount.plugins.fix_payees \uf0c1 Rename payees based on a set of rules. This can be used to clean up dirty imported payee names. This plugin accepts a list of rules in this format: plugin \"beancount.plugins.fix_payees\" \"[ (PAYEE, MATCH1, MATCH2, ...), ]\" Each of the \"MATCH\" clauses is a string, in the format: \"A:<regexp>\" : Match the account name. \"D:<regexp>\" : Match the payee or the narration. The plugin matches the Transactions in the file and if there is a case-insensitive match against the regular expression (we use re.search()), replaces the payee name by \"PAYEE\". If multiple rules match, only the first rule is used. For example: plugin \"beancount.plugins.fix_payees\" \"[ (\"T-Mobile USA\", \"A:Expenses:Communications:Phone\", \"D:t-mobile\"), (\"Con Edison\", \"A:Expenses:Home:Electricity\", \"D:con ?ed\"), (\"Birreria @ Eataly\", \"D:EATALY BIRRERIA\"), ]\" beancount.plugins.fix_payees.FixPayeesError ( tuple ) \uf0c1 FixPayeesError(source, message, entry) beancount.plugins.fix_payees.FixPayeesError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fix_payees.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.fix_payees.FixPayeesError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of FixPayeesError(source, message, entry) beancount.plugins.fix_payees.FixPayeesError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/fix_payees.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.fix_payees.fix_payees(entries, options_map, config) \uf0c1 Rename payees based on a set of rules. See module docstring for details. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config \u2013 A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. Source code in beancount/plugins/fix_payees.py def fix_payees(entries, options_map, config): \"\"\"Rename payees based on a set of rules. See module docstring for details. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config: A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. \"\"\" errors = [] if config.strip(): try: expr = ast.literal_eval(config) except (SyntaxError, ValueError): meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Syntax error in config: {}\".format(config), None)) return entries, errors else: return entries, errors # Pre-compile the regular expressions for performance. rules = [] for rule in ast.literal_eval(config): clauses = iter(rule) new_payee = next(clauses) regexps = [] for clause in clauses: match = re.match('([AD]):(.*)', clause) if not match: meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Invalid clause: {}\".format(clause), None)) continue command, regexp = match.groups() regexps.append((command, re.compile(regexp, re.I).search)) new_rule = [new_payee] + regexps rules.append(tuple(new_rule)) # Run the rules over the transaction objects. new_entries = [] replaced_entries = {rule[0]: [] for rule in rules} for entry in entries: if isinstance(entry, data.Transaction): for rule in rules: clauses = iter(rule) new_payee = next(clauses) # Attempt to match all the clauses. for clause in clauses: command, func = clause if command == 'D': if not ((entry.payee is not None and func(entry.payee)) or (entry.narration is not None and func(entry.narration))): break elif command == 'A': if not any(func(posting.account) for posting in entry.postings): break else: # Make the replacement. entry = entry._replace(payee=new_payee) replaced_entries[new_payee].append(entry) new_entries.append(entry) if _DEBUG: # Print debugging info. for payee, repl_entries in sorted(replaced_entries.items(), key=lambda x: len(x[1]), reverse=True): print('{:60}: {}'.format(payee, len(repl_entries))) return new_entries, errors beancount.plugins.forecast \uf0c1 An example of adding a forecasting feature to Beancount via a plugin. This entry filter plugin uses existing syntax to define and automatically inserted transactions in the future based on a convention. It serves mostly as an example of how you can experiment by creating and installing a local filter, and not so much as a serious forecasting feature (though the experiment is a good way to get something more general kickstarted eventually, I think the concept would generalize nicely and should eventually be added as a common feature of Beancount). A user can create a transaction like this: 2014-03-08 # \"Electricity bill [MONTHLY]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD and new transactions will be created monthly for the following year. Note the use of the '#' flag and the word 'MONTHLY' which defines the periodicity. The number of recurrences can optionally be specified either by providing an end date or by specifying the number of times that the transaction will be repeated. For example: 2014-03-08 # \"Electricity bill [MONTHLY UNTIL 2019-12-31]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [MONTHLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Transactions can also be repeated at yearly intervals, e.g.: 2014-03-08 # \"Electricity bill [YEARLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Other examples: 2014-03-08 # \"Electricity bill [WEEKLY SKIP 1 TIME REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [DAILY SKIP 3 TIMES REPEAT 1 TIME]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD beancount.plugins.forecast.forecast_plugin(entries, options_map) \uf0c1 An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file Returns: A tuple of entries and errors. Source code in beancount/plugins/forecast.py def forecast_plugin(entries, options_map): \"\"\"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Args: entries: a list of entry instances options_map: a dict of options parsed from the file Returns: A tuple of entries and errors. \"\"\" # Find the last entry's date. date_today = entries[-1].date # Filter out forecast entries from the list of valid entries. forecast_entries = [] filtered_entries = [] for entry in entries: outlist = (forecast_entries if (isinstance(entry, data.Transaction) and entry.flag == '#') else filtered_entries) outlist.append(entry) # Generate forecast entries up to the end of the current year. new_entries = [] for entry in forecast_entries: # Parse the periodicity. match = re.search(r'(^.*)\\[(MONTHLY|YEARLY|WEEKLY|DAILY)' r'(\\s+SKIP\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+REPEAT\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+UNTIL\\s+([0-9\\-]+))?\\]', entry.narration) if not match: new_entries.append(entry) continue forecast_narration = match.group(1).strip() forecast_interval = ( rrule.YEARLY if match.group(2).strip() == 'YEARLY' else rrule.WEEKLY if match.group(2).strip() == 'WEEKLY' else rrule.DAILY if match.group(2).strip() == 'DAILY' else rrule.MONTHLY) forecast_periodicity = {'dtstart': entry.date} if match.group(6): # e.g., [MONTHLY REPEAT 3 TIMES]: forecast_periodicity['count'] = int(match.group(6)) elif match.group(8): # e.g., [MONTHLY UNTIL 2020-01-01]: forecast_periodicity['until'] = datetime.datetime.strptime( match.group(8), '%Y-%m-%d').date() else: # e.g., [MONTHLY] forecast_periodicity['until'] = datetime.date( datetime.date.today().year, 12, 31) if match.group(4): # SKIP forecast_periodicity['interval'] = int(match.group(4)) + 1 # Generate a new entry for each forecast date. forecast_dates = [dt.date() for dt in rrule.rrule(forecast_interval, **forecast_periodicity)] for forecast_date in forecast_dates: forecast_entry = entry._replace(date=forecast_date, narration=forecast_narration) new_entries.append(forecast_entry) # Make sure the new entries inserted are sorted. new_entries.sort(key=data.entry_sortkey) return (filtered_entries + new_entries, []) beancount.plugins.implicit_prices \uf0c1 This plugin synthesizes Price directives for all Postings with a price or directive or if it is an augmenting posting, has a cost directive. beancount.plugins.implicit_prices.ImplicitPriceError ( tuple ) \uf0c1 ImplicitPriceError(source, message, entry) beancount.plugins.implicit_prices.ImplicitPriceError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/implicit_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.implicit_prices.ImplicitPriceError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of ImplicitPriceError(source, message, entry) beancount.plugins.implicit_prices.ImplicitPriceError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/implicit_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.implicit_prices.add_implicit_prices(entries, unused_options_map) \uf0c1 Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/implicit_prices.py def add_implicit_prices(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" new_entries = [] errors = [] # A dict of (date, currency, cost-currency) to price entry. new_price_entry_map = {} balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Always replicate the existing entries. new_entries.append(entry) if isinstance(entry, Transaction): # Inspect all the postings in the transaction. for posting in entry.postings: units = posting.units cost = posting.cost # Check if the position is matching against an existing # position. _, booking = balances[posting.account].add_position(posting) # Add prices when they're explicitly specified on a posting. An # explicitly specified price may occur in a conversion, e.g. # Assets:Account 100 USD @ 1.10 CAD # or, if a cost is also specified, as the current price of the # underlying instrument, e.g. # Assets:Account 100 HOOL {564.20} @ {581.97} USD if posting.price is not None: meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, posting.price) # Add costs, when we're not matching against an existing # position. This happens when we're just specifying the cost, # e.g. # Assets:Account 100 HOOL {564.20} elif (cost is not None and booking != inventory.Booking.REDUCED): meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, amount.Amount(cost.number, cost.currency)) else: price_entry = None if price_entry is not None: key = (price_entry.date, price_entry.currency, price_entry.amount.number, # Ideally should be removed. price_entry.amount.currency) try: new_price_entry_map[key] ## Do not fail for now. We still have many valid use ## cases of duplicate prices on the same date, for ## example, stock splits, or trades on two dates with ## two separate reported prices. We need to figure out a ## more elegant solution for this in the long term. ## Keeping both for now. We should ideally not use the ## number in the de-dup key above. # # dup_entry = new_price_entry_map[key] # if price_entry.amount.number == dup_entry.amount.number: # # Skip duplicates. # continue # else: # errors.append( # ImplicitPriceError( # entry.meta, # \"Duplicate prices for {} on {}\".format(entry, # dup_entry), # entry)) except KeyError: new_price_entry_map[key] = price_entry new_entries.append(price_entry) return new_entries, errors beancount.plugins.ira_contribs \uf0c1 Automatically adding IRA contributions postings. This plugin looks for increasing postings on specified accounts ('+' sign for Assets and Expenses accounts, '-' sign for the others), or postings with a particular flag on them and when it finds some, inserts a pair of postings on that transaction of the corresponding amounts in a different currency. The currency is intended to be an imaginary currency used to track the number of dollars contributed to a retirement account over time. For example, a possible configuration could be: plugin \"beancount.plugins.ira_contribs\" \"{ 'currency': 'IRAUSD', 'flag': 'M', 'accounts': { 'Income:US:Acme:Match401k': ( 'Assets:US:Federal:Match401k', 'Expenses:Taxes:TY{year}:US:Federal:Match401k'), ('C', 'Assets:US:Fidelity:PreTax401k:Cash'): ( 'Assets:US:Federal:PreTax401k', 'Expenses:Taxes:TY{year}:US:Federal:PreTax401k'), } }\" Note: In this example, the configuration that triggers on the \"Income:US:Acme:Match401k\" account does not require a flag for those accounts; the configuration for the \"Assets:US:Fidelity:PreTax401k:Cash\" account requires postings to have a \"C\" flag to trigger an insertion. Given a transaction like the following, which would be typical for a salary entry where the employer is automatically diverting some of the pre-tax money to a retirement account (in this example, at Fidelity): 2013-02-15 * \"ACME INC PAYROLL\" Income:US:Acme:Salary ... ... Assets:US:BofA:Checking ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD ... A posting with account 'Assets:US:Fidelity:PreTax401k:Cash', which is configured to match, would be found. The configuration above instructs the plugin to automatically insert new postings like this: 2013-02-15 * \"ACME INC PAYROLL\" ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD M Assets:US:Federal:PreTax401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:PreTax401k 620.50 IRAUSD ... Notice that the \"{year}\" string in the configuration's account names is automatically replaced by the current year in the account name. This is useful if you maintain separate tax accounts per year. Furthermore, as in the configuration example above, you may have multiple matching entries to trigger multiple insertions. For example, the employer may also match the employee's retirement contribution by depositing some money in the retirement account: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD In this example the funds get reported as invested immediately (an intermediate deposit into a cash account does not take place). The plugin configuration would match against the 'Income:US:Acme:Match401k' account and since it increases its value (the normal balance of an Income account is negative), postings would be inserted like this: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD M Assets:US:Federal:Match401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:Match401k 620.50 IRAUSD Note that the special dict keys 'currency' and 'flag' are used to specify which currency to use for the inserted postings, and if set, which flag to mark these postings with. beancount.plugins.ira_contribs.add_ira_contribs(entries, options_map, config_str) \uf0c1 Add legs for 401k employer match contributions. See module docstring for an example configuration. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config_str \u2013 A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. Source code in beancount/plugins/ira_contribs.py def add_ira_contribs(entries, options_map, config_str): \"\"\"Add legs for 401k employer match contributions. See module docstring for an example configuration. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config_str: A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. \"\"\" # Parse and extract configuration values. # FIXME: Use ast.literal_eval() here; you need to convert this code and the getters. # FIXME: Also, don't raise a RuntimeError, return an error object; review # this for all the plugins. # FIXME: This too is temporary. # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") # Currency of the inserted postings. currency = config_obj.pop('currency', 'UNKNOWN') # Flag to attach to the inserted postings. insert_flag = config_obj.pop('flag', None) # A dict of account names that trigger the insertion of postings to pairs of # inserted accounts when triggered. accounts = config_obj.pop('accounts', {}) # Convert the key in the accounts configuration for matching. account_transforms = {} for key, config in accounts.items(): if isinstance(key, str): flag = None account = key else: assert isinstance(key, tuple) flag, account = key account_transforms[account] = (flag, config) new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): orig_entry = entry for posting in entry.postings: if (posting.units is not MISSING and (posting.account in account_transforms) and (account_types.get_account_sign(posting.account) * posting.units.number > 0)): # Get the new account legs to insert. required_flag, (neg_account, pos_account) = account_transforms[posting.account] assert posting.cost is None # Check required flag if present. if (required_flag is None or (required_flag and required_flag == posting.flag)): # Insert income/expense entries for 401k. entry = add_postings( entry, amount.Amount(abs(posting.units.number), currency), neg_account.format(year=entry.date.year), pos_account.format(year=entry.date.year), insert_flag) if DEBUG and orig_entry is not entry: printer.print_entry(orig_entry) printer.print_entry(entry) new_entries.append(entry) return new_entries, [] beancount.plugins.ira_contribs.add_postings(entry, amount_, neg_account, pos_account, flag) \uf0c1 Insert positive and negative postings of a position in an entry. Parameters: entry \u2013 A Transaction instance. amount_ \u2013 An Amount instance to create the position, with positive number. neg_account \u2013 An account for the posting with the negative amount. pos_account \u2013 An account for the posting with the positive amount. flag \u2013 A string, that is to be set as flag for the new postings. Returns: A new, modified entry. Source code in beancount/plugins/ira_contribs.py def add_postings(entry, amount_, neg_account, pos_account, flag): \"\"\"Insert positive and negative postings of a position in an entry. Args: entry: A Transaction instance. amount_: An Amount instance to create the position, with positive number. neg_account: An account for the posting with the negative amount. pos_account: An account for the posting with the positive amount. flag: A string, that is to be set as flag for the new postings. Returns: A new, modified entry. \"\"\" return entry._replace(postings=entry.postings + [ data.Posting(neg_account, -amount_, None, None, flag, None), data.Posting(pos_account, amount_, None, None, flag, None), ]) beancount.plugins.leafonly \uf0c1 A plugin that issues errors when amounts are posted to non-leaf accounts, that is, accounts with child accounts. This is an extra constraint that you may want to apply optionally. If you install this plugin, it will issue errors for all accounts that have postings to non-leaf accounts. Some users may want to disallow this and enforce that only leaf accounts may have postings on them. beancount.plugins.leafonly.LeafOnlyError ( tuple ) \uf0c1 LeafOnlyError(source, message, entry) beancount.plugins.leafonly.LeafOnlyError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/leafonly.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.leafonly.LeafOnlyError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of LeafOnlyError(source, message, entry) beancount.plugins.leafonly.LeafOnlyError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/leafonly.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.leafonly.validate_leaf_only(entries, unused_options_map) \uf0c1 Check for non-leaf accounts that have postings on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/leafonly.py def validate_leaf_only(entries, unused_options_map): \"\"\"Check for non-leaf accounts that have postings on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" real_root = realization.realize(entries, compute_balance=False) default_meta = data.new_metadata('', 0) open_close_map = None # Lazily computed. errors = [] for real_account in realization.iter_children(real_root): if len(real_account) > 0 and real_account.txn_postings: if open_close_map is None: open_close_map = getters.get_account_open_close(entries) try: open_entry = open_close_map[real_account.account][0] except KeyError: open_entry = None errors.append(LeafOnlyError( open_entry.meta if open_entry else default_meta, \"Non-leaf account '{}' has postings on it\".format(real_account.account), open_entry)) return entries, errors beancount.plugins.mark_unverified \uf0c1 Add metadata to Postings which occur after their last Balance directives. Some people use Balance directives as a way to indicate that all postings before them are verified. They want to compute balances in each account as of the date of that last Balance directives. One way to do that is to use this plugin to mark the postings which occur after and to then filter them out using a WHERE clause on that metadata: SELECT account, sum(position) WHERE NOT meta(\"unverified\") Note that doing such a filtering may result in a list of balances which may not add to zero. Also, postings for accounts without a single Balance directive on them will not be marked as unverified as all (otherwise all the postings would be marked, this would make no sense). beancount.plugins.mark_unverified.mark_unverified(entries, options_map) \uf0c1 Add metadata to postings after the last Balance entry. See module doc. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/mark_unverified.py def mark_unverified(entries, options_map): \"\"\"Add metadata to postings after the last Balance entry. See module doc. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" # The last Balance directive seen for each account. last_balances = {} for entry in entries: if isinstance(entry, data.Balance): last_balances[entry.account] = entry new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): postings = entry.postings new_postings = postings for index, posting in enumerate(postings): balance = last_balances.get(posting.account, None) if balance and balance.date <= entry.date: if new_postings is postings: new_postings = postings.copy() new_meta = posting.meta.copy() new_meta['unverified'] = True new_postings[index] = posting._replace(meta=new_meta) if new_postings is not postings: entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, [] beancount.plugins.merge_meta \uf0c1 Merge the metadata from a second file into the current set of entries. This is useful if you like to keep more sensitive private data, such as account numbers or passwords, in a second, possibly encrypted file. This can be used to generate a will, for instance, for your loved ones to be able to figure where all your assets are in case you pass away. You can store all the super secret stuff in a more closely guarded, hidden away separate file. The metadata from Open directives: Account name must match. Close directives: Account name must match. Commodity directives: Currency must match. are copied over. Metadata from the external file conflicting with that present in the main file overwrites it (external data wins). WARNING! If you include an encrypted file and the main file is not encrypted, the contents extraction from the encrypted file may appear in the cache. beancount.plugins.merge_meta.merge_meta(entries, options_map, config) \uf0c1 Load a secondary file and merge its metadata in our given set of entries. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with more metadata attached to them. Source code in beancount/plugins/merge_meta.py def merge_meta(entries, options_map, config): \"\"\"Load a secondary file and merge its metadata in our given set of entries. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with more metadata attached to them. \"\"\" external_filename = config new_entries = list(entries) ext_entries, ext_errors, ext_options_map = loader.load_file(external_filename) # Map Open and Close directives. oc_map = getters.get_account_open_close(entries) ext_oc_map = getters.get_account_open_close(ext_entries) for account in set(oc_map.keys()) & set(ext_oc_map.keys()): open_entry, close_entry = oc_map[account] ext_open_entry, ext_close_entry = ext_oc_map[account] if open_entry and ext_open_entry: open_entry.meta.update(ext_open_entry.meta) if close_entry and ext_close_entry: close_entry.meta.update(ext_close_entry.meta) # Map Commodity directives. comm_map = getters.get_commodity_map(entries, False) ext_comm_map = getters.get_commodity_map(ext_entries, False) for currency in set(comm_map) & set(ext_comm_map): comm_entry = comm_map[currency] ext_comm_entry = ext_comm_map[currency] if comm_entry and ext_comm_entry: comm_entry.meta.update(ext_comm_entry.meta) # Note: We cannot include the external file in the list of inputs so that a # change of it triggers a cache rebuild because side-effects on options_map # aren't cascaded through. This is something that should be defined better # in the plugin interface and perhaps improved upon. return new_entries, ext_errors beancount.plugins.noduplicates \uf0c1 This plugin validates that there are no duplicate transactions. beancount.plugins.noduplicates.validate_no_duplicates(entries, unused_options_map) \uf0c1 Check that the entries are unique, by computing hashes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/noduplicates.py def validate_no_duplicates(entries, unused_options_map): \"\"\"Check that the entries are unique, by computing hashes. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" unused_hashes, errors = compare.hash_entries(entries, exclude_meta=True) return entries, errors beancount.plugins.nounused \uf0c1 This plugin validates that there are no unused accounts. beancount.plugins.nounused.UnusedAccountError ( tuple ) \uf0c1 UnusedAccountError(source, message, entry) beancount.plugins.nounused.UnusedAccountError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/nounused.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.nounused.UnusedAccountError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UnusedAccountError(source, message, entry) beancount.plugins.nounused.UnusedAccountError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/nounused.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.nounused.validate_unused_accounts(entries, unused_options_map) \uf0c1 Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/nounused.py def validate_unused_accounts(entries, unused_options_map): \"\"\"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Find all the accounts referenced by entries which are not Open, and the # open directives for error reporting below. open_map = {} referenced_accounts = set() for entry in entries: if isinstance(entry, data.Open): open_map[entry.account] = entry continue referenced_accounts.update(getters.get_entry_accounts(entry)) # Create a list of suitable errors, with the location of the Open directives # corresponding to the unused accounts. errors = [UnusedAccountError(open_entry.meta, \"Unused account '{}'\".format(account), open_entry) for account, open_entry in open_map.items() if account not in referenced_accounts] return entries, errors beancount.plugins.onecommodity \uf0c1 A plugin that issues errors when more than one commodity is used in an account. For investments or trading accounts, it can make it easier to filter the action around a single stock by using the name of the stock as the leaf of the account name. Notes: The plugin will automatically skip accounts that have explicitly declared commodities in their Open directive. You can also set the metadata \"onecommodity: FALSE\" on an account's Open directive to skip the checks for that account. If provided, the configuration should be a regular expression restricting the set of accounts to check. beancount.plugins.onecommodity.OneCommodityError ( tuple ) \uf0c1 OneCommodityError(source, message, entry) beancount.plugins.onecommodity.OneCommodityError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/onecommodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.onecommodity.OneCommodityError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of OneCommodityError(source, message, entry) beancount.plugins.onecommodity.OneCommodityError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/onecommodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.onecommodity.validate_one_commodity(entries, unused_options_map, config=None) \uf0c1 Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. Source code in beancount/plugins/onecommodity.py def validate_one_commodity(entries, unused_options_map, config=None): \"\"\"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Args: entries: A list of directives. unused_options_map: An options map. config: The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. \"\"\" accounts_re = re.compile(config) if config else None # Mappings of account name to lists of currencies for each units and cost. units_map = collections.defaultdict(set) cost_map = collections.defaultdict(set) # Mappings to use just for getting a relevant source. units_source_map = {} cost_source_map = {} # Gather the set of accounts to skip from the Open directives. skip_accounts = set() for entry in entries: if not isinstance(entry, data.Open): continue if (not entry.meta.get(\"onecommodity\", True) or (accounts_re and not accounts_re.match(entry.account)) or (entry.currencies and len(entry.currencies) > 1)): skip_accounts.add(entry.account) # Accumulate all the commodities used. for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.account in skip_accounts: continue units = posting.units units_map[posting.account].add(units.currency) if len(units_map[posting.account]) > 1: units_source_map[posting.account] = entry cost = posting.cost if cost: cost_map[posting.account].add(cost.currency) if len(cost_map[posting.account]) > 1: units_source_map[posting.account] = entry elif isinstance(entry, data.Balance): if entry.account in skip_accounts: continue units_map[entry.account].add(entry.amount.currency) if len(units_map[entry.account]) > 1: units_source_map[entry.account] = entry elif isinstance(entry, data.Open): if entry.currencies and len(entry.currencies) > 1: skip_accounts.add(entry.account) # Check units. errors = [] for account, currencies in units_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( units_source_map[account].meta, \"More than one currency in account '{}': {}\".format( account, ','.join(currencies)), None)) # Check costs. for account, currencies in cost_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( cost_source_map[account].meta, \"More than one cost currency in account '{}': {}\".format( account, ','.join(currencies)), None)) return entries, errors beancount.plugins.pedantic \uf0c1 A plugin of plugins which triggers are all the pedantic plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. beancount.plugins.sellgains \uf0c1 A plugin that cross-checks declared gains against prices on lot sales. When you sell stock, the gains can be automatically implied by the corresponding cash amounts. For example, in the following transaction the 2nd and 3rd postings should match the value of the stock sold: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL -10.125 USD The cost basis is checked against: 2141.36 + 008 + -10.125. That is, the balance checks computes -81 x 26.3125 = -2131.3125 + 2141.36 + 0.08 + -10.125 and checks that the residual is below a small tolerance. But... usually the income leg isn't given to you in statements. Beancount can automatically infer it using the balance, which is convenient, like this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL Additionally, most often you have the sales prices given to you on your transaction confirmation statement, so you can enter this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} @ 26.4375 USD Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL So in theory, if the price is given (26.4375 USD), we could verify that the proceeds from the sale at the given price match non-Income postings. That is, verify that -81 x 26.4375 = -2141.4375 + 2141.36 + 0.08 + is below a small tolerance value. So this plugin does this. In general terms, it does the following: For transactions with postings that have a cost and a price, it verifies that the sum of the positions on all postings to non-income accounts is below tolerance. This provides yet another level of verification and allows you to elide the income amounts, knowing that the price is there to provide an extra level of error-checking in case you enter a typo. beancount.plugins.sellgains.SellGainsError ( tuple ) \uf0c1 SellGainsError(source, message, entry) beancount.plugins.sellgains.SellGainsError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/sellgains.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.sellgains.SellGainsError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of SellGainsError(source, message, entry) beancount.plugins.sellgains.SellGainsError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/sellgains.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.sellgains.validate_sell_gains(entries, options_map) \uf0c1 Check the sum of asset account totals for lots sold with a price on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/sellgains.py def validate_sell_gains(entries, options_map): \"\"\"Check the sum of asset account totals for lots sold with a price on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] acc_types = options.get_account_types(options_map) proceed_types = set([acc_types.assets, acc_types.liabilities, acc_types.equity, acc_types.expenses]) for entry in entries: if not isinstance(entry, data.Transaction): continue # Find transactions whose lots at cost all have a price. postings_at_cost = [posting for posting in entry.postings if posting.cost is not None] if not postings_at_cost or not all(posting.price is not None for posting in postings_at_cost): continue # Accumulate the total expected proceeds and the sum of the asset and # expenses legs. total_price = inventory.Inventory() total_proceeds = inventory.Inventory() for posting in entry.postings: # If the posting is held at cost, add the priced value to the balance. if posting.cost is not None: assert posting.price is not None price = posting.price total_price.add_amount(amount.mul(price, -posting.units.number)) else: # Otherwise, use the weight and ignore postings to Income accounts. atype = account_types.get_account_type(posting.account) if atype in proceed_types: total_proceeds.add_amount(convert.get_weight(posting)) # Compare inventories, currency by currency. dict_price = {pos.units.currency: pos.units.number for pos in total_price} dict_proceeds = {pos.units.currency: pos.units.number for pos in total_proceeds} tolerances = interpolate.infer_tolerances(entry.postings, options_map) invalid = False for currency, price_number in dict_price.items(): # Accept a looser than usual tolerance because rounding occurs # differently. Also, it would be difficult for the user to satisfy # two sets of constraints manually. tolerance = tolerances.get(currency) * EXTRA_TOLERANCE_MULTIPLIER proceeds_number = dict_proceeds.pop(currency, ZERO) diff = abs(price_number - proceeds_number) if diff > tolerance: invalid = True break if invalid or dict_proceeds: errors.append( SellGainsError( entry.meta, \"Invalid price vs. proceeds/gains: {} vs. {}\".format( total_price, total_proceeds), entry)) return entries, errors beancount.plugins.split_expenses \uf0c1 Split expenses of a Beancount ledger between multiple people. This plugin is given a list of names. It assumes that any Expenses account whose components do not include any of the given names are to be split between the members. It goes through all the transactions and converts all such postings into multiple postings, one for each member. For example, given the names 'Martin' and 'Caroline', the following transaction: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation Will be converted to this: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation:Martin 134.50 USD Expenses:Accommodation:Caroline 134.50 USD After these transformations, all account names should include the name of a member. You can generate reports for a particular person by filtering postings to accounts with a component by their name. beancount.plugins.split_expenses.get_participants(filename, options_map) \uf0c1 Get the list of participants from the plugin configuration in the input file. Parameters: options_map \u2013 The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Exceptions: KeyError \u2013 If the configuration does not contain configuration for the list Source code in beancount/plugins/split_expenses.py def get_participants(filename, options_map): \"\"\"Get the list of participants from the plugin configuration in the input file. Args: options_map: The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Raises: KeyError: If the configuration does not contain configuration for the list of participants. \"\"\" plugin_options = dict(options_map[\"plugin\"]) try: return plugin_options[\"beancount.plugins.split_expenses\"].split() except KeyError: raise KeyError(\"Could not find the split_expenses plugin configuration.\") beancount.plugins.split_expenses.main() \uf0c1 Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. Source code in beancount/plugins/split_expenses.py def main(): \"\"\"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') parser.add_argument('-c', '--currency', action='store', help=\"Convert all the amounts to a single common currency\") oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output-text', '--text', action='store', help=\"Render results to text boxes\") oparser.add_argument('--output-csv', '--csv', action='store', help=\"Render results to CSV files\") oparser.add_argument('--output-stdout', '--stdout', action='store_true', help=\"Render results to stdout\") args = parser.parse_args() # Ensure the directories exist. for directory in [args.output_text, args.output_csv]: if directory and not path.exists(directory): os.makedirs(directory, exist_ok=True) # Load the input file and get the list of participants. entries, errors, options_map = loader.load_file(args.filename) participants = get_participants(args.filename, options_map) for participant in participants: print(\"Participant: {}\".format(participant)) save_query(\"balances\", participant, entries, options_map, r\"\"\" SELECT PARENT(account) AS account, CONV[SUM(position)] AS amount WHERE account ~ ':\\b{}' GROUP BY 1 ORDER BY 2 DESC \"\"\", participant, boxed=False, args=args) save_query(\"expenses\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, PARENT(account) AS account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Expenses.*\\b{}' \"\"\", participant, args=args) save_query(\"income\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Income.*\\b{}' \"\"\", participant, args=args) save_query(\"final\", None, entries, options_map, r\"\"\" SELECT GREP('\\b({})\\b', account) AS participant, CONV[SUM(position)] AS balance GROUP BY 1 ORDER BY 2 \"\"\", '|'.join(participants), args=args) # FIXME: Make this output to CSV files and upload to a spreadsheet. # FIXME: Add a fixed with option. This requires changing adding this to the # the renderer to be able to have elastic space and line splitting.. beancount.plugins.split_expenses.save_query(title, participant, entries, options_map, sql_query, *format_args, *, boxed=True, spaced=False, args=None) \uf0c1 Save the multiple files for this query. Parameters: title \u2013 A string, the title of this particular report to render. participant \u2013 A string, the name of the participant under consideration. entries \u2013 A list of directives (as per the loader). options_map \u2013 A dict of options (as per the loader). sql_query \u2013 A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args \u2013 A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed \u2013 A boolean, true if we should render the results in a fancy-looking ASCII box. spaced \u2013 If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args \u2013 A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. Source code in beancount/plugins/split_expenses.py def save_query(title, participant, entries, options_map, sql_query, *format_args, boxed=True, spaced=False, args=None): \"\"\"Save the multiple files for this query. Args: title: A string, the title of this particular report to render. participant: A string, the name of the participant under consideration. entries: A list of directives (as per the loader). options_map: A dict of options (as per the loader). sql_query: A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args: A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed: A boolean, true if we should render the results in a fancy-looking ASCII box. spaced: If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args: A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. \"\"\" # Replace CONV() to convert the currencies or not; if so, replace to # CONVERT(..., currency). replacement = (r'\\1' if args.currency is None else r'CONVERT(\\1, \"{}\")'.format(args.currency)) sql_query = re.sub(r'CONV\\[(.*?)\\]', replacement, sql_query) # Run the query. rtypes, rrows = query.run_query(entries, options_map, sql_query, *format_args, numberify=True) # The base of all filenames. filebase = title.replace(' ', '_') fmtopts = dict(boxed=boxed, spaced=spaced) # Output the text files. if args.output_text: basedir = (path.join(args.output_text, participant) if participant else args.output_text) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.txt') with open(filename, 'w') as file: query_render.render_text(rtypes, rrows, options_map['dcontext'], file, **fmtopts) # Output the CSV files. if args.output_csv: basedir = (path.join(args.output_csv, participant) if participant else args.output_csv) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.csv') with open(filename, 'w') as file: query_render.render_csv(rtypes, rrows, options_map['dcontext'], file, expand=False) if args.output_stdout: # Write out the query to stdout. query_render.render_text(rtypes, rrows, options_map['dcontext'], sys.stdout, **fmtopts) beancount.plugins.split_expenses.split_expenses(entries, options_map, config) \uf0c1 Split postings according to expenses (see module docstring for details). Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. Source code in beancount/plugins/split_expenses.py def split_expenses(entries, options_map, config): \"\"\"Split postings according to expenses (see module docstring for details). Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. \"\"\" # Validate and sanitize configuration. if isinstance(config, str): members = config.split() elif isinstance(config, (tuple, list)): members = config else: raise RuntimeError(\"Invalid plugin configuration: configuration for split_expenses \" \"should be a string or a sequence.\") acctypes = options.get_account_types(options_map) def is_expense_account(account): return account_types.get_account_type(account) == acctypes.expenses # A predicate to quickly identify if an account contains the name of a # member. is_individual_account = re.compile('|'.join(map(re.escape, members))).search # Existing and previously unseen accounts. new_accounts = set() # Filter the entries and transform transactions. new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): new_postings = [] for posting in entry.postings: if (is_expense_account(posting.account) and not is_individual_account(posting.account)): # Split this posting into multiple postings. split_units = amount.Amount(posting.units.number / len(members), posting.units.currency) for member in members: # Mark the account as new if never seen before. subaccount = account.join(posting.account, member) new_accounts.add(subaccount) # Ensure the modified postings are marked as # automatically calculated, so that the resulting # calculated amounts aren't used to affect inferred # tolerances. meta = posting.meta.copy() if posting.meta else {} meta[interpolate.AUTOMATIC_META] = True # Add a new posting for each member, to a new account # with the name of this member. new_postings.append( posting._replace(meta=meta, account=subaccount, units=split_units, cost=posting.cost)) else: new_postings.append(posting) # Modify the entry in-place, replace its postings. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Create Open directives for new subaccounts if necessary. oc_map = getters.get_account_open_close(entries) open_date = entries[0].date meta = data.new_metadata('', 0) open_entries = [] for new_account in new_accounts: if new_account not in oc_map: entry = data.Open(meta, open_date, new_account, None, None) open_entries.append(entry) return open_entries + new_entries, [] beancount.plugins.tag_pending \uf0c1 An example of tracking unpaid payables or receivables. A user with lots of invoices to track may want to produce a report of pending or incomplete payables or receivables. Beancount does not by default offer such a dedicated feature, but it is easy to build one by using existing link attributes on transactions. This is an example on how to implement that with a plugin. For example, assuming the user enters linked transactions like this: 2013-03-28 * \"Bill for datacenter electricity\" ^invoice-27a30ab61191 Expenses:Electricity 450.82 USD Liabilities:AccountsPayable 2013-04-15 * \"Paying electricity company\" ^invoice-27a30ab61191 Assets:Checking -450.82 USD Liabilities:AccountsPayable Transactions are grouped by link (\"invoice-27a30ab61191\") and then the intersection of their common accounts is automatically calculated (\"Liabilities:AccountsPayable\"). We then add up the balance of all the postings for this account in this link group and check if the sum is zero. If there is a residual amount in this balance, we mark the associated entries as incomplete by inserting a #PENDING tag on them. The user can then use that tag to navigate to the corresponding view in the web interface, or just find the entries and produce a listing of them. beancount.plugins.tag_pending.tag_pending_plugin(entries, options_map) \uf0c1 A plugin that finds and tags pending transactions. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/tag_pending.py def tag_pending_plugin(entries, options_map): \"\"\"A plugin that finds and tags pending transactions. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" return (tag_pending_transactions(entries, 'PENDING'), []) beancount.plugins.tag_pending.tag_pending_transactions(entries, tag_name='PENDING') \uf0c1 Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Parameters: entries \u2013 A list of directives/transactions to process. tag_name \u2013 A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. Source code in beancount/plugins/tag_pending.py def tag_pending_transactions(entries, tag_name='PENDING'): \"\"\"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Args: entries: A list of directives/transactions to process. tag_name: A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. \"\"\" link_groups = basicops.group_entries_by_link(entries) pending_entry_ids = set() for link, link_entries in link_groups.items(): assert link_entries if len(link_entries) == 1: # If a single entry is present, it is assumed incomplete. pending_entry_ids.add(id(link_entries[0])) else: # Compute the sum total balance of the common accounts. common_accounts = basicops.get_common_accounts(link_entries) common_balance = inventory.Inventory() for entry in link_entries: for posting in entry.postings: if posting.account in common_accounts: common_balance.add_position(posting) # Mark entries as pending if a residual balance is found. if not common_balance.is_empty(): for entry in link_entries: pending_entry_ids.add(id(entry)) # Insert tags if marked. return [(entry._replace(tags=(entry.tags or set()) | set((tag_name,))) if id(entry) in pending_entry_ids else entry) for entry in entries] beancount.plugins.unique_prices \uf0c1 This module adds validation that there is a single price defined per date and base/quote currencies. If multiple conflicting price values are declared, an error is generated. Note that multiple price entries with the same number do not generate an error. This is meant to be turned on if you want to use a very strict mode for entering prices, and may not be realistic usage. For example, if you have (1) a transaction with an implicitly generated price during the day (from its cost) and (2) a separate explicit price directive that declares a different price for the day's closing price, this would generate an error. I'm not certain this will be useful in the long run, so placing it in a plugin. beancount.plugins.unique_prices.UniquePricesError ( tuple ) \uf0c1 UniquePricesError(source, message, entry) beancount.plugins.unique_prices.UniquePricesError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unique_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.unique_prices.UniquePricesError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UniquePricesError(source, message, entry) beancount.plugins.unique_prices.UniquePricesError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/unique_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.unique_prices.validate_unique_prices(entries, unused_options_map) \uf0c1 Check that there is only a single price per day for a particular base/quote. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. Source code in beancount/plugins/unique_prices.py def validate_unique_prices(entries, unused_options_map): \"\"\"Check that there is only a single price per day for a particular base/quote. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. \"\"\" new_entries = [] errors = [] prices = collections.defaultdict(list) for entry in entries: if not isinstance(entry, data.Price): continue key = (entry.date, entry.currency, entry.amount.currency) prices[key].append(entry) errors = [] for price_entries in prices.values(): if len(price_entries) > 1: number_map = {price_entry.amount.number: price_entry for price_entry in price_entries} if len(number_map) > 1: # Note: This should be a list of entries for better error # reporting. (Later.) error_entry = next(iter(number_map.values())) errors.append( UniquePricesError(error_entry.meta, \"Disagreeing price entries\", price_entries)) return new_entries, errors beancount.plugins.unrealized \uf0c1 Compute unrealized gains. The configuration for this plugin is a single string, the name of the subaccount to add to post the unrealized gains to, like this: plugin \"beancount.plugins.unrealized\" \"Unrealized\" If you don't specify a name for the subaccount (the configuration value is optional), by default it inserts the unrealized gains in the same account that is being adjusted. beancount.plugins.unrealized.UnrealizedError ( tuple ) \uf0c1 UnrealizedError(source, message, entry) beancount.plugins.unrealized.UnrealizedError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unrealized.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.plugins.unrealized.UnrealizedError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of UnrealizedError(source, message, entry) beancount.plugins.unrealized.UnrealizedError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/plugins/unrealized.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.plugins.unrealized.add_unrealized_gains(entries, options_map, subaccount=None) \uf0c1 Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. subaccount \u2013 A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/unrealized.py def add_unrealized_gains(entries, options_map, subaccount=None): \"\"\"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. subaccount: A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" errors = [] meta = data.new_metadata('', 0) account_types = options.get_account_types(options_map) # Assert the subaccount name is in valid format. if subaccount: validation_account = account.join(account_types.assets, subaccount) if not account.is_valid(validation_account): errors.append( UnrealizedError(meta, \"Invalid subaccount name: '{}'\".format(subaccount), None)) return entries, errors if not entries: return (entries, errors) # Group positions by (account, cost, cost_currency). price_map = prices.build_price_map(entries) holdings_list = holdings.get_final_holdings(entries, price_map=price_map) # Group positions by (account, cost, cost_currency). holdings_list = holdings.aggregate_holdings_by( holdings_list, lambda h: (h.account, h.currency, h.cost_currency)) # Get the latest prices from the entries. price_map = prices.build_price_map(entries) # Create transactions to account for each position. new_entries = [] latest_date = entries[-1].date for index, holding in enumerate(holdings_list): if (holding.currency == holding.cost_currency or holding.cost_currency is None): continue # Note: since we're only considering positions held at cost, the # transaction that created the position *must* have created at least one # price point for that commodity, so we never expect for a price not to # be available, which is reasonable. if holding.price_number is None: # An entry without a price might indicate that this is a holding # resulting from leaked cost basis. {0ed05c502e63, b/16} if holding.number: errors.append( UnrealizedError(meta, \"A valid price for {h.currency}/{h.cost_currency} \" \"could not be found\".format(h=holding), None)) continue # Compute the PnL; if there is no profit or loss, we create a # corresponding entry anyway. pnl = holding.market_value - holding.book_value if holding.number == ZERO: # If the number of units sum to zero, the holdings should have been # zero. errors.append( UnrealizedError( meta, \"Number of units of {} in {} in holdings sum to zero \" \"for account {} and should not\".format( holding.currency, holding.cost_currency, holding.account), None)) continue # Compute the name of the accounts and add the requested subaccount name # if requested. asset_account = holding.account income_account = account.join(account_types.income, account.sans_root(holding.account)) if subaccount: asset_account = account.join(asset_account, subaccount) income_account = account.join(income_account, subaccount) # Create a new transaction to account for this difference in gain. gain_loss_str = \"gain\" if pnl > ZERO else \"loss\" narration = (\"Unrealized {} for {h.number} units of {h.currency} \" \"(price: {h.price_number:.4f} {h.cost_currency} as of {h.price_date}, \" \"average cost: {h.cost_number:.4f} {h.cost_currency})\").format( gain_loss_str, h=holding) entry = data.Transaction(data.new_metadata(meta[\"filename\"], lineno=1000 + index), latest_date, flags.FLAG_UNREALIZED, None, narration, EMPTY_SET, EMPTY_SET, []) # Book this as income, converting the account name to be the same, but as income. # Note: this is a rather convenient but arbitrary choice--maybe it would be best to # let the user decide to what account to book it, but I don't a nice way to let the # user specify this. # # Note: we never set a price because we don't want these to end up in Conversions. entry.postings.extend([ data.Posting( asset_account, amount.Amount(pnl, holding.cost_currency), None, None, None, None), data.Posting( income_account, amount.Amount(-pnl, holding.cost_currency), None, None, None, None) ]) new_entries.append(entry) # Ensure that the accounts we're going to use to book the postings exist, by # creating open entries for those that we generated that weren't already # existing accounts. new_accounts = {posting.account for entry in new_entries for posting in entry.postings} open_entries = getters.get_account_open_close(entries) new_open_entries = [] for account_ in sorted(new_accounts): if account_ not in open_entries: meta = data.new_metadata(meta[\"filename\"], index) open_entry = data.Open(meta, latest_date, account_, None, None) new_open_entries.append(open_entry) return (entries + new_open_entries + new_entries, errors) beancount.plugins.unrealized.get_unrealized_entries(entries) \uf0c1 Return entries automatically created for unrealized gains. Parameters: entries \u2013 A list of directives. Returns: A list of directives, all of which are in the original list. Source code in beancount/plugins/unrealized.py def get_unrealized_entries(entries): \"\"\"Return entries automatically created for unrealized gains. Args: entries: A list of directives. Returns: A list of directives, all of which are in the original list. \"\"\" return [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.flag == flags.FLAG_UNREALIZED)]","title":"beancount.plugins"},{"location":"api_reference/beancount.plugins.html#beancountplugins","text":"Example plugins for filtering transactions. These are various examples of how to filter entries in various creative ways. IMPORTANT: These are not meant to be complete features, rather just experiments in problem-solving using Beancount, work-in-progress that can be selectively installed via a --plugin option, or one-offs to answer questions on the mailing-list.","title":"beancount.plugins"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto","text":"A plugin of plugins which triggers are all the automatic and lax plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests. You can just import the \"auto\" plugin. Put that in a macro. Also see: the 'pedantic' plugin.","title":"auto"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto_accounts","text":"This module automatically inserts Open directives for accounts not opened (at the date of the first entry) and automatically removes open directives for unused accounts. This can be used as a convenience for doing demos, or when setting up your initial transactions, as an intermediate step.","title":"auto_accounts"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.auto_accounts.auto_insert_open","text":"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/auto_accounts.py def auto_insert_open(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" opened_accounts = {entry.account for entry in entries if isinstance(entry, data.Open)} new_entries = [] accounts_first, _ = getters.get_accounts_use_map(entries) for index, (account, date_first_used) in enumerate(sorted(accounts_first.items())): if account not in opened_accounts: meta = data.new_metadata('', index) new_entries.append(data.Open(meta, date_first_used, account, None, None)) if new_entries: new_entries.extend(entries) new_entries.sort(key=data.entry_sortkey) else: new_entries = entries return new_entries, []","title":"auto_insert_open()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions","text":"A plugin that automatically converts postings at price to postings held at cost, applying an automatic booking algorithm in assigning the cost bases and matching lots. This plugin restricts itself to applying these transformations within a particular account, which you provide. For each of those accounts, it also requires a corresponding Income account to book the profit/loss of reducing lots (i.e., sales): plugin \"beancount.plugins.book_conversions\" \"Assets:Bitcoin,Income:Bitcoin\" Then, simply input the transactions with price conversion. We use \"Bitcoins\" in this example, converting Bitcoin purchases that were carried out as currency into maintaining these with cost basis, for tax reporting purposes: 2015-09-04 * \"Buy some bitcoins\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.333507 BTC @ 230.76 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bank -1000.00 USD Assets:Bitcoin 4.345747 BTC @ 230.11 USD 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -6.000000 BTC @ 230.50 USD Expenses:Something The result is that cost bases are inserted on augmenting lots: 2015-09-04 * \"Buy some bitcoins\" Assets:Bitcoin 4.333507 BTC {230.76 USD} @ 230.76 USD Assets:Bank -1000.00 USD 2015-09-05 * \"Buy some more bitcoins at a different price\" Assets:Bitcoin 4.345747 BTC {230.11 USD} @ 230.11 USD Assets:Bank -1000.00 USD While on reducing lots, matching FIFO lots are automatically found and the corresponding cost basis added: 2015-09-20 * \"Use (sell) some bitcoins\" Assets:Bitcoin -4.333507 BTC {230.76 USD} @ 230.50 USD Assets:Bitcoin -1.666493 BTC {230.11 USD} @ 230.50 USD Income:Bitcoin 0.47677955 USD Expenses:Something 1383.00000000 USD Note that multiple lots were required to fulfill the sale quantity here. As in this example, this may result in multiple lots being created for a single one. Finally, Beancount will eventually support booking methods built-in, but this is a quick method that shows how to hack your own booking method via transformations of the postings that run in a plugin. Implementation notes: This code uses the FIFO method only for now. However, it would be very easy to customize it to provide other booking methods, e.g. LIFO, or otherwise. This will be added eventually, and I'm hoping to reuse the same inventory abstractions that will be used to implement the fallback booking methods from the booking proposal review (http://furius.ca/beancount/doc/proposal-booking). Instead of keeping a list of (Position, Transaction) pairs for the pending FIFO lots, we really ought to use a beancount.core.inventory.Inventory instance. However, the class does not contain sufficient data to carry out FIFO booking at the moment. A newer implementation, living in the \"booking\" branch, does, and will be used in the future. This code assumes that a positive number of units is an augmenting lot and a reducing one has a negative number of units, though we never call them that way on purpose (to eventually allow this code to handle short positions). This is not strictly true; however, we would need an Inventory in order to figrue this out. This will be done in the future and is not difficult to do. IMPORTANT: This plugin was developed before the booking methods (FIFO, LIFO, and others) were fully implemented in Beancount. It was built to answer a question on the mailing-list about FIFO booking. You probably don't need to use them anymore. Always prefer to use the native syntax instead of this.","title":"book_conversions"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError","text":"BookConversionError(source, message, entry)","title":"BookConversionError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__new__","text":"Create new instance of BookConversionError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.BookConversionError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError","text":"ConfigError(source, message, entry)","title":"ConfigError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/book_conversions.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__new__","text":"Create new instance of ConfigError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.ConfigError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/book_conversions.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.augment_inventory","text":"Add the lots from the given posting to the running inventory. Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. entry \u2013 The parent transaction. eindex \u2013 The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. Source code in beancount/plugins/book_conversions.py def augment_inventory(pending_lots, posting, entry, eindex): \"\"\"Add the lots from the given posting to the running inventory. Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. entry: The parent transaction. eindex: The index of the parent transaction housing this posting. Returns: A new posting with cost basis inserted to be added to a transformed transaction. \"\"\" number = posting.units.number new_posting = posting._replace( units=copy.copy(posting.units), cost=position.Cost(posting.price.number, posting.price.currency, entry.date, None)) pending_lots.append(([number], new_posting, eindex)) return new_posting","title":"augment_inventory()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.book_price_conversions","text":"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Parameters: entries \u2013 A list of entry instances. assets_account \u2013 An account string, the name of the account to process. income_account \u2013 An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. Source code in beancount/plugins/book_conversions.py def book_price_conversions(entries, assets_account, income_account): \"\"\"Rewrite transactions to insert cost basis according to a booking method. See module docstring for full details. Args: entries: A list of entry instances. assets_account: An account string, the name of the account to process. income_account: An account string, the name of the account to use for booking realized profit/loss. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. matches: A list of (number, augmenting-posting, reducing-postings) for all matched lots. \"\"\" # Pairs of (Position, Transaction) instances used to match augmenting # entries with reducing ones. pending_lots = [] # A list of pairs of matching (augmenting, reducing) postings. all_matches = [] new_entries = [] errors = [] for eindex, entry in enumerate(entries): # Figure out if this transaction has postings in Bitcoins without a cost. # The purpose of this plugin is to fixup those. if isinstance(entry, data.Transaction) and any(is_matching(posting, assets_account) for posting in entry.postings): # Segregate the reducing lots, augmenting lots and other lots. augmenting, reducing, other = [], [], [] for pindex, posting in enumerate(entry.postings): if is_matching(posting, assets_account): out = augmenting if posting.units.number >= ZERO else reducing else: out = other out.append(posting) # We will create a replacement list of postings with costs filled # in, possibly more than the original list, to account for the # different lots. new_postings = [] # Convert all the augmenting postings to cost basis. for posting in augmenting: new_postings.append(augment_inventory(pending_lots, posting, entry, eindex)) # Then process reducing postings. if reducing: # Process all the reducing postings, booking them to matching lots. pnl = inventory.Inventory() for posting in reducing: rpostings, matches, posting_pnl, new_errors = ( reduce_inventory(pending_lots, posting, eindex)) new_postings.extend(rpostings) all_matches.extend(matches) errors.extend(new_errors) pnl.add_amount(amount.Amount(posting_pnl, posting.price.currency)) # If some reducing lots were seen in this transaction, insert an # Income leg to absorb the P/L. We need to do this for each currency # which incurred P/L. if not pnl.is_empty(): for pos in pnl: meta = data.new_metadata('', 0) new_postings.append( data.Posting(income_account, -pos.units, None, None, None, meta)) # Third, add back all the other unrelated legs in. for posting in other: new_postings.append(posting) # Create a replacement entry. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Add matching metadata to all matching postings. mod_matches = link_entries_with_metadata(new_entries, all_matches) # Resolve the indexes to their possibly modified Transaction instances. matches = [(data.TxnPosting(new_entries[aug_index], aug_posting), data.TxnPosting(new_entries[red_index], red_posting)) for (aug_index, aug_posting), (red_index, red_posting) in mod_matches] return new_entries, errors, matches","title":"book_price_conversions()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.book_price_conversions_plugin","text":"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. config \u2013 A string, in \",\" format. Returns: A tuple of entries \u2013 A list of new, modified entries. errors: A list of errors generated by this plugin. Source code in beancount/plugins/book_conversions.py def book_price_conversions_plugin(entries, options_map, config): \"\"\"Plugin that rewrites transactions to insert cost basis according to a booking method. See book_price_conversions() for details. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. config: A string, in \",\" format. Returns: A tuple of entries: A list of new, modified entries. errors: A list of errors generated by this plugin. \"\"\" # The expected configuration is two account names, separated by whitespace. errors = [] try: assets_account, income_account = re.split(r'[,; \\t]', config) if not account.is_valid(assets_account) or not account.is_valid(income_account): raise ValueError(\"Invalid account string\") except ValueError as exc: errors.append( ConfigError( None, ('Invalid configuration: \"{}\": {}, skipping booking').format(config, exc), None)) return entries, errors new_entries, errors, _ = book_price_conversions(entries, assets_account, income_account) return new_entries, errors","title":"book_price_conversions_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.extract_trades","text":"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Parameters: entries \u2013 The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). Source code in beancount/plugins/book_conversions.py def extract_trades(entries): \"\"\"Find all the matching trades from the metadata attached to postings. This inspects all the postings and pairs them up using the special metadata field that was added by this plugin when booking matching lots, and returns pairs of those postings. Args: entries: The list of directives to extract from. Returns: A list of (number, augmenting-posting, reducing-posting). \"\"\" trade_map = collections.defaultdict(list) for index, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue for posting in entry.postings: links_str = posting.meta.get(META, None) if links_str: links = links_str.split(',') for link in links: trade_map[link].append((index, entry, posting)) # Sort matches according to the index of the first entry, drop the index # used for doing this, and convert the objects to tuples.. trades = [(data.TxnPosting(augmenting[1], augmenting[2]), data.TxnPosting(reducing[1], reducing[2])) for augmenting, reducing in sorted(trade_map.values())] # Sanity check. for matches in trades: assert len(matches) == 2 return trades","title":"extract_trades()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.is_matching","text":"\"Identify if the given posting is one to be booked. Parameters: posting \u2013 An instance of a Posting. account \u2013 The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. Source code in beancount/plugins/book_conversions.py def is_matching(posting, account): \"\"\"\"Identify if the given posting is one to be booked. Args: posting: An instance of a Posting. account: The account name configured. Returns: A boolean, true if this posting is one that we should be adding a cost to. \"\"\" return (posting.account == account and posting.cost is None and posting.price is not None)","title":"is_matching()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.link_entries_with_metadata","text":"Modify the entries in-place to add matching links to postings. Parameters: entries \u2013 The list of entries to modify. all_matches \u2013 A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. Source code in beancount/plugins/book_conversions.py def link_entries_with_metadata(entries, all_matches): \"\"\"Modify the entries in-place to add matching links to postings. Args: entries: The list of entries to modify. all_matches: A list of pairs of (augmenting-posting, reducing-posting). Returns: A list of pairs of (index, Posting) for the new (augmenting, reducing) annotated postings. \"\"\" # Allocate trade names and compute a map of posting to trade names. link_map = collections.defaultdict(list) for (aug_index, aug_posting), (red_index, red_posting) in all_matches: link = 'trade-{}'.format(str(uuid.uuid4()).split('-')[-1]) link_map[id(aug_posting)].append(link) link_map[id(red_posting)].append(link) # Modify the postings. postings_repl_map = {} for entry in entries: if isinstance(entry, data.Transaction): for index, posting in enumerate(entry.postings): links = link_map.pop(id(posting), None) if links: new_posting = posting._replace(meta=posting.meta.copy()) new_posting.meta[META] = ','.join(links) entry.postings[index] = new_posting postings_repl_map[id(posting)] = new_posting # Just a sanity check. assert not link_map, \"Internal error: not all matches found.\" # Return a list of the modified postings (mapping the old matches to the # newly created ones). return [((aug_index, postings_repl_map[id(aug_posting)]), (red_index, postings_repl_map[id(red_posting)])) for (aug_index, aug_posting), (red_index, red_posting) in all_matches]","title":"link_entries_with_metadata()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.main","text":"Extract trades from metadata-annotated postings and report on them. Source code in beancount/plugins/book_conversions.py def main(): \"\"\"Extract trades from metadata-annotated postings and report on them. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output', action='store', help=\"Filename to output results to (default goes to stdout)\") oparser.add_argument('-f', '--format', default='text', choices=['text', 'csv'], help=\"Output format to render to (text, csv)\") args = parser.parse_args() # Load the input file. entries, errors, options_map = loader.load_file(args.filename) # Get the list of trades. trades = extract_trades(entries) # Produce a table of all the trades. columns = ('units currency cost_currency ' 'buy_date buy_price sell_date sell_price pnl').split() header = ['Units', 'Currency', 'Cost Currency', 'Buy Date', 'Buy Price', 'Sell Date', 'Sell Price', 'P/L'] body = [] for aug, red in trades: units = -red.posting.units.number buy_price = aug.posting.price.number sell_price = red.posting.price.number pnl = (units * (sell_price - buy_price)).quantize(buy_price) body.append([ -red.posting.units.number, red.posting.units.currency, red.posting.price.currency, aug.txn.date.isoformat(), buy_price, red.txn.date.isoformat(), sell_price, pnl ]) trades_table = table.Table(columns, header, body) # Render the table as text or CSV. outfile = open(args.output, 'w') if args.output else sys.stdout table.render_table(trades_table, outfile, args.format)","title":"main()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.book_conversions.reduce_inventory","text":"Match a reducing posting against a list of lots (using FIFO order). Parameters: pending_lots \u2013 A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting \u2013 The posting whose position is to be added. eindex \u2013 The index of the parent transaction housing this posting. Returns: A tuple of postings \u2013 A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. Source code in beancount/plugins/book_conversions.py def reduce_inventory(pending_lots, posting, eindex): \"\"\"Match a reducing posting against a list of lots (using FIFO order). Args: pending_lots: A list of pending ([number], Posting, Transaction) to be matched. The number is modified in-place, destructively. posting: The posting whose position is to be added. eindex: The index of the parent transaction housing this posting. Returns: A tuple of postings: A list of new Posting instances corresponding to the given posting, that were booked to the current list of lots. matches: A list of pairs of (augmenting-posting, reducing-posting). pnl: A Decimal, the P/L incurred in reducing these lots. errors: A list of new errors generated in reducing these lots. \"\"\" new_postings = [] matches = [] pnl = ZERO errors = [] match_number = -posting.units.number match_currency = posting.units.currency cost_currency = posting.price.currency while match_number != ZERO: # Find the first lot with matching currency. for fnumber, fposting, findex in pending_lots: funits = fposting.units fcost = fposting.cost if (funits.currency == match_currency and fcost and fcost.currency == cost_currency): assert fnumber[0] > ZERO, \"Internal error, zero lot\" break else: errors.append( BookConversionError(posting.meta, \"Could not match position {}\".format(posting), None)) break # Reduce the pending lots. number = min(match_number, fnumber[0]) cost = fcost match_number -= number fnumber[0] -= number if fnumber[0] == ZERO: pending_lots.pop(0) # Add a corresponding posting. rposting = posting._replace( units=amount.Amount(-number, posting.units.currency), cost=copy.copy(cost)) new_postings.append(rposting) # Update the P/L. pnl += number * (posting.price.number - cost.number) # Add to the list of matches. matches.append(((findex, fposting), (eindex, rposting))) return new_postings, matches, pnl, errors","title":"reduce_inventory()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost","text":"A plugin that ensures cost basis is preserved in unbooked transactions. This is intended to be used in accounts using the \"NONE\" booking method, to manually ensure that the sum total of the cost basis of reducing legs matches the average of what's in the account inventory. This is a partial first step toward implementing the \"AVERAGE\" booking method. In other words, this plugins provides assertions that will constrain you to approximate what the \"AVERAGE\" booking method will do, manually, and not to leak too much cost basis through unmatching bookings without checks. (Note the contrived context here: Ideally the \"NONE\" booking method would simply not exist.)","title":"check_average_cost"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError","text":"MatchBasisError(source, message, entry)","title":"MatchBasisError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_average_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__new__","text":"Create new instance of MatchBasisError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.MatchBasisError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/check_average_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_average_cost.validate_average_cost","text":"Check that reducing legs on unbooked postings are near the average cost basis. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 The configuration as a string version of a float. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_average_cost.py def validate_average_cost(entries, options_map, config_str=None): \"\"\"Check that reducing legs on unbooked postings are near the average cost basis. Args: entries: A list of directives. unused_options_map: An options map. config_str: The configuration as a string version of a float. Returns: A list of new errors, if any were found. \"\"\" # Initialize tolerance bounds. if config_str and config_str.strip(): # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, float): raise RuntimeError(\"Invalid configuration for check_average_cost: \" \"must be a float\") tolerance = config_obj else: tolerance = DEFAULT_TOLERANCE min_tolerance = D(1 - tolerance) max_tolerance = D(1 + tolerance) errors = [] ocmap = getters.get_account_open_close(entries) balances = collections.defaultdict(inventory.Inventory) for entry in entries: if isinstance(entry, Transaction): for posting in entry.postings: dopen = ocmap.get(posting.account, None) # Only process accounts with a NONE booking value. if dopen and dopen[0] and dopen[0].booking == Booking.NONE: balance = balances[(posting.account, posting.units.currency, posting.cost.currency if posting.cost else None)] if posting.units.number < ZERO: average = balance.average().get_only_position() if average is not None: number = average.cost.number min_valid = number * min_tolerance max_valid = number * max_tolerance if not (min_valid <= posting.cost.number <= max_valid): errors.append( MatchBasisError( entry.meta, (\"Cost basis on reducing posting is too far from \" \"the average cost ({} vs. {})\".format( posting.cost.number, average.cost.number)), entry)) balance.add_position(posting) return entries, errors","title":"validate_average_cost()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_closing","text":"A plugin that automatically inserts a balance check on a tagged closing posting. Some postings are known to the user to be \"closing trades\", which means that the resulting position of the instrument just after the trade should be zero. For instance, this is the case for most ordinary options trading, only one lot of a particular instrument is held, and eventually expires or gets sold off in its entirely. One would like to confirm that, and the way to do this in Beancount is to insert a balance check. This plugin allows you to do that more simply, by inserting metadata. For example, this transaction: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD closing: TRUE Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL Would expand into the following two directives: 2018-02-16 * \"SOLD -14 QQQ 100 16 FEB 18 160 CALL @5.31\" Assets:US:Brokerage:Main:Options -1400 QQQ180216C160 {2.70 USD} @ 5.31 USD Expenses:Financial:Commissions 17.45 USD Expenses:Financial:Fees 0.42 USD Assets:US:Brokerage:Main:Cash 7416.13 USD Income:US:Brokerage:Main:PnL 2018-02-17 balance Assets:US:Brokerage:Main:Options 0 QQQ180216C160 Insert the closing line when you know you're closing the position.","title":"check_closing"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_closing.check_closing","text":"Expand 'closing' metadata to a zero balance check. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_closing.py def check_closing(entries, options_map): \"\"\"Expand 'closing' metadata to a zero balance check. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.meta and posting.meta.get('closing', False): # Remove the metadata. meta = posting.meta.copy() del meta['closing'] entry = entry._replace(meta=meta) # Insert a balance. date = entry.date + datetime.timedelta(days=1) balance = data.Balance(data.new_metadata(\"\", 0), date, posting.account, amount.Amount(ZERO, posting.units.currency), None, None) new_entries.append(balance) new_entries.append(entry) return new_entries, []","title":"check_closing()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity","text":"A plugin that verifies that all seen commodities have a Commodity directive. This is useful if you're a bit pedantic and like to make sure that you're declared attributes for each of the commodities you use. It's useful if you use the portfolio export, for example.","title":"check_commodity"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError","text":"CheckCommodityError(source, message, entry)","title":"CheckCommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/check_commodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__new__","text":"Create new instance of CheckCommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.CheckCommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/check_commodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.check_commodity.validate_commodity_directives","text":"Find all commodities used and ensure they have a corresponding Commodity directive. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/check_commodity.py def validate_commodity_directives(entries, options_map): \"\"\"Find all commodities used and ensure they have a corresponding Commodity directive. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" commodities_used = options_map['commodities'] errors = [] meta = data.new_metadata('', 0) commodity_map = getters.get_commodity_map(entries, create_missing=False) for currency in commodities_used: commodity_entry = commodity_map.get(currency, None) if commodity_entry is None: errors.append( CheckCommodityError( meta, \"Missing Commodity directive for '{}'\".format(currency), None)) return entries, errors","title":"validate_commodity_directives()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost","text":"This plugin validates that currencies held at cost aren't ever converted at price and vice-versa. This is usually the case, and using it will prevent users from making the mistake of selling a lot without specifying it via its cost basis.","title":"coherent_cost"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError","text":"CoherentCostError(source, message, entry)","title":"CoherentCostError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/coherent_cost.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__new__","text":"Create new instance of CoherentCostError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.CoherentCostError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/coherent_cost.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.coherent_cost.validate_coherent_cost","text":"Check that all currencies are either used at cost or not at all, but never both. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/coherent_cost.py def validate_coherent_cost(entries, unused_options_map): \"\"\"Check that all currencies are either used at cost or not at all, but never both. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] with_cost = {} without_cost = {} for entry in data.filter_txns(entries): for posting in entry.postings: target_set = without_cost if posting.cost is None else with_cost currency = posting.units.currency target_set.setdefault(currency, entry) for currency in set(with_cost) & set(without_cost): errors.append( CoherentCostError( without_cost[currency].meta, \"Currency '{}' is used both with and without cost\".format(currency), with_cost[currency])) # Note: We really ought to include both of the first transactions here. return entries, errors","title":"validate_coherent_cost()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr","text":"A plugin that asserts that all Commodity directives have a particular attribute and that it is part of a set of enum values. The configuration must be a mapping of attribute name to list of valid values, like this: plugin \"beancount.plugins.commodity_attr\" \"{ 'sector': ['Technology', 'Financials', 'Energy'], 'name': None, }\" The plugin issues an error if a Commodity directive is missing the attribute, or if the attribute value is not in the valid set. If you'd like to just ensure the attribute is set, set the list of valid values to None, as in the 'name' attribute in the example above.","title":"commodity_attr"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError","text":"CommodityError(source, message, entry)","title":"CommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__new__","text":"Create new instance of CommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.CommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError","text":"ConfigError(source, message, entry)","title":"ConfigError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/commodity_attr.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__new__","text":"Create new instance of ConfigError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.ConfigError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/commodity_attr.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.commodity_attr.validate_commodity_attr","text":"Check that all Commodity directives have a valid attribute. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config_str \u2013 A configuration string. Returns: A list of new errors, if any were found. Source code in beancount/plugins/commodity_attr.py def validate_commodity_attr(entries, unused_options_map, config_str): \"\"\"Check that all Commodity directives have a valid attribute. Args: entries: A list of directives. unused_options_map: An options map. config_str: A configuration string. Returns: A list of new errors, if any were found. \"\"\" errors = [] # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): errors.append(ConfigError( data.new_metadata('', 0), \"Invalid configuration for commodity_attr plugin; skipping.\", None)) return entries, errors validmap = {attr: frozenset(values) if values is not None else None for attr, values in config_obj.items()} for entry in entries: if not isinstance(entry, data.Commodity): continue for attr, values in validmap.items(): value = entry.meta.get(attr, None) if value is None: errors.append(CommodityError( entry.meta, \"Missing attribute '{}' for Commodity directive {}\".format( attr, entry.currency), None)) continue if values and value not in values: errors.append(CommodityError( entry.meta, \"Invalid attribute '{}' for Commodity\".format(value) + \" directive {}; valid options: {}\".format( entry.currency, ', '.join(values)), None)) return entries, errors","title":"validate_commodity_attr()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts","text":"An implementation of currency accounts. This is an automatic implementation of the method described here: https://www.mathstat.dal.ca/~selinger/accounting/tutorial.html You enable it just like this: plugin \"beancount.plugins.currency_accounts\" \"Equity:CurrencyAccounts\" Accounts will be automatically created under the given base account, with the currency name appended to it, e.g., Equity:CurrencyAccounts:CAD Equity:CurrencyAccounts:USD etc., where used. You can have a look at the account balances with a query like this: bean-query $L \"select account, sum(position), convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts' \" The sum total of the converted amounts should be a number not too large: bean-query $L \"select convert(sum(position), 'USD') where date >= 2018-01-01 and account ~ 'CurrencyAccounts'\" WARNING: This is a prototype. Note the FIXMEs in the code below, which indicate some potential problems.","title":"currency_accounts"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.get_neutralizing_postings","text":"Process an entry. Parameters: curmap \u2013 A dict of currency to a list of Postings of this transaction. base_account \u2013 A string, the root account name to insert. new_accounts \u2013 A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. Source code in beancount/plugins/currency_accounts.py def get_neutralizing_postings(curmap, base_account, new_accounts): \"\"\"Process an entry. Args: curmap: A dict of currency to a list of Postings of this transaction. base_account: A string, the root account name to insert. new_accounts: A set, a mutable accumulator of new account names. Returns: A modified entry, with new postings inserted to rebalance currency trading accounts. \"\"\" new_postings = [] for currency, postings in curmap.items(): # Compute the per-currency balance. inv = inventory.Inventory() for posting in postings: inv.add_amount(convert.get_cost(posting)) if inv.is_empty(): new_postings.extend(postings) continue # Re-insert original postings and remove price conversions. # # Note: This may cause problems if the implicit_prices plugin is # configured to run after this one, or if you need the price annotations # for some scripting or serious work. # # FIXME: We need to handle these important cases (they're not frivolous, # this is a prototype), probably by inserting some exceptions with # collaborating code in the booking (e.g. insert some metadata that # disables price conversions on those postings). # # FIXME(2): Ouch! Some of the residual seeps through here, where there # are more than a single currency block. This needs fixing too. You can # easily mitigate some of this to some extent, by excluding transactions # which don't have any price conversion in them. for pos in postings: if pos.price is not None: pos = pos._replace(price=None) new_postings.append(pos) # Insert the currency trading accounts postings. amount = inv.get_only_position().units acc = account.join(base_account, currency) new_accounts.add(acc) new_postings.append( Posting(acc, -amount, None, None, None, None)) return new_postings","title":"get_neutralizing_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.group_postings_by_weight_currency","text":"Return where this entry might require adjustment. Source code in beancount/plugins/currency_accounts.py def group_postings_by_weight_currency(entry: Transaction): \"\"\"Return where this entry might require adjustment.\"\"\" curmap = collections.defaultdict(list) has_price = False for posting in entry.postings: currency = posting.units.currency if posting.cost: currency = posting.cost.currency if posting.price: assert posting.price.currency == currency elif posting.price: has_price = True currency = posting.price.currency if posting.price: has_price = True curmap[currency].append(posting) return curmap, has_price","title":"group_postings_by_weight_currency()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.currency_accounts.insert_currency_trading_postings","text":"Insert currency trading postings. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The base account name for currency trading accounts. Returns: A list of new errors, if any were found. Source code in beancount/plugins/currency_accounts.py def insert_currency_trading_postings(entries, options_map, config): \"\"\"Insert currency trading postings. Args: entries: A list of directives. unused_options_map: An options map. config: The base account name for currency trading accounts. Returns: A list of new errors, if any were found. \"\"\" base_account = config.strip() if not account.is_valid(base_account): base_account = DEFAULT_BASE_ACCOUNT errors = [] new_entries = [] new_accounts = set() for entry in entries: if isinstance(entry, Transaction): curmap, has_price = group_postings_by_weight_currency(entry) if has_price and len(curmap) > 1: new_postings = get_neutralizing_postings( curmap, base_account, new_accounts) entry = entry._replace(postings=new_postings) if META_PROCESSED: entry.meta[META_PROCESSED] = True new_entries.append(entry) earliest_date = entries[0].date open_entries = [ data.Open(data.new_metadata('', index), earliest_date, acc, None, None) for index, acc in enumerate(sorted(new_accounts))] return open_entries + new_entries, errors","title":"insert_currency_trading_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses","text":"For tagged transactions, convert expenses to a single account. This plugin allows you to select a tag and it automatically converts all the Expenses postings to use a single account. For example, with this input: plugin \"divert_expenses\" \"['kid', 'Expenses:Child']\" 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Food:Grocery 10.27 USD It will output: 2018-01-28 * \"CVS\" \"Formula\" #kid Liabilities:CreditCard -10.27 USD Expenses:Child 10.27 USD You can limit the diversion to one posting only, like this: 2018-05-05 * \"CVS/PHARMACY\" \"\" #kai Liabilities:CreditCard -66.38 USD Expenses:Pharmacy 21.00 USD ;; Vitamins for Kai Expenses:Pharmacy 45.38 USD divert: FALSE See unit test for details. See this thread for context: https://docs.google.com/drawings/d/18fTrrGlmz0jFbfcGGHTffbdRwbmST8r9_3O26Dd1Xww/edit?usp=sharing","title":"divert_expenses"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses.divert_expenses","text":"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. options_map \u2013 A parser options dict. config_str \u2013 A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. Source code in beancount/plugins/divert_expenses.py def divert_expenses(entries, options_map, config_str): \"\"\"Divert expenses. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. options_map: A parser options dict. config_str: A configuration string, which is intended to be a list of two strings, a tag, and an account to replace expenses with. Returns: A modified list of entries. \"\"\" # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") tag = config_obj['tag'] replacement_account = config_obj['account'] acctypes = options.get_account_types(options_map) new_entries = [] errors = [] for entry in entries: if isinstance(entry, Transaction) and tag in entry.tags: entry = replace_diverted_accounts(entry, replacement_account, acctypes) new_entries.append(entry) return new_entries, errors","title":"divert_expenses()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.divert_expenses.replace_diverted_accounts","text":"Replace the Expenses accounts from the entry. Parameters: entry \u2013 A Transaction directive. replacement_account \u2013 A string, the account to use for replacement. acctypes \u2013 An AccountTypes instance. Returns: A possibly entry directive. Source code in beancount/plugins/divert_expenses.py def replace_diverted_accounts(entry, replacement_account, acctypes): \"\"\"Replace the Expenses accounts from the entry. Args: entry: A Transaction directive. replacement_account: A string, the account to use for replacement. acctypes: An AccountTypes instance. Returns: A possibly entry directive. \"\"\" new_postings = [] for posting in entry.postings: divert = posting.meta.get('divert', None) if posting.meta else None if (divert is True or ( divert is None and account_types.is_account_type(acctypes.expenses, posting.account))): posting = posting._replace(account=replacement_account, meta={'diverted_account': posting.account}) new_postings.append(posting) return entry._replace(postings=new_postings)","title":"replace_diverted_accounts()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.exclude_tag","text":"Exclude #virtual tags. This is used to demonstrate excluding a set of transactions from a particular tag. In this example module, the tag name is fixed, but if we integrated this we could provide a way to choose which tags to exclude. This is simply just another mechanism for selecting a subset of transactions. See discussion here for details: https://groups.google.com/d/msg/ledger-cli/N8Slh2t45K0/aAz0i3Be4LYJ","title":"exclude_tag"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.exclude_tag.exclude_tag","text":"Select all transactions that do not have a #virtual tag. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/exclude_tag.py def exclude_tag(entries, options_map): \"\"\"Select all transactions that do not have a #virtual tag. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" filtered_entries = [entry for entry in entries if (not isinstance(entry, data.Transaction) or not entry.tags or EXCLUDED_TAG not in entry.tags)] return (filtered_entries, [])","title":"exclude_tag()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account","text":"Insert an posting with a default account when there is only a single posting. This is convenient to use in files which have mostly expenses, such as during a trip. Set the name of the default account to fill in as an option.","title":"fill_account"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError","text":"FillAccountError(source, message, entry)","title":"FillAccountError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fill_account.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__new__","text":"Create new instance of FillAccountError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.FillAccountError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/fill_account.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fill_account.fill_account","text":"Insert an posting with a default account when there is only a single posting. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 A parser options dict. insert_account \u2013 A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/fill_account.py def fill_account(entries, unused_options_map, insert_account): \"\"\"Insert an posting with a default account when there is only a single posting. Args: entries: A list of directives. unused_options_map: A parser options dict. insert_account: A string, the name of the account. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" if not account.is_valid(insert_account): return entries, [ FillAccountError(data.new_metadata('', 0), \"Invalid account name: '{}'\".format(insert_account), None)] new_entries = [] for entry in entries: if isinstance(entry, data.Transaction) and len(entry.postings) == 1: inv = inventory.Inventory() for posting in entry.postings: if posting.cost is None: inv.add_amount(posting.units) else: inv.add_amount(convert.get_cost(posting)) inv.reduce(convert.get_units) new_postings = list(entry.postings) for pos in inv: new_postings.append(data.Posting(insert_account, -pos.units, None, None, None, None)) entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, []","title":"fill_account()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees","text":"Rename payees based on a set of rules. This can be used to clean up dirty imported payee names. This plugin accepts a list of rules in this format: plugin \"beancount.plugins.fix_payees\" \"[ (PAYEE, MATCH1, MATCH2, ...), ]\" Each of the \"MATCH\" clauses is a string, in the format: \"A:<regexp>\" : Match the account name. \"D:<regexp>\" : Match the payee or the narration. The plugin matches the Transactions in the file and if there is a case-insensitive match against the regular expression (we use re.search()), replaces the payee name by \"PAYEE\". If multiple rules match, only the first rule is used. For example: plugin \"beancount.plugins.fix_payees\" \"[ (\"T-Mobile USA\", \"A:Expenses:Communications:Phone\", \"D:t-mobile\"), (\"Con Edison\", \"A:Expenses:Home:Electricity\", \"D:con ?ed\"), (\"Birreria @ Eataly\", \"D:EATALY BIRRERIA\"), ]\"","title":"fix_payees"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError","text":"FixPayeesError(source, message, entry)","title":"FixPayeesError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/fix_payees.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__new__","text":"Create new instance of FixPayeesError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.FixPayeesError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/fix_payees.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.fix_payees.fix_payees","text":"Rename payees based on a set of rules. See module docstring for details. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config \u2013 A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. Source code in beancount/plugins/fix_payees.py def fix_payees(entries, options_map, config): \"\"\"Rename payees based on a set of rules. See module docstring for details. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config: A configuration string, which is intended to be a list of (PAYEE, MATCH, ...) rules. See module docstring for details. Returns: A tuple of entries and errors. \"\"\" errors = [] if config.strip(): try: expr = ast.literal_eval(config) except (SyntaxError, ValueError): meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Syntax error in config: {}\".format(config), None)) return entries, errors else: return entries, errors # Pre-compile the regular expressions for performance. rules = [] for rule in ast.literal_eval(config): clauses = iter(rule) new_payee = next(clauses) regexps = [] for clause in clauses: match = re.match('([AD]):(.*)', clause) if not match: meta = data.new_metadata(options_map['filename'], 0) errors.append(FixPayeesError(meta, \"Invalid clause: {}\".format(clause), None)) continue command, regexp = match.groups() regexps.append((command, re.compile(regexp, re.I).search)) new_rule = [new_payee] + regexps rules.append(tuple(new_rule)) # Run the rules over the transaction objects. new_entries = [] replaced_entries = {rule[0]: [] for rule in rules} for entry in entries: if isinstance(entry, data.Transaction): for rule in rules: clauses = iter(rule) new_payee = next(clauses) # Attempt to match all the clauses. for clause in clauses: command, func = clause if command == 'D': if not ((entry.payee is not None and func(entry.payee)) or (entry.narration is not None and func(entry.narration))): break elif command == 'A': if not any(func(posting.account) for posting in entry.postings): break else: # Make the replacement. entry = entry._replace(payee=new_payee) replaced_entries[new_payee].append(entry) new_entries.append(entry) if _DEBUG: # Print debugging info. for payee, repl_entries in sorted(replaced_entries.items(), key=lambda x: len(x[1]), reverse=True): print('{:60}: {}'.format(payee, len(repl_entries))) return new_entries, errors","title":"fix_payees()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.forecast","text":"An example of adding a forecasting feature to Beancount via a plugin. This entry filter plugin uses existing syntax to define and automatically inserted transactions in the future based on a convention. It serves mostly as an example of how you can experiment by creating and installing a local filter, and not so much as a serious forecasting feature (though the experiment is a good way to get something more general kickstarted eventually, I think the concept would generalize nicely and should eventually be added as a common feature of Beancount). A user can create a transaction like this: 2014-03-08 # \"Electricity bill [MONTHLY]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD and new transactions will be created monthly for the following year. Note the use of the '#' flag and the word 'MONTHLY' which defines the periodicity. The number of recurrences can optionally be specified either by providing an end date or by specifying the number of times that the transaction will be repeated. For example: 2014-03-08 # \"Electricity bill [MONTHLY UNTIL 2019-12-31]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [MONTHLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Transactions can also be repeated at yearly intervals, e.g.: 2014-03-08 # \"Electricity bill [YEARLY REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD Other examples: 2014-03-08 # \"Electricity bill [WEEKLY SKIP 1 TIME REPEAT 10 TIMES]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD 2014-03-08 # \"Electricity bill [DAILY SKIP 3 TIMES REPEAT 1 TIME]\" Expenses:Electricity 50.10 USD Assets:Checking -50.10 USD","title":"forecast"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.forecast.forecast_plugin","text":"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file Returns: A tuple of entries and errors. Source code in beancount/plugins/forecast.py def forecast_plugin(entries, options_map): \"\"\"An example filter that piggybacks on top of the Beancount input syntax to insert forecast entries automatically. This functions accepts the return value of beancount.loader.load_file() and must return the same type of output. Args: entries: a list of entry instances options_map: a dict of options parsed from the file Returns: A tuple of entries and errors. \"\"\" # Find the last entry's date. date_today = entries[-1].date # Filter out forecast entries from the list of valid entries. forecast_entries = [] filtered_entries = [] for entry in entries: outlist = (forecast_entries if (isinstance(entry, data.Transaction) and entry.flag == '#') else filtered_entries) outlist.append(entry) # Generate forecast entries up to the end of the current year. new_entries = [] for entry in forecast_entries: # Parse the periodicity. match = re.search(r'(^.*)\\[(MONTHLY|YEARLY|WEEKLY|DAILY)' r'(\\s+SKIP\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+REPEAT\\s+([1-9][0-9]*)\\s+TIME.?)' r'?(\\s+UNTIL\\s+([0-9\\-]+))?\\]', entry.narration) if not match: new_entries.append(entry) continue forecast_narration = match.group(1).strip() forecast_interval = ( rrule.YEARLY if match.group(2).strip() == 'YEARLY' else rrule.WEEKLY if match.group(2).strip() == 'WEEKLY' else rrule.DAILY if match.group(2).strip() == 'DAILY' else rrule.MONTHLY) forecast_periodicity = {'dtstart': entry.date} if match.group(6): # e.g., [MONTHLY REPEAT 3 TIMES]: forecast_periodicity['count'] = int(match.group(6)) elif match.group(8): # e.g., [MONTHLY UNTIL 2020-01-01]: forecast_periodicity['until'] = datetime.datetime.strptime( match.group(8), '%Y-%m-%d').date() else: # e.g., [MONTHLY] forecast_periodicity['until'] = datetime.date( datetime.date.today().year, 12, 31) if match.group(4): # SKIP forecast_periodicity['interval'] = int(match.group(4)) + 1 # Generate a new entry for each forecast date. forecast_dates = [dt.date() for dt in rrule.rrule(forecast_interval, **forecast_periodicity)] for forecast_date in forecast_dates: forecast_entry = entry._replace(date=forecast_date, narration=forecast_narration) new_entries.append(forecast_entry) # Make sure the new entries inserted are sorted. new_entries.sort(key=data.entry_sortkey) return (filtered_entries + new_entries, [])","title":"forecast_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices","text":"This plugin synthesizes Price directives for all Postings with a price or directive or if it is an augmenting posting, has a cost directive.","title":"implicit_prices"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError","text":"ImplicitPriceError(source, message, entry)","title":"ImplicitPriceError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/implicit_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__new__","text":"Create new instance of ImplicitPriceError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.ImplicitPriceError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/implicit_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.implicit_prices.add_implicit_prices","text":"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. Source code in beancount/plugins/implicit_prices.py def add_implicit_prices(entries, unused_options_map): \"\"\"Insert implicitly defined prices from Transactions. Explicit price entries are simply maintained in the output list. Prices from postings with costs or with prices from Transaction entries are synthesized as new Price entries in the list of entries output. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: A list of entries, possibly with more Price entries than before, and a list of errors. \"\"\" new_entries = [] errors = [] # A dict of (date, currency, cost-currency) to price entry. new_price_entry_map = {} balances = collections.defaultdict(inventory.Inventory) for entry in entries: # Always replicate the existing entries. new_entries.append(entry) if isinstance(entry, Transaction): # Inspect all the postings in the transaction. for posting in entry.postings: units = posting.units cost = posting.cost # Check if the position is matching against an existing # position. _, booking = balances[posting.account].add_position(posting) # Add prices when they're explicitly specified on a posting. An # explicitly specified price may occur in a conversion, e.g. # Assets:Account 100 USD @ 1.10 CAD # or, if a cost is also specified, as the current price of the # underlying instrument, e.g. # Assets:Account 100 HOOL {564.20} @ {581.97} USD if posting.price is not None: meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, posting.price) # Add costs, when we're not matching against an existing # position. This happens when we're just specifying the cost, # e.g. # Assets:Account 100 HOOL {564.20} elif (cost is not None and booking != inventory.Booking.REDUCED): meta = data.new_metadata(entry.meta[\"filename\"], entry.meta[\"lineno\"]) price_entry = data.Price(meta, entry.date, units.currency, amount.Amount(cost.number, cost.currency)) else: price_entry = None if price_entry is not None: key = (price_entry.date, price_entry.currency, price_entry.amount.number, # Ideally should be removed. price_entry.amount.currency) try: new_price_entry_map[key] ## Do not fail for now. We still have many valid use ## cases of duplicate prices on the same date, for ## example, stock splits, or trades on two dates with ## two separate reported prices. We need to figure out a ## more elegant solution for this in the long term. ## Keeping both for now. We should ideally not use the ## number in the de-dup key above. # # dup_entry = new_price_entry_map[key] # if price_entry.amount.number == dup_entry.amount.number: # # Skip duplicates. # continue # else: # errors.append( # ImplicitPriceError( # entry.meta, # \"Duplicate prices for {} on {}\".format(entry, # dup_entry), # entry)) except KeyError: new_price_entry_map[key] = price_entry new_entries.append(price_entry) return new_entries, errors","title":"add_implicit_prices()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs","text":"Automatically adding IRA contributions postings. This plugin looks for increasing postings on specified accounts ('+' sign for Assets and Expenses accounts, '-' sign for the others), or postings with a particular flag on them and when it finds some, inserts a pair of postings on that transaction of the corresponding amounts in a different currency. The currency is intended to be an imaginary currency used to track the number of dollars contributed to a retirement account over time. For example, a possible configuration could be: plugin \"beancount.plugins.ira_contribs\" \"{ 'currency': 'IRAUSD', 'flag': 'M', 'accounts': { 'Income:US:Acme:Match401k': ( 'Assets:US:Federal:Match401k', 'Expenses:Taxes:TY{year}:US:Federal:Match401k'), ('C', 'Assets:US:Fidelity:PreTax401k:Cash'): ( 'Assets:US:Federal:PreTax401k', 'Expenses:Taxes:TY{year}:US:Federal:PreTax401k'), } }\" Note: In this example, the configuration that triggers on the \"Income:US:Acme:Match401k\" account does not require a flag for those accounts; the configuration for the \"Assets:US:Fidelity:PreTax401k:Cash\" account requires postings to have a \"C\" flag to trigger an insertion. Given a transaction like the following, which would be typical for a salary entry where the employer is automatically diverting some of the pre-tax money to a retirement account (in this example, at Fidelity): 2013-02-15 * \"ACME INC PAYROLL\" Income:US:Acme:Salary ... ... Assets:US:BofA:Checking ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD ... A posting with account 'Assets:US:Fidelity:PreTax401k:Cash', which is configured to match, would be found. The configuration above instructs the plugin to automatically insert new postings like this: 2013-02-15 * \"ACME INC PAYROLL\" ... Assets:US:Fidelity:PreTax401k:Cash 620.50 USD M Assets:US:Federal:PreTax401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:PreTax401k 620.50 IRAUSD ... Notice that the \"{year}\" string in the configuration's account names is automatically replaced by the current year in the account name. This is useful if you maintain separate tax accounts per year. Furthermore, as in the configuration example above, you may have multiple matching entries to trigger multiple insertions. For example, the employer may also match the employee's retirement contribution by depositing some money in the retirement account: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD In this example the funds get reported as invested immediately (an intermediate deposit into a cash account does not take place). The plugin configuration would match against the 'Income:US:Acme:Match401k' account and since it increases its value (the normal balance of an Income account is negative), postings would be inserted like this: 2013-02-15 * \"BUYMF - MATCH\" \"Employer match, invested in SaveEasy 2030 fund\" Assets:US:Fidelity:Match401k:SE2030 34.793 SE2030 {17.834 USD} Income:US:Acme:Match401k -620.50 USD M Assets:US:Federal:Match401k -620.50 IRAUSD M Expenses:Taxes:TY2013:US:Federal:Match401k 620.50 IRAUSD Note that the special dict keys 'currency' and 'flag' are used to specify which currency to use for the inserted postings, and if set, which flag to mark these postings with.","title":"ira_contribs"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs.add_ira_contribs","text":"Add legs for 401k employer match contributions. See module docstring for an example configuration. Parameters: entries \u2013 a list of entry instances options_map \u2013 a dict of options parsed from the file config_str \u2013 A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. Source code in beancount/plugins/ira_contribs.py def add_ira_contribs(entries, options_map, config_str): \"\"\"Add legs for 401k employer match contributions. See module docstring for an example configuration. Args: entries: a list of entry instances options_map: a dict of options parsed from the file config_str: A configuration string, which is intended to be a Python dict mapping match-accounts to a pair of (negative-account, position-account) account names. Returns: A tuple of entries and errors. \"\"\" # Parse and extract configuration values. # FIXME: Use ast.literal_eval() here; you need to convert this code and the getters. # FIXME: Also, don't raise a RuntimeError, return an error object; review # this for all the plugins. # FIXME: This too is temporary. # pylint: disable=eval-used config_obj = eval(config_str, {}, {}) if not isinstance(config_obj, dict): raise RuntimeError(\"Invalid plugin configuration: should be a single dict.\") # Currency of the inserted postings. currency = config_obj.pop('currency', 'UNKNOWN') # Flag to attach to the inserted postings. insert_flag = config_obj.pop('flag', None) # A dict of account names that trigger the insertion of postings to pairs of # inserted accounts when triggered. accounts = config_obj.pop('accounts', {}) # Convert the key in the accounts configuration for matching. account_transforms = {} for key, config in accounts.items(): if isinstance(key, str): flag = None account = key else: assert isinstance(key, tuple) flag, account = key account_transforms[account] = (flag, config) new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): orig_entry = entry for posting in entry.postings: if (posting.units is not MISSING and (posting.account in account_transforms) and (account_types.get_account_sign(posting.account) * posting.units.number > 0)): # Get the new account legs to insert. required_flag, (neg_account, pos_account) = account_transforms[posting.account] assert posting.cost is None # Check required flag if present. if (required_flag is None or (required_flag and required_flag == posting.flag)): # Insert income/expense entries for 401k. entry = add_postings( entry, amount.Amount(abs(posting.units.number), currency), neg_account.format(year=entry.date.year), pos_account.format(year=entry.date.year), insert_flag) if DEBUG and orig_entry is not entry: printer.print_entry(orig_entry) printer.print_entry(entry) new_entries.append(entry) return new_entries, []","title":"add_ira_contribs()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.ira_contribs.add_postings","text":"Insert positive and negative postings of a position in an entry. Parameters: entry \u2013 A Transaction instance. amount_ \u2013 An Amount instance to create the position, with positive number. neg_account \u2013 An account for the posting with the negative amount. pos_account \u2013 An account for the posting with the positive amount. flag \u2013 A string, that is to be set as flag for the new postings. Returns: A new, modified entry. Source code in beancount/plugins/ira_contribs.py def add_postings(entry, amount_, neg_account, pos_account, flag): \"\"\"Insert positive and negative postings of a position in an entry. Args: entry: A Transaction instance. amount_: An Amount instance to create the position, with positive number. neg_account: An account for the posting with the negative amount. pos_account: An account for the posting with the positive amount. flag: A string, that is to be set as flag for the new postings. Returns: A new, modified entry. \"\"\" return entry._replace(postings=entry.postings + [ data.Posting(neg_account, -amount_, None, None, flag, None), data.Posting(pos_account, amount_, None, None, flag, None), ])","title":"add_postings()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly","text":"A plugin that issues errors when amounts are posted to non-leaf accounts, that is, accounts with child accounts. This is an extra constraint that you may want to apply optionally. If you install this plugin, it will issue errors for all accounts that have postings to non-leaf accounts. Some users may want to disallow this and enforce that only leaf accounts may have postings on them.","title":"leafonly"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError","text":"LeafOnlyError(source, message, entry)","title":"LeafOnlyError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/leafonly.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__new__","text":"Create new instance of LeafOnlyError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.LeafOnlyError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/leafonly.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.leafonly.validate_leaf_only","text":"Check for non-leaf accounts that have postings on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/leafonly.py def validate_leaf_only(entries, unused_options_map): \"\"\"Check for non-leaf accounts that have postings on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" real_root = realization.realize(entries, compute_balance=False) default_meta = data.new_metadata('', 0) open_close_map = None # Lazily computed. errors = [] for real_account in realization.iter_children(real_root): if len(real_account) > 0 and real_account.txn_postings: if open_close_map is None: open_close_map = getters.get_account_open_close(entries) try: open_entry = open_close_map[real_account.account][0] except KeyError: open_entry = None errors.append(LeafOnlyError( open_entry.meta if open_entry else default_meta, \"Non-leaf account '{}' has postings on it\".format(real_account.account), open_entry)) return entries, errors","title":"validate_leaf_only()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.mark_unverified","text":"Add metadata to Postings which occur after their last Balance directives. Some people use Balance directives as a way to indicate that all postings before them are verified. They want to compute balances in each account as of the date of that last Balance directives. One way to do that is to use this plugin to mark the postings which occur after and to then filter them out using a WHERE clause on that metadata: SELECT account, sum(position) WHERE NOT meta(\"unverified\") Note that doing such a filtering may result in a list of balances which may not add to zero. Also, postings for accounts without a single Balance directive on them will not be marked as unverified as all (otherwise all the postings would be marked, this would make no sense).","title":"mark_unverified"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.mark_unverified.mark_unverified","text":"Add metadata to postings after the last Balance entry. See module doc. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/mark_unverified.py def mark_unverified(entries, options_map): \"\"\"Add metadata to postings after the last Balance entry. See module doc. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" # The last Balance directive seen for each account. last_balances = {} for entry in entries: if isinstance(entry, data.Balance): last_balances[entry.account] = entry new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): postings = entry.postings new_postings = postings for index, posting in enumerate(postings): balance = last_balances.get(posting.account, None) if balance and balance.date <= entry.date: if new_postings is postings: new_postings = postings.copy() new_meta = posting.meta.copy() new_meta['unverified'] = True new_postings[index] = posting._replace(meta=new_meta) if new_postings is not postings: entry = entry._replace(postings=new_postings) new_entries.append(entry) return new_entries, []","title":"mark_unverified()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.merge_meta","text":"Merge the metadata from a second file into the current set of entries. This is useful if you like to keep more sensitive private data, such as account numbers or passwords, in a second, possibly encrypted file. This can be used to generate a will, for instance, for your loved ones to be able to figure where all your assets are in case you pass away. You can store all the super secret stuff in a more closely guarded, hidden away separate file. The metadata from Open directives: Account name must match. Close directives: Account name must match. Commodity directives: Currency must match. are copied over. Metadata from the external file conflicting with that present in the main file overwrites it (external data wins). WARNING! If you include an encrypted file and the main file is not encrypted, the contents extraction from the encrypted file may appear in the cache.","title":"merge_meta"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.merge_meta.merge_meta","text":"Load a secondary file and merge its metadata in our given set of entries. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with more metadata attached to them. Source code in beancount/plugins/merge_meta.py def merge_meta(entries, options_map, config): \"\"\"Load a secondary file and merge its metadata in our given set of entries. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with more metadata attached to them. \"\"\" external_filename = config new_entries = list(entries) ext_entries, ext_errors, ext_options_map = loader.load_file(external_filename) # Map Open and Close directives. oc_map = getters.get_account_open_close(entries) ext_oc_map = getters.get_account_open_close(ext_entries) for account in set(oc_map.keys()) & set(ext_oc_map.keys()): open_entry, close_entry = oc_map[account] ext_open_entry, ext_close_entry = ext_oc_map[account] if open_entry and ext_open_entry: open_entry.meta.update(ext_open_entry.meta) if close_entry and ext_close_entry: close_entry.meta.update(ext_close_entry.meta) # Map Commodity directives. comm_map = getters.get_commodity_map(entries, False) ext_comm_map = getters.get_commodity_map(ext_entries, False) for currency in set(comm_map) & set(ext_comm_map): comm_entry = comm_map[currency] ext_comm_entry = ext_comm_map[currency] if comm_entry and ext_comm_entry: comm_entry.meta.update(ext_comm_entry.meta) # Note: We cannot include the external file in the list of inputs so that a # change of it triggers a cache rebuild because side-effects on options_map # aren't cascaded through. This is something that should be defined better # in the plugin interface and perhaps improved upon. return new_entries, ext_errors","title":"merge_meta()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.noduplicates","text":"This plugin validates that there are no duplicate transactions.","title":"noduplicates"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.noduplicates.validate_no_duplicates","text":"Check that the entries are unique, by computing hashes. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/noduplicates.py def validate_no_duplicates(entries, unused_options_map): \"\"\"Check that the entries are unique, by computing hashes. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" unused_hashes, errors = compare.hash_entries(entries, exclude_meta=True) return entries, errors","title":"validate_no_duplicates()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused","text":"This plugin validates that there are no unused accounts.","title":"nounused"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError","text":"UnusedAccountError(source, message, entry)","title":"UnusedAccountError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/nounused.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__new__","text":"Create new instance of UnusedAccountError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.UnusedAccountError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/nounused.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.nounused.validate_unused_accounts","text":"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/nounused.py def validate_unused_accounts(entries, unused_options_map): \"\"\"Check that all accounts declared open are actually used. We check that all of the accounts that are open are at least referred to by another directive. These are probably unused, so issue a warning (we like to be pedantic). Note that an account that is open and then closed is considered used--this is a valid use case that may occur in reality. If you have a use case for an account to be open but never used, you can quiet that warning by initializing the account with a balance asserts or a pad directive, or even use a note will be sufficient. (This is probably a good candidate for optional inclusion as a \"pedantic\" plugin.) Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" # Find all the accounts referenced by entries which are not Open, and the # open directives for error reporting below. open_map = {} referenced_accounts = set() for entry in entries: if isinstance(entry, data.Open): open_map[entry.account] = entry continue referenced_accounts.update(getters.get_entry_accounts(entry)) # Create a list of suitable errors, with the location of the Open directives # corresponding to the unused accounts. errors = [UnusedAccountError(open_entry.meta, \"Unused account '{}'\".format(account), open_entry) for account, open_entry in open_map.items() if account not in referenced_accounts] return entries, errors","title":"validate_unused_accounts()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity","text":"A plugin that issues errors when more than one commodity is used in an account. For investments or trading accounts, it can make it easier to filter the action around a single stock by using the name of the stock as the leaf of the account name. Notes: The plugin will automatically skip accounts that have explicitly declared commodities in their Open directive. You can also set the metadata \"onecommodity: FALSE\" on an account's Open directive to skip the checks for that account. If provided, the configuration should be a regular expression restricting the set of accounts to check.","title":"onecommodity"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError","text":"OneCommodityError(source, message, entry)","title":"OneCommodityError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/onecommodity.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__new__","text":"Create new instance of OneCommodityError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.OneCommodityError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/onecommodity.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.onecommodity.validate_one_commodity","text":"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. config \u2013 The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. Source code in beancount/plugins/onecommodity.py def validate_one_commodity(entries, unused_options_map, config=None): \"\"\"Check that each account has units in only a single commodity. This is an extra constraint that you may want to apply optionally, despite Beancount's ability to support inventories and aggregations with more than one commodity. I believe this also matches GnuCash's model, where each account has a single commodity attached to it. Args: entries: A list of directives. unused_options_map: An options map. config: The plugin configuration string, a regular expression to match against the subset of accounts to check. Returns: A list of new errors, if any were found. \"\"\" accounts_re = re.compile(config) if config else None # Mappings of account name to lists of currencies for each units and cost. units_map = collections.defaultdict(set) cost_map = collections.defaultdict(set) # Mappings to use just for getting a relevant source. units_source_map = {} cost_source_map = {} # Gather the set of accounts to skip from the Open directives. skip_accounts = set() for entry in entries: if not isinstance(entry, data.Open): continue if (not entry.meta.get(\"onecommodity\", True) or (accounts_re and not accounts_re.match(entry.account)) or (entry.currencies and len(entry.currencies) > 1)): skip_accounts.add(entry.account) # Accumulate all the commodities used. for entry in entries: if isinstance(entry, data.Transaction): for posting in entry.postings: if posting.account in skip_accounts: continue units = posting.units units_map[posting.account].add(units.currency) if len(units_map[posting.account]) > 1: units_source_map[posting.account] = entry cost = posting.cost if cost: cost_map[posting.account].add(cost.currency) if len(cost_map[posting.account]) > 1: units_source_map[posting.account] = entry elif isinstance(entry, data.Balance): if entry.account in skip_accounts: continue units_map[entry.account].add(entry.amount.currency) if len(units_map[entry.account]) > 1: units_source_map[entry.account] = entry elif isinstance(entry, data.Open): if entry.currencies and len(entry.currencies) > 1: skip_accounts.add(entry.account) # Check units. errors = [] for account, currencies in units_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( units_source_map[account].meta, \"More than one currency in account '{}': {}\".format( account, ','.join(currencies)), None)) # Check costs. for account, currencies in cost_map.items(): if account in skip_accounts: continue if len(currencies) > 1: errors.append(OneCommodityError( cost_source_map[account].meta, \"More than one cost currency in account '{}': {}\".format( account, ','.join(currencies)), None)) return entries, errors","title":"validate_one_commodity()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.pedantic","text":"A plugin of plugins which triggers are all the pedantic plugins. In a sense, this is the inverse of \"pedantic.\" This is useful when doing some types of quick and dirty tests.","title":"pedantic"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains","text":"A plugin that cross-checks declared gains against prices on lot sales. When you sell stock, the gains can be automatically implied by the corresponding cash amounts. For example, in the following transaction the 2nd and 3rd postings should match the value of the stock sold: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL -10.125 USD The cost basis is checked against: 2141.36 + 008 + -10.125. That is, the balance checks computes -81 x 26.3125 = -2131.3125 + 2141.36 + 0.08 + -10.125 and checks that the residual is below a small tolerance. But... usually the income leg isn't given to you in statements. Beancount can automatically infer it using the balance, which is convenient, like this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL Additionally, most often you have the sales prices given to you on your transaction confirmation statement, so you can enter this: 1999-07-31 * \"Sell\" Assets:US:BRS:Company:ESPP -81 ADSK {26.3125 USD} @ 26.4375 USD Assets:US:BRS:Company:Cash 2141.36 USD Expenses:Financial:Fees 0.08 USD Income:US:Company:ESPP:PnL So in theory, if the price is given (26.4375 USD), we could verify that the proceeds from the sale at the given price match non-Income postings. That is, verify that -81 x 26.4375 = -2141.4375 + 2141.36 + 0.08 + is below a small tolerance value. So this plugin does this. In general terms, it does the following: For transactions with postings that have a cost and a price, it verifies that the sum of the positions on all postings to non-income accounts is below tolerance. This provides yet another level of verification and allows you to elide the income amounts, knowing that the price is there to provide an extra level of error-checking in case you enter a typo.","title":"sellgains"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError","text":"SellGainsError(source, message, entry)","title":"SellGainsError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/sellgains.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__new__","text":"Create new instance of SellGainsError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.SellGainsError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/sellgains.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.sellgains.validate_sell_gains","text":"Check the sum of asset account totals for lots sold with a price on them. Parameters: entries \u2013 A list of directives. unused_options_map \u2013 An options map. Returns: A list of new errors, if any were found. Source code in beancount/plugins/sellgains.py def validate_sell_gains(entries, options_map): \"\"\"Check the sum of asset account totals for lots sold with a price on them. Args: entries: A list of directives. unused_options_map: An options map. Returns: A list of new errors, if any were found. \"\"\" errors = [] acc_types = options.get_account_types(options_map) proceed_types = set([acc_types.assets, acc_types.liabilities, acc_types.equity, acc_types.expenses]) for entry in entries: if not isinstance(entry, data.Transaction): continue # Find transactions whose lots at cost all have a price. postings_at_cost = [posting for posting in entry.postings if posting.cost is not None] if not postings_at_cost or not all(posting.price is not None for posting in postings_at_cost): continue # Accumulate the total expected proceeds and the sum of the asset and # expenses legs. total_price = inventory.Inventory() total_proceeds = inventory.Inventory() for posting in entry.postings: # If the posting is held at cost, add the priced value to the balance. if posting.cost is not None: assert posting.price is not None price = posting.price total_price.add_amount(amount.mul(price, -posting.units.number)) else: # Otherwise, use the weight and ignore postings to Income accounts. atype = account_types.get_account_type(posting.account) if atype in proceed_types: total_proceeds.add_amount(convert.get_weight(posting)) # Compare inventories, currency by currency. dict_price = {pos.units.currency: pos.units.number for pos in total_price} dict_proceeds = {pos.units.currency: pos.units.number for pos in total_proceeds} tolerances = interpolate.infer_tolerances(entry.postings, options_map) invalid = False for currency, price_number in dict_price.items(): # Accept a looser than usual tolerance because rounding occurs # differently. Also, it would be difficult for the user to satisfy # two sets of constraints manually. tolerance = tolerances.get(currency) * EXTRA_TOLERANCE_MULTIPLIER proceeds_number = dict_proceeds.pop(currency, ZERO) diff = abs(price_number - proceeds_number) if diff > tolerance: invalid = True break if invalid or dict_proceeds: errors.append( SellGainsError( entry.meta, \"Invalid price vs. proceeds/gains: {} vs. {}\".format( total_price, total_proceeds), entry)) return entries, errors","title":"validate_sell_gains()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses","text":"Split expenses of a Beancount ledger between multiple people. This plugin is given a list of names. It assumes that any Expenses account whose components do not include any of the given names are to be split between the members. It goes through all the transactions and converts all such postings into multiple postings, one for each member. For example, given the names 'Martin' and 'Caroline', the following transaction: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation Will be converted to this: 2015-02-01 * \"Aqua Viva Tulum - two nights\" Income:Caroline:CreditCard -269.00 USD Expenses:Accommodation:Martin 134.50 USD Expenses:Accommodation:Caroline 134.50 USD After these transformations, all account names should include the name of a member. You can generate reports for a particular person by filtering postings to accounts with a component by their name.","title":"split_expenses"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.get_participants","text":"Get the list of participants from the plugin configuration in the input file. Parameters: options_map \u2013 The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Exceptions: KeyError \u2013 If the configuration does not contain configuration for the list Source code in beancount/plugins/split_expenses.py def get_participants(filename, options_map): \"\"\"Get the list of participants from the plugin configuration in the input file. Args: options_map: The options map, as produced by the parser. Returns: A list of strings, the names of participants as they should appear in the account names. Raises: KeyError: If the configuration does not contain configuration for the list of participants. \"\"\" plugin_options = dict(options_map[\"plugin\"]) try: return plugin_options[\"beancount.plugins.split_expenses\"].split() except KeyError: raise KeyError(\"Could not find the split_expenses plugin configuration.\")","title":"get_participants()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.main","text":"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. Source code in beancount/plugins/split_expenses.py def main(): \"\"\"Generate final reports for a shared expenses on a trip or project. For each of many participants, generate a detailed list of expenses, contributions, a categorized summary of expenses, and a final balance. Also produce a global list of final balances so that participants can reconcile between each other. \"\"\" logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') parser = version.ArgumentParser(description=__doc__.strip()) parser.add_argument('filename', help='Beancount input filename') parser.add_argument('-c', '--currency', action='store', help=\"Convert all the amounts to a single common currency\") oparser = parser.add_argument_group('Outputs') oparser.add_argument('-o', '--output-text', '--text', action='store', help=\"Render results to text boxes\") oparser.add_argument('--output-csv', '--csv', action='store', help=\"Render results to CSV files\") oparser.add_argument('--output-stdout', '--stdout', action='store_true', help=\"Render results to stdout\") args = parser.parse_args() # Ensure the directories exist. for directory in [args.output_text, args.output_csv]: if directory and not path.exists(directory): os.makedirs(directory, exist_ok=True) # Load the input file and get the list of participants. entries, errors, options_map = loader.load_file(args.filename) participants = get_participants(args.filename, options_map) for participant in participants: print(\"Participant: {}\".format(participant)) save_query(\"balances\", participant, entries, options_map, r\"\"\" SELECT PARENT(account) AS account, CONV[SUM(position)] AS amount WHERE account ~ ':\\b{}' GROUP BY 1 ORDER BY 2 DESC \"\"\", participant, boxed=False, args=args) save_query(\"expenses\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, PARENT(account) AS account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Expenses.*\\b{}' \"\"\", participant, args=args) save_query(\"income\", participant, entries, options_map, r\"\"\" SELECT date, flag, description, account, JOINSTR(links) AS links, CONV[position] AS amount, CONV[balance] AS balance WHERE account ~ 'Income.*\\b{}' \"\"\", participant, args=args) save_query(\"final\", None, entries, options_map, r\"\"\" SELECT GREP('\\b({})\\b', account) AS participant, CONV[SUM(position)] AS balance GROUP BY 1 ORDER BY 2 \"\"\", '|'.join(participants), args=args) # FIXME: Make this output to CSV files and upload to a spreadsheet. # FIXME: Add a fixed with option. This requires changing adding this to the # the renderer to be able to have elastic space and line splitting..","title":"main()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.save_query","text":"Save the multiple files for this query. Parameters: title \u2013 A string, the title of this particular report to render. participant \u2013 A string, the name of the participant under consideration. entries \u2013 A list of directives (as per the loader). options_map \u2013 A dict of options (as per the loader). sql_query \u2013 A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args \u2013 A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed \u2013 A boolean, true if we should render the results in a fancy-looking ASCII box. spaced \u2013 If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args \u2013 A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. Source code in beancount/plugins/split_expenses.py def save_query(title, participant, entries, options_map, sql_query, *format_args, boxed=True, spaced=False, args=None): \"\"\"Save the multiple files for this query. Args: title: A string, the title of this particular report to render. participant: A string, the name of the participant under consideration. entries: A list of directives (as per the loader). options_map: A dict of options (as per the loader). sql_query: A string with the SQL query, possibly with some placeholders left for *format_args to replace. *format_args: A tuple of arguments to be formatted into the SQL query string. This is provided as a convenience. boxed: A boolean, true if we should render the results in a fancy-looking ASCII box. spaced: If true, leave an empty line between each of the rows. This is useful if the results have a lot of rows that render over multiple lines. args: A dummy object with the following attributes: output_text: An optional directory name, to produce a text rendering of the report. output_csv: An optional directory name, to produce a CSV rendering of the report. output_stdout: A boolean, if true, also render the output to stdout. currency: An optional currency (a string). If you use this, you should wrap query targets to be converted with the pseudo-function \"CONV[...]\" and it will get replaced to CONVERT(..., CURRENCY) automatically. \"\"\" # Replace CONV() to convert the currencies or not; if so, replace to # CONVERT(..., currency). replacement = (r'\\1' if args.currency is None else r'CONVERT(\\1, \"{}\")'.format(args.currency)) sql_query = re.sub(r'CONV\\[(.*?)\\]', replacement, sql_query) # Run the query. rtypes, rrows = query.run_query(entries, options_map, sql_query, *format_args, numberify=True) # The base of all filenames. filebase = title.replace(' ', '_') fmtopts = dict(boxed=boxed, spaced=spaced) # Output the text files. if args.output_text: basedir = (path.join(args.output_text, participant) if participant else args.output_text) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.txt') with open(filename, 'w') as file: query_render.render_text(rtypes, rrows, options_map['dcontext'], file, **fmtopts) # Output the CSV files. if args.output_csv: basedir = (path.join(args.output_csv, participant) if participant else args.output_csv) os.makedirs(basedir, exist_ok=True) filename = path.join(basedir, filebase + '.csv') with open(filename, 'w') as file: query_render.render_csv(rtypes, rrows, options_map['dcontext'], file, expand=False) if args.output_stdout: # Write out the query to stdout. query_render.render_text(rtypes, rrows, options_map['dcontext'], sys.stdout, **fmtopts)","title":"save_query()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.split_expenses.split_expenses","text":"Split postings according to expenses (see module docstring for details). Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. config \u2013 The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. Source code in beancount/plugins/split_expenses.py def split_expenses(entries, options_map, config): \"\"\"Split postings according to expenses (see module docstring for details). Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. config: The plugin configuration string. Returns: A list of entries, with potentially more accounts and potentially more postings with smaller amounts. \"\"\" # Validate and sanitize configuration. if isinstance(config, str): members = config.split() elif isinstance(config, (tuple, list)): members = config else: raise RuntimeError(\"Invalid plugin configuration: configuration for split_expenses \" \"should be a string or a sequence.\") acctypes = options.get_account_types(options_map) def is_expense_account(account): return account_types.get_account_type(account) == acctypes.expenses # A predicate to quickly identify if an account contains the name of a # member. is_individual_account = re.compile('|'.join(map(re.escape, members))).search # Existing and previously unseen accounts. new_accounts = set() # Filter the entries and transform transactions. new_entries = [] for entry in entries: if isinstance(entry, data.Transaction): new_postings = [] for posting in entry.postings: if (is_expense_account(posting.account) and not is_individual_account(posting.account)): # Split this posting into multiple postings. split_units = amount.Amount(posting.units.number / len(members), posting.units.currency) for member in members: # Mark the account as new if never seen before. subaccount = account.join(posting.account, member) new_accounts.add(subaccount) # Ensure the modified postings are marked as # automatically calculated, so that the resulting # calculated amounts aren't used to affect inferred # tolerances. meta = posting.meta.copy() if posting.meta else {} meta[interpolate.AUTOMATIC_META] = True # Add a new posting for each member, to a new account # with the name of this member. new_postings.append( posting._replace(meta=meta, account=subaccount, units=split_units, cost=posting.cost)) else: new_postings.append(posting) # Modify the entry in-place, replace its postings. entry = entry._replace(postings=new_postings) new_entries.append(entry) # Create Open directives for new subaccounts if necessary. oc_map = getters.get_account_open_close(entries) open_date = entries[0].date meta = data.new_metadata('', 0) open_entries = [] for new_account in new_accounts: if new_account not in oc_map: entry = data.Open(meta, open_date, new_account, None, None) open_entries.append(entry) return open_entries + new_entries, []","title":"split_expenses()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending","text":"An example of tracking unpaid payables or receivables. A user with lots of invoices to track may want to produce a report of pending or incomplete payables or receivables. Beancount does not by default offer such a dedicated feature, but it is easy to build one by using existing link attributes on transactions. This is an example on how to implement that with a plugin. For example, assuming the user enters linked transactions like this: 2013-03-28 * \"Bill for datacenter electricity\" ^invoice-27a30ab61191 Expenses:Electricity 450.82 USD Liabilities:AccountsPayable 2013-04-15 * \"Paying electricity company\" ^invoice-27a30ab61191 Assets:Checking -450.82 USD Liabilities:AccountsPayable Transactions are grouped by link (\"invoice-27a30ab61191\") and then the intersection of their common accounts is automatically calculated (\"Liabilities:AccountsPayable\"). We then add up the balance of all the postings for this account in this link group and check if the sum is zero. If there is a residual amount in this balance, we mark the associated entries as incomplete by inserting a #PENDING tag on them. The user can then use that tag to navigate to the corresponding view in the web interface, or just find the entries and produce a listing of them.","title":"tag_pending"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending.tag_pending_plugin","text":"A plugin that finds and tags pending transactions. Parameters: entries \u2013 A list of entry instances. options_map \u2013 A dict of options parsed from the file. Returns: A tuple of entries and errors. Source code in beancount/plugins/tag_pending.py def tag_pending_plugin(entries, options_map): \"\"\"A plugin that finds and tags pending transactions. Args: entries: A list of entry instances. options_map: A dict of options parsed from the file. Returns: A tuple of entries and errors. \"\"\" return (tag_pending_transactions(entries, 'PENDING'), [])","title":"tag_pending_plugin()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.tag_pending.tag_pending_transactions","text":"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Parameters: entries \u2013 A list of directives/transactions to process. tag_name \u2013 A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. Source code in beancount/plugins/tag_pending.py def tag_pending_transactions(entries, tag_name='PENDING'): \"\"\"Filter out incomplete linked transactions to a transfer account. Given a list of entries, group the entries by their link and compute the balance of the intersection of their common accounts. If the balance does not sum to zero, insert a 'tag_name' tag in the entries. Args: entries: A list of directives/transactions to process. tag_name: A string, the name of the tag to be inserted if a linked group of entries is found not to match Returns: A modified set of entries, possibly tagged as pending. \"\"\" link_groups = basicops.group_entries_by_link(entries) pending_entry_ids = set() for link, link_entries in link_groups.items(): assert link_entries if len(link_entries) == 1: # If a single entry is present, it is assumed incomplete. pending_entry_ids.add(id(link_entries[0])) else: # Compute the sum total balance of the common accounts. common_accounts = basicops.get_common_accounts(link_entries) common_balance = inventory.Inventory() for entry in link_entries: for posting in entry.postings: if posting.account in common_accounts: common_balance.add_position(posting) # Mark entries as pending if a residual balance is found. if not common_balance.is_empty(): for entry in link_entries: pending_entry_ids.add(id(entry)) # Insert tags if marked. return [(entry._replace(tags=(entry.tags or set()) | set((tag_name,))) if id(entry) in pending_entry_ids else entry) for entry in entries]","title":"tag_pending_transactions()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices","text":"This module adds validation that there is a single price defined per date and base/quote currencies. If multiple conflicting price values are declared, an error is generated. Note that multiple price entries with the same number do not generate an error. This is meant to be turned on if you want to use a very strict mode for entering prices, and may not be realistic usage. For example, if you have (1) a transaction with an implicitly generated price during the day (from its cost) and (2) a separate explicit price directive that declares a different price for the day's closing price, this would generate an error. I'm not certain this will be useful in the long run, so placing it in a plugin.","title":"unique_prices"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError","text":"UniquePricesError(source, message, entry)","title":"UniquePricesError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unique_prices.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__new__","text":"Create new instance of UniquePricesError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.UniquePricesError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/unique_prices.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unique_prices.validate_unique_prices","text":"Check that there is only a single price per day for a particular base/quote. Parameters: entries \u2013 A list of directives. We're interested only in the Transaction instances. unused_options_map \u2013 A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. Source code in beancount/plugins/unique_prices.py def validate_unique_prices(entries, unused_options_map): \"\"\"Check that there is only a single price per day for a particular base/quote. Args: entries: A list of directives. We're interested only in the Transaction instances. unused_options_map: A parser options dict. Returns: The list of input entries, and a list of new UniquePricesError instances generated. \"\"\" new_entries = [] errors = [] prices = collections.defaultdict(list) for entry in entries: if not isinstance(entry, data.Price): continue key = (entry.date, entry.currency, entry.amount.currency) prices[key].append(entry) errors = [] for price_entries in prices.values(): if len(price_entries) > 1: number_map = {price_entry.amount.number: price_entry for price_entry in price_entries} if len(number_map) > 1: # Note: This should be a list of entries for better error # reporting. (Later.) error_entry = next(iter(number_map.values())) errors.append( UniquePricesError(error_entry.meta, \"Disagreeing price entries\", price_entries)) return new_entries, errors","title":"validate_unique_prices()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized","text":"Compute unrealized gains. The configuration for this plugin is a single string, the name of the subaccount to add to post the unrealized gains to, like this: plugin \"beancount.plugins.unrealized\" \"Unrealized\" If you don't specify a name for the subaccount (the configuration value is optional), by default it inserts the unrealized gains in the same account that is being adjusted.","title":"unrealized"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError","text":"UnrealizedError(source, message, entry)","title":"UnrealizedError"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/plugins/unrealized.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__new__","text":"Create new instance of UnrealizedError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.UnrealizedError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/plugins/unrealized.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.add_unrealized_gains","text":"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Parameters: entries \u2013 A list of data directives. options_map \u2013 A dict of options, that confirms to beancount.parser.options. subaccount \u2013 A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. Source code in beancount/plugins/unrealized.py def add_unrealized_gains(entries, options_map, subaccount=None): \"\"\"Insert entries for unrealized capital gains. This function inserts entries that represent unrealized gains, at the end of the available history. It returns a new list of entries, with the new gains inserted. It replaces the account type with an entry in an income account. Optionally, it can book the gain in a subaccount of the original and income accounts. Args: entries: A list of data directives. options_map: A dict of options, that confirms to beancount.parser.options. subaccount: A string, and optional the name of a subaccount to create under an account to book the unrealized gain. If this is left to its default value, the gain is booked directly in the same account. Returns: A list of entries, which includes the new unrealized capital gains entries at the end, and a list of errors. The new list of entries is still sorted. \"\"\" errors = [] meta = data.new_metadata('', 0) account_types = options.get_account_types(options_map) # Assert the subaccount name is in valid format. if subaccount: validation_account = account.join(account_types.assets, subaccount) if not account.is_valid(validation_account): errors.append( UnrealizedError(meta, \"Invalid subaccount name: '{}'\".format(subaccount), None)) return entries, errors if not entries: return (entries, errors) # Group positions by (account, cost, cost_currency). price_map = prices.build_price_map(entries) holdings_list = holdings.get_final_holdings(entries, price_map=price_map) # Group positions by (account, cost, cost_currency). holdings_list = holdings.aggregate_holdings_by( holdings_list, lambda h: (h.account, h.currency, h.cost_currency)) # Get the latest prices from the entries. price_map = prices.build_price_map(entries) # Create transactions to account for each position. new_entries = [] latest_date = entries[-1].date for index, holding in enumerate(holdings_list): if (holding.currency == holding.cost_currency or holding.cost_currency is None): continue # Note: since we're only considering positions held at cost, the # transaction that created the position *must* have created at least one # price point for that commodity, so we never expect for a price not to # be available, which is reasonable. if holding.price_number is None: # An entry without a price might indicate that this is a holding # resulting from leaked cost basis. {0ed05c502e63, b/16} if holding.number: errors.append( UnrealizedError(meta, \"A valid price for {h.currency}/{h.cost_currency} \" \"could not be found\".format(h=holding), None)) continue # Compute the PnL; if there is no profit or loss, we create a # corresponding entry anyway. pnl = holding.market_value - holding.book_value if holding.number == ZERO: # If the number of units sum to zero, the holdings should have been # zero. errors.append( UnrealizedError( meta, \"Number of units of {} in {} in holdings sum to zero \" \"for account {} and should not\".format( holding.currency, holding.cost_currency, holding.account), None)) continue # Compute the name of the accounts and add the requested subaccount name # if requested. asset_account = holding.account income_account = account.join(account_types.income, account.sans_root(holding.account)) if subaccount: asset_account = account.join(asset_account, subaccount) income_account = account.join(income_account, subaccount) # Create a new transaction to account for this difference in gain. gain_loss_str = \"gain\" if pnl > ZERO else \"loss\" narration = (\"Unrealized {} for {h.number} units of {h.currency} \" \"(price: {h.price_number:.4f} {h.cost_currency} as of {h.price_date}, \" \"average cost: {h.cost_number:.4f} {h.cost_currency})\").format( gain_loss_str, h=holding) entry = data.Transaction(data.new_metadata(meta[\"filename\"], lineno=1000 + index), latest_date, flags.FLAG_UNREALIZED, None, narration, EMPTY_SET, EMPTY_SET, []) # Book this as income, converting the account name to be the same, but as income. # Note: this is a rather convenient but arbitrary choice--maybe it would be best to # let the user decide to what account to book it, but I don't a nice way to let the # user specify this. # # Note: we never set a price because we don't want these to end up in Conversions. entry.postings.extend([ data.Posting( asset_account, amount.Amount(pnl, holding.cost_currency), None, None, None, None), data.Posting( income_account, amount.Amount(-pnl, holding.cost_currency), None, None, None, None) ]) new_entries.append(entry) # Ensure that the accounts we're going to use to book the postings exist, by # creating open entries for those that we generated that weren't already # existing accounts. new_accounts = {posting.account for entry in new_entries for posting in entry.postings} open_entries = getters.get_account_open_close(entries) new_open_entries = [] for account_ in sorted(new_accounts): if account_ not in open_entries: meta = data.new_metadata(meta[\"filename\"], index) open_entry = data.Open(meta, latest_date, account_, None, None) new_open_entries.append(open_entry) return (entries + new_open_entries + new_entries, errors)","title":"add_unrealized_gains()"},{"location":"api_reference/beancount.plugins.html#beancount.plugins.unrealized.get_unrealized_entries","text":"Return entries automatically created for unrealized gains. Parameters: entries \u2013 A list of directives. Returns: A list of directives, all of which are in the original list. Source code in beancount/plugins/unrealized.py def get_unrealized_entries(entries): \"\"\"Return entries automatically created for unrealized gains. Args: entries: A list of directives. Returns: A list of directives, all of which are in the original list. \"\"\" return [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.flag == flags.FLAG_UNREALIZED)]","title":"get_unrealized_entries()"},{"location":"api_reference/beancount.scripts.html","text":"beancount.scripts \uf0c1 Implementation of the various scripts available from bin. This is structured this way because we want all the significant codes under a single directory, for analysis, grepping and unit testing. beancount.scripts.bake \uf0c1 Bake a Beancount input file's web files to a directory hierarchy. You provide a Beancount filename, an output directory, and this script runs a server and a scraper that puts all the files in the directory, and if your output name has an archive suffix, we automatically the fetched directory contents to the archive and delete them. beancount.scripts.bake.archive(command_template, directory, archive, quiet=False) \uf0c1 Archive the directory to the given tar/gz archive filename. Parameters: command_template \u2013 A string, the command template to format with in order to compute the command to run. directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. quiet \u2013 A boolean, True to suppress output. Exceptions: IOError \u2013 if the directory does not exist or if the archive name already Source code in beancount/scripts/bake.py def archive(command_template, directory, archive, quiet=False): \"\"\"Archive the directory to the given tar/gz archive filename. Args: command_template: A string, the command template to format with in order to compute the command to run. directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. quiet: A boolean, True to suppress output. Raises: IOError: if the directory does not exist or if the archive name already exists. \"\"\" directory = path.abspath(directory) archive = path.abspath(archive) if not path.exists(directory): raise IOError(\"Directory to archive '{}' does not exist\".format( directory)) if path.exists(archive): raise IOError(\"Output archive name '{}' already exists\".format( archive)) command = command_template.format(directory=directory, dirname=path.dirname(directory), basename=path.basename(directory), archive=archive) pipe = subprocess.Popen(shlex.split(command), shell=False, cwd=path.dirname(directory), stdout=subprocess.PIPE if quiet else None, stderr=subprocess.PIPE if quiet else None) _, _ = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Archive failure\") beancount.scripts.bake.archive_zip(directory, archive) \uf0c1 Archive the directory to the given tar/gz archive filename. Parameters: directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. Source code in beancount/scripts/bake.py def archive_zip(directory, archive): \"\"\"Archive the directory to the given tar/gz archive filename. Args: directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. \"\"\" # Figure out optimal level of compression among the supported ones in this # installation. for spec, compression in [ ('lzma', zipfile.ZIP_LZMA), ('bz2', zipfile.ZIP_BZIP2), ('zlib', zipfile.ZIP_DEFLATED)]: if importlib.util.find_spec(spec): zip_compression = compression break else: # Default is no compression. zip_compression = zipfile.ZIP_STORED with file_utils.chdir(directory), zipfile.ZipFile( archive, 'w', compression=zip_compression) as archfile: for root, dirs, files in os.walk(directory): for filename in files: relpath = path.relpath(path.join(root, filename), directory) archfile.write(relpath) beancount.scripts.bake.bake_to_directory(webargs, output_dir, render_all_pages=True) \uf0c1 Serve and bake a Beancount's web to a directory. Parameters: webargs \u2013 An argparse parsed options object with the web app arguments. output_dir \u2013 A directory name. We don't check here whether it exists or not. quiet \u2013 A boolean, True to suppress web server fetch log. render_all_pages \u2013 If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. Source code in beancount/scripts/bake.py def bake_to_directory(webargs, output_dir, render_all_pages=True): \"\"\"Serve and bake a Beancount's web to a directory. Args: webargs: An argparse parsed options object with the web app arguments. output_dir: A directory name. We don't check here whether it exists or not. quiet: A boolean, True to suppress web server fetch log. render_all_pages: If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. \"\"\" callback = functools.partial(save_scraped_document, output_dir) if render_all_pages: ignore_regexps = None else: regexps = [ # Skip the context pages, too slow. r'/context/', # Skip the link pages, too slow. r'/link/', # Skip the component pages... too many. r'/view/component/', # Skip served documents. r'/.*/doc/', # Skip monthly pages. r'/view/year/\\d\\d\\d\\d/month/', ] ignore_regexps = '({})'.format('|'.join(regexps)) processed_urls, skipped_urls = web.scrape_webapp(webargs, callback, ignore_regexps) beancount.scripts.bake.normalize_filename(url) \uf0c1 Convert URL paths to filenames. Add .html extension if needed. Parameters: url \u2013 A string, the url to convert. Returns: A string, possibly with an extension appended. Source code in beancount/scripts/bake.py def normalize_filename(url): \"\"\"Convert URL paths to filenames. Add .html extension if needed. Args: url: A string, the url to convert. Returns: A string, possibly with an extension appended. \"\"\" if url.endswith('/'): return path.join(url, 'index.html') elif BINARY_MATCH(url): return url else: return url if url.endswith('.html') else (url + '.html') beancount.scripts.bake.relativize_links(html, current_url) \uf0c1 Make all the links in the contents string relative to an URL. Parameters: html \u2013 An lxml document node. current_url \u2013 A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. Source code in beancount/scripts/bake.py def relativize_links(html, current_url): \"\"\"Make all the links in the contents string relative to an URL. Args: html: An lxml document node. current_url: A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. \"\"\" current_dir = path.dirname(current_url) for element, attribute, link, pos in lxml.html.iterlinks(html): if path.isabs(link): relative_link = path.relpath(normalize_filename(link), current_dir) element.set(attribute, relative_link) beancount.scripts.bake.remove_links(html, targets) \uf0c1 Convert a list of anchors () from an HTML tree to spans (). Parameters: html \u2013 An lxml document node. targets \u2013 A set of string, targets to be removed. Source code in beancount/scripts/bake.py def remove_links(html, targets): \"\"\"Convert a list of anchors () from an HTML tree to spans (). Args: html: An lxml document node. targets: A set of string, targets to be removed. \"\"\" for element, attribute, link, pos in lxml.html.iterlinks(html): if link in targets: del element.attrib[attribute] element.tag = 'span' element.set('class', 'removed-link') beancount.scripts.bake.save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls) \uf0c1 Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Parameters: output_dir \u2013 A string, the output directory to write. url \u2013 A string, the originally requested URL. response \u2013 An http response as per urlopen. contents \u2013 Bytes, the content of a response. html_root \u2013 An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls \u2013 A set of the links from the file that were skipped. Source code in beancount/scripts/bake.py def save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls): \"\"\"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Args: output_dir: A string, the output directory to write. url: A string, the originally requested URL. response: An http response as per urlopen. contents: Bytes, the content of a response. html_root: An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls: A set of the links from the file that were skipped. \"\"\" if response.status != 200: logging.error(\"Invalid status: %s\", response.status) # Ignore directories. if url.endswith('/'): return # Note that we're saving the file under the non-redirected URL, because this # will have to be opened using files and there are no redirects that way. if response.info().get_content_type() == 'text/html': if html_root is None: html_root = lxml.html.document_fromstring(contents) remove_links(html_root, skipped_urls) relativize_links(html_root, url) contents = lxml.html.tostring(html_root, method=\"html\") # Compute output filename and write out the relativized contents. output_filename = path.join(output_dir, normalize_filename(url).lstrip('/')) os.makedirs(path.dirname(output_filename), exist_ok=True) with open(output_filename, 'wb') as outfile: outfile.write(contents) beancount.scripts.check \uf0c1 Parse, check and realize a beancount input file. This also measures the time it takes to run all these steps. beancount.scripts.deps \uf0c1 Check the installation dependencies and report the version numbers of each. This is meant to be used as an error diagnostic tool. beancount.scripts.deps.check_cdecimal() \uf0c1 Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_cdecimal(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" # Note: this code mirrors and should be kept in-sync with that at the top of # beancount.core.number. # Try the built-in installation. import decimal if is_fast_decimal(decimal): return ('cdecimal', '{} (built-in)'.format(decimal.__version__), True) # Try an explicitly installed version. try: import cdecimal if is_fast_decimal(cdecimal): return ('cdecimal', getattr(cdecimal, '__version__', 'OKAY'), True) except ImportError: pass # Not found. return ('cdecimal', None, False) beancount.scripts.deps.check_dependencies() \uf0c1 Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. Source code in beancount/scripts/deps.py def check_dependencies(): \"\"\"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. \"\"\" return [ # Check for a complete installation of Python itself. check_python(), check_cdecimal(), # Modules we really do need installed. check_import('dateutil'), check_import('bottle'), check_import('ply', module_name='ply.yacc', min_version='3.4'), check_import('lxml', module_name='lxml.etree', min_version='3'), # Optionally required to upload data to Google Drive. check_import('googleapiclient'), check_import('oauth2client'), check_import('httplib2'), # Optionally required to support various price source fetchers. check_import('requests', min_version='2.0'), # Optionally required to support imports (identify, extract, file) code. check_python_magic(), check_import('beautifulsoup4', module_name='bs4', min_version='4'), ] beancount.scripts.deps.check_import(package_name, min_version=None, module_name=None) \uf0c1 Check that a particular module name is installed. Parameters: package_name \u2013 A string, the name of the package and module to be imported to verify this works. This should have a version attribute on it. min_version \u2013 If not None, a string, the minimum version number we require. module_name \u2013 The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_import(package_name, min_version=None, module_name=None): \"\"\"Check that a particular module name is installed. Args: package_name: A string, the name of the package and module to be imported to verify this works. This should have a __version__ attribute on it. min_version: If not None, a string, the minimum version number we require. module_name: The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" if module_name is None: module_name = package_name try: __import__(module_name) module = sys.modules[module_name] if min_version is not None: version = module.__version__ assert isinstance(version, str) is_sufficient = (parse_version(version) >= parse_version(min_version) if min_version else True) else: version, is_sufficient = None, True except ImportError: version, is_sufficient = None, False return (package_name, version, is_sufficient) beancount.scripts.deps.check_python() \uf0c1 Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" return ('python3', '.'.join(map(str, sys.version_info[:3])), sys.version_info[:2] >= (3, 3)) beancount.scripts.deps.check_python_magic() \uf0c1 Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python_magic(): \"\"\"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" try: import magic # Check that python-magic and not filemagic is installed. if not hasattr(magic, 'from_file'): # 'filemagic' is installed; install python-magic. raise ImportError return ('python-magic', 'OK', True) except ImportError: return ('python-magic', None, False) beancount.scripts.deps.is_fast_decimal(decimal_module) \uf0c1 Return true if a fast C decimal implementation is installed. Source code in beancount/scripts/deps.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType) beancount.scripts.deps.list_dependencies(file=<_io.TextIOWrapper name='' mode='w' encoding='utf-8'>) \uf0c1 Check the dependencies and produce a listing on the given file. Parameters: file \u2013 A file object to write the output to. Source code in beancount/scripts/deps.py def list_dependencies(file=sys.stderr): \"\"\"Check the dependencies and produce a listing on the given file. Args: file: A file object to write the output to. \"\"\" print(\"Dependencies:\") for package, version, sufficient in check_dependencies(): print(\" {:16}: {} {}\".format( package, version or 'NOT INSTALLED', \"(INSUFFICIENT)\" if version and not sufficient else \"\"), file=file) beancount.scripts.deps.parse_version(version_str) \uf0c1 Parse the version string into a comparable tuple. Source code in beancount/scripts/deps.py def parse_version(version_str: str) -> str: \"\"\"Parse the version string into a comparable tuple.\"\"\" return [int(v) for v in version_str.split('.')] beancount.scripts.directories \uf0c1 Check that document directories mirror a list of accounts correctly. beancount.scripts.directories.ValidateDirectoryError ( Exception ) \uf0c1 A directory validation error. beancount.scripts.directories.validate_directories(entries, document_dirs) \uf0c1 Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: entries \u2013 A list of directives. document_dirs \u2013 A list of string, the directory roots to walk and validate. Source code in beancount/scripts/directories.py def validate_directories(entries, document_dirs): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: entries: A list of directives. document_dirs: A list of string, the directory roots to walk and validate. \"\"\" # Get the list of accounts declared in the ledge. accounts = getters.get_accounts(entries) # For each of the roots, validate the hierarchy of directories. for document_dir in document_dirs: errors = validate_directory(accounts, document_dir) for error in errors: print(\"ERROR: {}\".format(error)) beancount.scripts.directories.validate_directory(accounts, document_dir) \uf0c1 Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Parameters: account \u2013 A set or dict of account names. document_dir \u2013 A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. Source code in beancount/scripts/directories.py def validate_directory(accounts, document_dir): \"\"\"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Args: account: A set or dict of account names. document_dir: A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. \"\"\" # Generate all parent accounts in the account_set we're checking against, so # that parent directories with no corresponding account don't warn. accounts_with_parents = set(accounts) for account_ in accounts: while True: parent = account.parent(account_) if not parent: break if parent in accounts_with_parents: break accounts_with_parents.add(parent) account_ = parent errors = [] for directory, account_name, _, _ in account.walk(document_dir): if account_name not in accounts_with_parents: errors.append(ValidateDirectoryError( \"Invalid directory '{}': no corresponding account '{}'\".format( directory, account_name))) return errors beancount.scripts.doctor \uf0c1 Debugging tool for those finding bugs in Beancount. This tool is able to dump lexer/parser state, and will provide other services in the name of debugging. beancount.scripts.doctor.RenderError ( tuple ) \uf0c1 RenderError(source, message, entry) beancount.scripts.doctor.RenderError.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/doctor.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.doctor.RenderError.__new__(_cls, source, message, entry) special staticmethod \uf0c1 Create new instance of RenderError(source, message, entry) beancount.scripts.doctor.RenderError.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/doctor.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.doctor.do_checkdeps(*unused_args) \uf0c1 Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.') beancount.scripts.doctor.do_context(filename, args) \uf0c1 Describe the context that a particular transaction is applied to. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). Source code in beancount/scripts/doctor.py def do_context(filename, args): \"\"\"Describe the context that a particular transaction is applied to. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). \"\"\" from beancount.reports import context from beancount import loader # Check we have the required number of arguments. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") # Load the input files. entries, errors, options_map = loader.load_file(filename) # Parse the arguments, get the line number. match = re.match(r\"(.+):(\\d+)$\", args[0]) if match: search_filename = path.abspath(match.group(1)) lineno = int(match.group(2)) elif re.match(r\"(\\d+)$\", args[0]): # Note: Make sure to use the absolute filename used by the parser to # resolve the file. search_filename = options_map['filename'] lineno = int(args[0]) else: raise SystemExit(\"Invalid format for location.\") str_context = context.render_file_context(entries, options_map, search_filename, lineno) sys.stdout.write(str_context) beancount.scripts.doctor.do_deps(*unused_args) \uf0c1 Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.') beancount.scripts.doctor.do_directories(filename, args) \uf0c1 Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: filename \u2013 A string, the Beancount input filename. args \u2013 The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. Source code in beancount/scripts/doctor.py def do_directories(filename, args): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: filename: A string, the Beancount input filename. args: The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. \"\"\" from beancount import loader from beancount.scripts import directories entries, _, __ = loader.load_file(filename) directories.validate_directories(entries, args) beancount.scripts.doctor.do_display_context(filename, args) \uf0c1 Print out the precision inferred from the parsed numbers in the input file. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_display_context(filename, args): \"\"\"Print out the precision inferred from the parsed numbers in the input file. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount import loader entries, errors, options_map = loader.load_file(filename) dcontext = options_map['dcontext'] sys.stdout.write(str(dcontext)) beancount.scripts.doctor.do_dump_lexer(filename, unused_args) \uf0c1 Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text))) beancount.scripts.doctor.do_lex(filename, unused_args) \uf0c1 Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text))) beancount.scripts.doctor.do_linked(filename, args) \uf0c1 Print out a list of transactions linked to the one at the given line. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_linked(filename, args): \"\"\"Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. if closest_entry is None: raise SystemExit(\"No entry could be found before {}:{}\".format(filename, lineno)) links = (closest_entry.links if isinstance(closest_entry, data.Transaction) else data.EMPTY_SET) if not links: linked_entries = [closest_entry] else: # Find all linked entries. # # Note that there is an option here: You can either just look at the links # on the closest entry, or you can include the links of the linked # transactions as well. Whichever one you want depends on how you use your # links. Best would be to query the user (in Emacs) when there are many # links present. follow_links = True if not follow_links: linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] else: links = set(links) linked_entries = [] while True: num_linked = len(linked_entries) linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] if len(linked_entries) == num_linked: break for entry in linked_entries: if entry.links: links.update(entry.links) # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) dformat = options_map['dcontext'].build(alignment=display_context.Align.DOT, reserved=2) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income)) beancount.scripts.doctor.do_list_options(*unused_args) \uf0c1 Print out a list of the available options. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_list_options(*unused_args): \"\"\"Print out a list of the available options. Args: unused_args: Ignored. \"\"\" from beancount.parser import options print(options.list_options()) beancount.scripts.doctor.do_missing_open(filename, args) \uf0c1 Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_missing_open(filename, args): \"\"\"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import printer from beancount.core import data from beancount.core import getters from beancount import loader entries, errors, options_map = loader.load_file(filename) # Get accounts usage and open directives. first_use_map, _ = getters.get_accounts_use_map(entries) open_close_map = getters.get_account_open_close(entries) new_entries = [] for account, first_use_date in first_use_map.items(): if account not in open_close_map: new_entries.append( data.Open(data.new_metadata(filename, 0), first_use_date, account, None, None)) dcontext = options_map['dcontext'] printer.print_entries(data.sorted(new_entries), dcontext) beancount.scripts.doctor.do_parse(filename, unused_args) \uf0c1 Run the parser in debug mode. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_parse(filename, unused_args): \"\"\"Run the parser in debug mode. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import parser entries, errors, _ = parser.parse_file(filename, yydebug=1) beancount.scripts.doctor.do_print_options(filename, *args) \uf0c1 Print out the actual options parsed from a file. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_print_options(filename, *args): \"\"\"Print out the actual options parsed from a file. Args: unused_args: Ignored. \"\"\" from beancount import loader _, __, options_map = loader.load_file(filename) for key, value in sorted(options_map.items()): print('{}: {}'.format(key, value)) beancount.scripts.doctor.do_roundtrip(filename, unused_args) \uf0c1 Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_roundtrip(filename, unused_args): \"\"\"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import printer from beancount.core import compare from beancount import loader round1_filename = round2_filename = None try: logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') logging.info(\"Read the entries\") entries, errors, options_map = loader.load_file(filename) printer.print_errors(errors, file=sys.stderr) logging.info(\"Print them out to a file\") basename, extension = path.splitext(filename) round1_filename = ''.join([basename, '.roundtrip1', extension]) with open(round1_filename, 'w') as outfile: printer.print_entries(entries, file=outfile) logging.info(\"Read the entries from that file\") # Note that we don't want to run any of the auto-generation here, but # parsing now returns incomplete objects and we assume idempotence on a # file that was output from the printer after having been processed, so # it shouldn't add anything new. That is, a processed file printed and # resolve when parsed again should contain the same entries, i.e. # nothing new should be generated. entries_roundtrip, errors, options_map = loader.load_file(round1_filename) # Print out the list of errors from parsing the results. if errors: print(',----------------------------------------------------------------------') printer.print_errors(errors, file=sys.stdout) print('`----------------------------------------------------------------------') logging.info(\"Print what you read to yet another file\") round2_filename = ''.join([basename, '.roundtrip2', extension]) with open(round2_filename, 'w') as outfile: printer.print_entries(entries_roundtrip, file=outfile) logging.info(\"Compare the original entries with the re-read ones\") same, missing1, missing2 = compare.compare_entries(entries, entries_roundtrip) if same: logging.info('Entries are the same. Congratulations.') else: logging.error('Entries differ!') print() print('\\n\\nMissing from original:') for entry in entries: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() print('\\n\\nMissing from round-trip:') for entry in missing2: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() finally: for rfilename in (round1_filename, round2_filename): if path.exists(rfilename): os.remove(rfilename) beancount.scripts.doctor.do_validate_html(directory, args) \uf0c1 Validate all the HTML files under a directory hierarchy. Parameters: directory \u2013 A string, the root directory whose contents to validate. args \u2013 A tuple of the rest of arguments. Source code in beancount/scripts/doctor.py def do_validate_html(directory, args): \"\"\"Validate all the HTML files under a directory hierarchy. Args: directory: A string, the root directory whose contents to validate. args: A tuple of the rest of arguments. \"\"\" from beancount.web import scrape files, missing, empty = scrape.validate_local_links_in_dir(directory) logging.info('%d files processed', len(files)) for target in missing: logging.error('Missing %s', target) for target in empty: logging.error('Empty %s', target) beancount.scripts.doctor.get_commands() \uf0c1 Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). Source code in beancount/scripts/doctor.py def get_commands(): \"\"\"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). \"\"\" commands = [] for attr_name, attr_value in globals().items(): match = re.match('do_(.*)', attr_name) if match: commands.append((match.group(1), misc_utils.first_paragraph(attr_value.__doc__))) return commands beancount.scripts.example \uf0c1 Generate a decently-sized example history, based on some rules. This script is used to generate some meaningful input to Beancount, input that looks as realistic as possible for a moderately complex mock individual. This can also be used as an input generator for a stress test for performance evaluation. beancount.scripts.example.check_non_negative(entries, account, currency) \uf0c1 Check that the balance of the given account never goes negative. Parameters: entries \u2013 A list of directives. account \u2013 An account string, the account to check the balance for. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 if the balance goes negative. Source code in beancount/scripts/example.py def check_non_negative(entries, account, currency): \"\"\"Check that the balance of the given account never goes negative. Args: entries: A list of directives. account: An account string, the account to check the balance for. currency: A string, the currency to check minimums for. Raises: AssertionError: if the balance goes negative. \"\"\" previous_date = None for txn_posting, balances in postings_for(data.sorted(entries), [account], before=True): balance = balances[account] date = txn_posting.txn.date if date != previous_date: assert all(pos.units.number >= ZERO for pos in balance.get_positions()), ( \"Negative balance: {} at: {}\".format(balance, txn_posting.txn.date)) previous_date = date beancount.scripts.example.compute_trip_dates(date_begin, date_end) \uf0c1 Generate dates at reasonable intervals for trips during the given time period. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of dates for the trips within the period. Source code in beancount/scripts/example.py def compute_trip_dates(date_begin, date_end): \"\"\"Generate dates at reasonable intervals for trips during the given time period. Args: date_begin: The start date. date_end: The end date. Yields: Pairs of dates for the trips within the period. \"\"\" # Min and max number of days remaining at home. days_at_home = (4*30, 13*30) # Length of trip. days_trip = (8, 22) # Number of days to ensure no trip at the beginning and the end. days_buffer = 21 date_begin += datetime.timedelta(days=days_buffer) date_end -= datetime.timedelta(days=days_buffer) date = date_begin while 1: duration_at_home = datetime.timedelta(days=random.randint(*days_at_home)) duration_trip = datetime.timedelta(days=random.randint(*days_trip)) date_trip_begin = date + duration_at_home date_trip_end = date_trip_begin + duration_trip if date_trip_end >= date_end: break yield (date_trip_begin, date_trip_end) date = date_trip_end beancount.scripts.example.contextualize_file(contents, employer) \uf0c1 Replace generic strings in the generated file with realistic strings. Parameters: contents \u2013 A string, the generic file contents. Returns: A string, the contextualized version. Source code in beancount/scripts/example.py def contextualize_file(contents, employer): \"\"\"Replace generic strings in the generated file with realistic strings. Args: contents: A string, the generic file contents. Returns: A string, the contextualized version. \"\"\" replacements = { 'CC': 'US', 'Bank1': 'BofA', 'Bank1_Institution': 'Bank of America', 'Bank1_Address': '123 America Street, LargeTown, USA', 'Bank1_Phone': '+1.012.345.6789', 'CreditCard1': 'Chase:Slate', 'CreditCard2': 'Amex:BlueCash', 'Employer1': employer, 'Retirement': 'Vanguard', 'Retirement_Institution': 'Vanguard Group', 'Retirement_Address': \"P.O. Box 1110, Valley Forge, PA 19482-1110\", 'Retirement_Phone': \"+1.800.523.1188\", 'Investment': 'ETrade', # Commodities 'CCY': 'USD', 'VACHR': 'VACHR', 'DEFCCY': 'IRAUSD', 'MFUND1': 'VBMPX', 'MFUND2': 'RGAGX', 'STK1': 'ITOT', 'STK2': 'VEA', 'STK3': 'VHT', 'STK4': 'GLD', } new_contents = replace(contents, replacements) return new_contents, replacements beancount.scripts.example.date_iter(date_begin, date_end) \uf0c1 Generate a sequence of dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_iter(date_begin, date_end): \"\"\"Generate a sequence of dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date. \"\"\" assert date_begin <= date_end date = date_begin one_day = datetime.timedelta(days=1) while date < date_end: date += one_day yield date beancount.scripts.example.date_random_seq(date_begin, date_end, days_min, days_max) \uf0c1 Generate a sequence of dates with some random increase in days. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. days_min \u2013 The minimum number of days to advance on each iteration. days_max \u2013 The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_random_seq(date_begin, date_end, days_min, days_max): \"\"\"Generate a sequence of dates with some random increase in days. Args: date_begin: The start date. date_end: The end date. days_min: The minimum number of days to advance on each iteration. days_max: The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. \"\"\" assert days_min > 0 assert days_min <= days_max date = date_begin while date < date_end: nb_days_forward = random.randint(days_min, days_max) date += datetime.timedelta(days=nb_days_forward) if date >= date_end: break yield date beancount.scripts.example.delay_dates(date_iter, delay_days_min, delay_days_max) \uf0c1 Delay the dates from the given iterator by some uniformly drawn number of days. Parameters: date_iter \u2013 An iterator of datetime.date instances. delay_days_min \u2013 The minimum amount of advance days for the transaction. delay_days_max \u2013 The maximum amount of advance days for the transaction. Yields: datetime.date instances. Source code in beancount/scripts/example.py def delay_dates(date_iter, delay_days_min, delay_days_max): \"\"\"Delay the dates from the given iterator by some uniformly drawn number of days. Args: date_iter: An iterator of datetime.date instances. delay_days_min: The minimum amount of advance days for the transaction. delay_days_max: The maximum amount of advance days for the transaction. Yields: datetime.date instances. \"\"\" dates = list(date_iter) last_date = dates[-1] last_date = last_date.date() if isinstance(last_date, datetime.datetime) else last_date for dtime in dates: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime date += datetime.timedelta(days=random.randint(delay_days_min, delay_days_max)) if date >= last_date: break yield date beancount.scripts.example.generate_balance_checks(entries, account, date_iter) \uf0c1 Generate balance check entries to the given frequency. Parameters: entries \u2013 A list of directives that contain all the transactions for the accounts. account \u2013 The name of the account for which to generate. date_iter \u2013 Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. Source code in beancount/scripts/example.py def generate_balance_checks(entries, account, date_iter): \"\"\"Generate balance check entries to the given frequency. Args: entries: A list of directives that contain all the transactions for the accounts. account: The name of the account for which to generate. date_iter: Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. \"\"\" balance_checks = [] date_iter = iter(date_iter) next_date = next(date_iter) with misc_utils.swallow(StopIteration): for txn_posting, balance in postings_for(entries, [account], before=True): while txn_posting.txn.date >= next_date: amount = balance[account].get_currency_units('CCY').number balance_checks.extend(parse(\"\"\" {next_date} balance {account} {amount} CCY \"\"\", **locals())) next_date = next(date_iter) return balance_checks beancount.scripts.example.generate_banking(entries, date_begin, date_end, amount_initial) \uf0c1 Generate a checking account opening. Parameters: entries \u2013 A list of entries which affect this account. date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. amount_initial \u2013 A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking(entries, date_begin, date_end, amount_initial): \"\"\"Generate a checking account opening. Args: entries: A list of entries which affect this account. date_begin: A date instance, the beginning date. date_end: A date instance, the end date. amount_initial: A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. \"\"\" amount_initial_neg = -amount_initial new_entries = parse(\"\"\" {date_begin} open Assets:CC:Bank1 institution: \"Bank1_Institution\" address: \"Bank1_Address\" phone: \"Bank1_Phone\" {date_begin} open Assets:CC:Bank1:Checking CCY account: \"00234-48574897\" ;; {date_begin} open Assets:CC:Bank1:Savings CCY {date_begin} * \"Opening Balance for checking account\" Assets:CC:Bank1:Checking {amount_initial} CCY Equity:Opening-Balances {amount_initial_neg} CCY \"\"\", **locals()) date_balance = date_begin + datetime.timedelta(days=1) account = 'Assets:CC:Bank1:Checking' for txn_posting, balances in postings_for(data.sorted(entries + new_entries), [account], before=True): if txn_posting.txn.date >= date_balance: break amount_balance = balances[account].get_currency_units('CCY').number bal_entries = parse(\"\"\" {date_balance} balance Assets:CC:Bank1:Checking {amount_balance} CCY \"\"\", **locals()) return new_entries + bal_entries beancount.scripts.example.generate_banking_expenses(date_begin, date_end, account, rent_amount) \uf0c1 Generate expenses paid out of a checking account, typically living expenses. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. account \u2013 The checking account to generate expenses to. rent_amount \u2013 The amount of rent. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking_expenses(date_begin, date_end, account, rent_amount): \"\"\"Generate expenses paid out of a checking account, typically living expenses. Args: date_begin: The start date. date_end: The end date. account: The checking account to generate expenses to. rent_amount: The amount of rent. Returns: A list of directives. \"\"\" fee_expenses = generate_periodic_expenses( rrule.rrule(rrule.MONTHLY, bymonthday=4, dtstart=date_begin, until=date_end), \"BANK FEES\", \"Monthly bank fee\", account, 'Expenses:Financial:Fees', lambda: D('4.00')) rent_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 2, 5), \"RiverBank Properties\", \"Paying the rent\", account, 'Expenses:Home:Rent', lambda: random.normalvariate(float(rent_amount), 0)) electricity_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 7, 8), \"EDISON POWER\", \"\", account, 'Expenses:Home:Electricity', lambda: random.normalvariate(65, 0)) internet_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 20, 22), \"Wine-Tarner Cable\", \"\", account, 'Expenses:Home:Internet', lambda: random.normalvariate(80, 0.10)) phone_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 17, 19), \"Verizon Wireless\", \"\", account, 'Expenses:Home:Phone', lambda: random.normalvariate(60, 10)) return data.sorted(fee_expenses + rent_expenses + electricity_expenses + internet_expenses + phone_expenses) beancount.scripts.example.generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from) \uf0c1 Generate entries to clear the value of an account. Parameters: date_iter \u2013 An iterator of datetime.date instances. payee \u2013 A string, the payee name to use on the transactions. narration \u2013 A string, the narration to use on the transactions. entries \u2013 A list of entries. account_clear \u2013 The account to clear. account_from \u2013 The source account to clear 'account_clear' from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from): \"\"\"Generate entries to clear the value of an account. Args: date_iter: An iterator of datetime.date instances. payee: A string, the payee name to use on the transactions. narration: A string, the narration to use on the transactions. entries: A list of entries. account_clear: The account to clear. account_from: The source account to clear 'account_clear' from. Returns: A list of directives. \"\"\" # The next date we're looking for. next_date = next(iter(date_iter)) # Iterate over all the postings of the account to clear. new_entries = [] for txn_posting, balances in postings_for(entries, [account_clear]): balance_clear = balances[account_clear] # Check if we need to clear. if next_date <= txn_posting.txn.date: pos_amount = balance_clear.get_currency_units('CCY') neg_amount = -pos_amount new_entries.extend(parse(\"\"\" {next_date} * \"{payee}\" \"{narration}\" {account_clear} {neg_amount.number:.2f} CCY {account_from} {pos_amount.number:.2f} CCY \"\"\", **locals())) balance_clear.add_amount(neg_amount) # Advance to the next date we're looking for. try: next_date = next(iter(date_iter)) except StopIteration: break return new_entries beancount.scripts.example.generate_commodity_entries(date_birth) \uf0c1 Create a list of Commodity entries for all the currencies we're using. Parameters: date_birth \u2013 A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. Source code in beancount/scripts/example.py def generate_commodity_entries(date_birth): \"\"\"Create a list of Commodity entries for all the currencies we're using. Args: date_birth: A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. \"\"\" return parse(\"\"\" 1792-01-01 commodity USD name: \"US Dollar\" export: \"CASH\" {date_birth} commodity VACHR name: \"Employer Vacation Hours\" export: \"IGNORE\" {date_birth} commodity IRAUSD name: \"US 401k and IRA Contributions\" export: \"IGNORE\" 2009-05-01 commodity RGAGX name: \"American Funds The Growth Fund of America Class R-6\" export: \"MUTF:RGAGX\" price: \"USD:google/MUTF:RGAGX\" 1995-09-18 commodity VBMPX name: \"Vanguard Total Bond Market Index Fund Institutional Plus Shares\" export: \"MUTF:VBMPX\" price: \"USD:google/MUTF:VBMPX\" 2004-01-20 commodity ITOT name: \"iShares Core S&P Total U.S. Stock Market ETF\" export: \"NYSEARCA:ITOT\" price: \"USD:google/NYSEARCA:ITOT\" 2007-07-20 commodity VEA name: \"Vanguard FTSE Developed Markets ETF\" export: \"NYSEARCA:VEA\" price: \"USD:google/NYSEARCA:VEA\" 2004-01-26 commodity VHT name: \"Vanguard Health Care ETF\" export: \"NYSEARCA:VHT\" price: \"USD:google/NYSEARCA:VHT\" 2004-11-01 commodity GLD name: \"SPDR Gold Trust (ETF)\" export: \"NYSEARCA:GLD\" price: \"USD:google/NYSEARCA:GLD\" 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" \"\"\", **locals()) beancount.scripts.example.generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end) \uf0c1 Generate bi-weekly entries for payroll salary income. Parameters: employer_name \u2013 A string, the human-readable name of the employer. employer_address \u2013 A string, the address of the employer. annual_salary \u2013 A Decimal, the annual salary of the employee. account_deposit \u2013 An account string, the account to deposit the salary to. account_retirement \u2013 An account string, the account to deposit retirement contributions to. date_begin \u2013 The start date. date_end \u2013 The end date. Returns: A list of directives, including open directives for the account. Source code in beancount/scripts/example.py def generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end): \"\"\"Generate bi-weekly entries for payroll salary income. Args: employer_name: A string, the human-readable name of the employer. employer_address: A string, the address of the employer. annual_salary: A Decimal, the annual salary of the employee. account_deposit: An account string, the account to deposit the salary to. account_retirement: An account string, the account to deposit retirement contributions to. date_begin: The start date. date_end: The end date. Returns: A list of directives, including open directives for the account. \"\"\" preamble = parse(\"\"\" {date_begin} event \"employer\" \"{employer_name}, {employer_address}\" {date_begin} open Income:CC:Employer1:Salary CCY ;{date_begin} open Income:CC:Employer1:AnnualBonus CCY {date_begin} open Income:CC:Employer1:GroupTermLife CCY {date_begin} open Income:CC:Employer1:Vacation VACHR {date_begin} open Assets:CC:Employer1:Vacation VACHR {date_begin} open Expenses:Vacation VACHR {date_begin} open Expenses:Health:Life:GroupTermLife {date_begin} open Expenses:Health:Medical:Insurance {date_begin} open Expenses:Health:Dental:Insurance {date_begin} open Expenses:Health:Vision:Insurance ;{date_begin} open Expenses:Vacation:Employer \"\"\", **locals()) date_prev = None contrib_retirement = ZERO contrib_socsec = ZERO biweekly_pay = annual_salary / 26 gross = biweekly_pay medicare = gross * D('0.0231') federal = gross * D('0.2303') state = gross * D('0.0791') city = gross * D('0.0379') sdi = D('1.12') lifeinsurance = D('24.32') dental = D('2.90') medical = D('27.38') vision = D('42.30') fixed = (medicare + federal + state + city + sdi + dental + medical + vision) # Calculate vacation hours per-pay. with decimal.localcontext() as ctx: ctx.prec = 4 vacation_hrs = (ANNUAL_VACATION_DAYS * D('8')) / D('26') transactions = [] for dtime in misc_utils.skipiter( rrule.rrule(rrule.WEEKLY, byweekday=rrule.TH, dtstart=date_begin, until=date_end), 2): date = dtime.date() year = date.year if not date_prev or date_prev.year != date.year: contrib_retirement = RETIREMENT_LIMITS.get(date.year, RETIREMENT_LIMITS[None]) contrib_socsec = D('7000') date_prev = date retirement_uncapped = math.ceil((gross * D('0.25')) / 100) * 100 retirement = min(contrib_retirement, retirement_uncapped) contrib_retirement -= retirement socsec_uncapped = gross * D('0.0610') socsec = min(contrib_socsec, socsec_uncapped) contrib_socsec -= socsec with decimal.localcontext() as ctx: ctx.prec = 6 deposit = (gross - retirement - fixed - socsec) retirement_neg = -retirement gross_neg = -gross lifeinsurance_neg = -lifeinsurance vacation_hrs_neg = -vacation_hrs template = \"\"\" {date} * \"{employer_name}\" \"Payroll\" {account_deposit} {deposit:.2f} CCY {account_retirement} {retirement:.2f} CCY Assets:CC:Federal:PreTax401k {retirement_neg:.2f} DEFCCY Expenses:Taxes:Y{year}:CC:Federal:PreTax401k {retirement:.2f} DEFCCY Income:CC:Employer1:Salary {gross_neg:.2f} CCY Income:CC:Employer1:GroupTermLife {lifeinsurance_neg:.2f} CCY Expenses:Health:Life:GroupTermLife {lifeinsurance:.2f} CCY Expenses:Health:Dental:Insurance {dental} CCY Expenses:Health:Medical:Insurance {medical} CCY Expenses:Health:Vision:Insurance {vision} CCY Expenses:Taxes:Y{year}:CC:Medicare {medicare:.2f} CCY Expenses:Taxes:Y{year}:CC:Federal {federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {state:.2f} CCY Expenses:Taxes:Y{year}:CC:CityNYC {city:.2f} CCY Expenses:Taxes:Y{year}:CC:SDI {sdi:.2f} CCY Expenses:Taxes:Y{year}:CC:SocSec {socsec:.2f} CCY Assets:CC:Employer1:Vacation {vacation_hrs:.2f} VACHR Income:CC:Employer1:Vacation {vacation_hrs_neg:.2f} VACHR \"\"\" if retirement == ZERO: # Remove retirement lines. template = '\\n'.join(line for line in template.splitlines() if not re.search(r'\\bretirement\\b', line)) transactions.extend(parse(template, **locals())) return preamble + transactions beancount.scripts.example.generate_expense_accounts(date_birth) \uf0c1 Generate directives for expense accounts. Parameters: date_birth \u2013 Birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_expense_accounts(date_birth): \"\"\"Generate directives for expense accounts. Args: date_birth: Birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" {date_birth} open Expenses:Food:Groceries {date_birth} open Expenses:Food:Restaurant {date_birth} open Expenses:Food:Coffee {date_birth} open Expenses:Food:Alcohol {date_birth} open Expenses:Transport:Tram {date_birth} open Expenses:Home:Rent {date_birth} open Expenses:Home:Electricity {date_birth} open Expenses:Home:Internet {date_birth} open Expenses:Home:Phone {date_birth} open Expenses:Financial:Fees {date_birth} open Expenses:Financial:Commissions \"\"\", **locals()) beancount.scripts.example.generate_open_entries(date, accounts, currency=None) \uf0c1 Generate a list of Open entries for the given accounts: Parameters: date \u2013 A datetime.date instance for the open entries. accounts \u2013 A list of account strings. currency \u2013 An optional currency constraint. Returns: A list of Open directives. Source code in beancount/scripts/example.py def generate_open_entries(date, accounts, currency=None): \"\"\"Generate a list of Open entries for the given accounts: Args: date: A datetime.date instance for the open entries. accounts: A list of account strings. currency: An optional currency constraint. Returns: A list of Open directives. \"\"\" assert isinstance(accounts, (list, tuple)) return parse(''.join( '{date} open {account} {currency}\\n'.format(date=date, account=account, currency=currency or '') for account in accounts)) beancount.scripts.example.generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment) \uf0c1 Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Parameters: entries \u2013 A list of existing entries that affect this account so far. The generated entries will also affect this account. account \u2013 An account string, the account to monitor. account_out \u2013 An account string, the savings account to make transfers to. transfer_minimum \u2013 The minimum amount of funds to always leave in this account after a transfer. transfer_threshold \u2013 The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment \u2013 A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. Source code in beancount/scripts/example.py def generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment): \"\"\"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Args: entries: A list of existing entries that affect this account so far. The generated entries will also affect this account. account: An account string, the account to monitor. account_out: An account string, the savings account to make transfers to. transfer_minimum: The minimum amount of funds to always leave in this account after a transfer. transfer_threshold: The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment: A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. \"\"\" last_date = entries[-1].date # Reverse the balance amounts taking into account the minimum balance for # all time in the future. amounts = [(balances[account].get_currency_units('CCY').number, txn_posting) for txn_posting, balances in postings_for(entries, [account])] reversed_amounts = [] last_amount, _ = amounts[-1] for current_amount, _ in reversed(amounts): if current_amount < last_amount: reversed_amounts.append(current_amount) last_amount = current_amount else: reversed_amounts.append(last_amount) capped_amounts = reversed(reversed_amounts) # Create transfers outward where the future allows it. new_entries = [] offset_amount = ZERO for current_amount, (_, txn_posting) in zip(capped_amounts, amounts): if txn_posting.txn.date >= last_date: break adjusted_amount = current_amount - offset_amount if adjusted_amount > (transfer_minimum + transfer_threshold): amount_transfer = round_to(adjusted_amount - transfer_minimum, transfer_increment) date = txn_posting.txn.date + datetime.timedelta(days=1) amount_transfer_neg = -amount_transfer new_entries.extend(parse(\"\"\" {date} * \"Transfering accumulated savings to other account\" {account} {amount_transfer_neg:2f} CCY {account_out} {amount_transfer:2f} CCY \"\"\", **locals())) offset_amount += amount_transfer return new_entries beancount.scripts.example.generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator) \uf0c1 Generate periodic expense transactions. Parameters: date_iter \u2013 An iterator for dates or datetimes. payee \u2013 A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration \u2013 A string, the narration to use on the transactions. account_from \u2013 An account string the debited account. account_to \u2013 An account string the credited account. amount_generator \u2013 A callable object to generate variates. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator): \"\"\"Generate periodic expense transactions. Args: date_iter: An iterator for dates or datetimes. payee: A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration: A string, the narration to use on the transactions. account_from: An account string the debited account. account_to: An account string the credited account. amount_generator: A callable object to generate variates. Returns: A list of directives. \"\"\" new_entries = [] for dtime in date_iter: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime amount = D(amount_generator()) txn_payee = (payee if isinstance(payee, str) else random.choice(payee)) txn_narration = (narration if isinstance(narration, str) else random.choice(narration)) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{txn_payee}\" \"{txn_narration}\" {account_from} {amount_neg:.2f} CCY {account_to} {amount:.2f} CCY \"\"\", **locals())) return new_entries beancount.scripts.example.generate_prices(date_begin, date_end, currencies, cost_currency) \uf0c1 Generate weekly or monthly price entries for the given currencies. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. currencies \u2013 A list of currency strings to generate prices for. cost_currency \u2013 A string, the cost currency. Returns: A list of Price directives. Source code in beancount/scripts/example.py def generate_prices(date_begin, date_end, currencies, cost_currency): \"\"\"Generate weekly or monthly price entries for the given currencies. Args: date_begin: The start date. date_end: The end date. currencies: A list of currency strings to generate prices for. cost_currency: A string, the cost currency. Returns: A list of Price directives. \"\"\" digits = D('0.01') entries = [] counter = itertools.count() for currency in currencies: start_price = random.uniform(30, 200) growth = random.uniform(0.02, 0.13) # %/year mu = growth * (7 / 365) sigma = random.uniform(0.005, 0.02) # Vol for dtime, price_float in zip(rrule.rrule(rrule.WEEKLY, byweekday=rrule.FR, dtstart=date_begin, until=date_end), price_series(start_price, mu, sigma)): price = D(price_float).quantize(digits) meta = data.new_metadata(generate_prices.__name__, next(counter)) entry = data.Price(meta, dtime.date(), currency, amount.Amount(price, cost_currency)) entries.append(entry) return entries beancount.scripts.example.generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking) \uf0c1 Generate expenses paid out of a credit card account, including payments to the credit card. Parameters: date_birth \u2013 The user's birth date. date_begin \u2013 The start date. date_end \u2013 The end date. account_credit \u2013 The credit card account to generate expenses against. account_checking \u2013 The checking account to generate payments from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking): \"\"\"Generate expenses paid out of a credit card account, including payments to the credit card. Args: date_birth: The user's birth date. date_begin: The start date. date_end: The end date. account_credit: The credit card account to generate expenses against. account_checking: The checking account to generate payments from. Returns: A list of directives. \"\"\" restaurant_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 1, 5), RESTAURANT_NAMES, RESTAURANT_NARRATIONS, account_credit, 'Expenses:Food:Restaurant', lambda: min(random.lognormvariate(math.log(30), math.log(1.5)), random.randint(200, 220))) groceries_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 5, 20), GROCERIES_NAMES, \"Buying groceries\", account_credit, 'Expenses:Food:Groceries', lambda: min(random.lognormvariate(math.log(80), math.log(1.3)), random.randint(250, 300))) subway_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 27, 33), \"Metro Transport Authority\", \"Tram tickets\", account_credit, 'Expenses:Transport:Tram', lambda: D('120.00')) credit_expenses = data.sorted(restaurant_expenses + groceries_expenses + subway_expenses) # Entries to open accounts. credit_preamble = generate_open_entries(date_birth, [account_credit], 'CCY') return data.sorted(credit_preamble + credit_expenses) beancount.scripts.example.generate_retirement_employer_match(entries, account_invest, account_income) \uf0c1 Generate employer matching contributions into a retirement account. Parameters: entries \u2013 A list of directives that cover the retirement account. account_invest \u2013 The name of the retirement cash account. account_income \u2013 The name of the income account. Returns: A list of new entries generated for employer contributions. Source code in beancount/scripts/example.py def generate_retirement_employer_match(entries, account_invest, account_income): \"\"\"Generate employer matching contributions into a retirement account. Args: entries: A list of directives that cover the retirement account. account_invest: The name of the retirement cash account. account_income: The name of the income account. Returns: A list of new entries generated for employer contributions. \"\"\" match_frac = D('0.50') new_entries = parse(\"\"\" {date} open {account_income} CCY \"\"\", date=entries[0].date, account_income=account_income) for txn_posting, balances in postings_for(entries, [account_invest]): amount = txn_posting.posting.units.number * match_frac amount_neg = -amount date = txn_posting.txn.date + ONE_DAY new_entries.extend(parse(\"\"\" {date} * \"Employer match for contribution\" {account_invest} {amount:.2f} CCY {account_income} {amount_neg:.2f} CCY \"\"\", **locals())) return new_entries beancount.scripts.example.generate_retirement_investments(entries, account, commodities_items, price_map) \uf0c1 Invest money deposited to the given retirement account. Parameters: entries \u2013 A list of directives account \u2013 The root account for all retirement investment sub-accounts. commodities_items \u2013 A list of (commodity, fraction to be invested in) items. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. Source code in beancount/scripts/example.py def generate_retirement_investments(entries, account, commodities_items, price_map): \"\"\"Invest money deposited to the given retirement account. Args: entries: A list of directives account: The root account for all retirement investment sub-accounts. commodities_items: A list of (commodity, fraction to be invested in) items. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. \"\"\" open_entries = [] account_cash = join(account, 'Cash') date_origin = entries[0].date open_entries.extend(parse(\"\"\" {date_origin} open {account} CCY institution: \"Retirement_Institution\" address: \"Retirement_Address\" phone: \"Retirement_Phone\" {date_origin} open {account_cash} CCY number: \"882882\" \"\"\", **locals())) for currency, _ in commodities_items: open_entries.extend(parse(\"\"\" {date_origin} open {account}:{currency} {currency} number: \"882882\" \"\"\", **locals())) new_entries = [] for txn_posting, balances in postings_for(entries, [account_cash]): balance = balances[account_cash] amount_to_invest = balance.get_currency_units('CCY').number # Find the date the following Monday, the date to invest. txn_date = txn_posting.txn.date while txn_date.weekday() != calendar.MONDAY: txn_date += ONE_DAY amount_invested = ZERO for commodity, fraction in commodities_items: amount_fraction = amount_to_invest * D(fraction) # Find the price at that date. _, price = prices.get_price(price_map, (commodity, 'CCY'), txn_date) units = (amount_fraction / price).quantize(D('0.001')) amount_cash = (units * price).quantize(D('0.01')) amount_cash_neg = -amount_cash new_entries.extend(parse(\"\"\" {txn_date} * \"Investing {fraction:.0%} of cash in {commodity}\" {account}:{commodity} {units:.3f} {commodity} {{{price:.2f} CCY}} {account}:Cash {amount_cash_neg:.2f} CCY \"\"\", **locals())) balance.add_amount(amount.Amount(-amount_cash, 'CCY')) return data.sorted(open_entries + new_entries) beancount.scripts.example.generate_tax_accounts(year, date_max) \uf0c1 Generate accounts and contribution directives for a particular tax year. Parameters: year \u2013 An integer, the year we're to generate this for. date_max \u2013 The maximum date to produce an entry for. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_accounts(year, date_max): \"\"\"Generate accounts and contribution directives for a particular tax year. Args: year: An integer, the year we're to generate this for. date_max: The maximum date to produce an entry for. Returns: A list of directives. \"\"\" date_year = datetime.date(year, 1, 1) date_filing = (datetime.date(year + 1, 3, 20) + datetime.timedelta(days=random.randint(0, 5))) date_federal = (date_filing + datetime.timedelta(days=random.randint(0, 4))) date_state = (date_filing + datetime.timedelta(days=random.randint(0, 4))) quantum = D('0.01') amount_federal = D(max(random.normalvariate(500, 120), 12)).quantize(quantum) amount_federal_neg = -amount_federal amount_state = D(max(random.normalvariate(300, 100), 10)).quantize(quantum) amount_state_neg = -amount_state amount_payable = -(amount_federal + amount_state) amount_limit = RETIREMENT_LIMITS.get(year, RETIREMENT_LIMITS[None]) amount_limit_neg = -amount_limit entries = parse(\"\"\" ;; Open tax accounts for that year. {date_year} open Expenses:Taxes:Y{year}:CC:Federal:PreTax401k DEFCCY {date_year} open Expenses:Taxes:Y{year}:CC:Medicare CCY {date_year} open Expenses:Taxes:Y{year}:CC:Federal CCY {date_year} open Expenses:Taxes:Y{year}:CC:CityNYC CCY {date_year} open Expenses:Taxes:Y{year}:CC:SDI CCY {date_year} open Expenses:Taxes:Y{year}:CC:State CCY {date_year} open Expenses:Taxes:Y{year}:CC:SocSec CCY ;; Check that the tax amounts have been fully used. {date_year} balance Assets:CC:Federal:PreTax401k 0 DEFCCY {date_year} * \"Allowed contributions for one year\" Income:CC:Federal:PreTax401k {amount_limit_neg} DEFCCY Assets:CC:Federal:PreTax401k {amount_limit} DEFCCY {date_filing} * \"Filing taxes for {year}\" Expenses:Taxes:Y{year}:CC:Federal {amount_federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {amount_state:.2f} CCY Liabilities:AccountsPayable {amount_payable:.2f} CCY {date_federal} * \"FEDERAL TAXPYMT\" Assets:CC:Bank1:Checking {amount_federal_neg:.2f} CCY Liabilities:AccountsPayable {amount_federal:.2f} CCY {date_state} * \"STATE TAX & FINANC PYMT\" Assets:CC:Bank1:Checking {amount_state_neg:.2f} CCY Liabilities:AccountsPayable {amount_state:.2f} CCY \"\"\", **locals()) return [entry for entry in entries if entry.date < date_max] beancount.scripts.example.generate_tax_preamble(date_birth) \uf0c1 Generate tax declarations not specific to any particular year. Parameters: date_birth \u2013 A date instance, the birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_preamble(date_birth): \"\"\"Generate tax declarations not specific to any particular year. Args: date_birth: A date instance, the birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" ;; Tax accounts not specific to a year. {date_birth} open Income:CC:Federal:PreTax401k DEFCCY {date_birth} open Assets:CC:Federal:PreTax401k DEFCCY \"\"\", **locals()) beancount.scripts.example.generate_taxable_investment(date_begin, date_end, entries, price_map, stocks) \uf0c1 Generate opening directives and transactions for an investment account. Parameters: date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. entries \u2013 A list of entries that contains at least the transfers to the investment account's cash account. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). stocks \u2013 A list of strings, the list of commodities to invest in. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_taxable_investment(date_begin, date_end, entries, price_map, stocks): \"\"\"Generate opening directives and transactions for an investment account. Args: date_begin: A date instance, the beginning date. date_end: A date instance, the end date. entries: A list of entries that contains at least the transfers to the investment account's cash account. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). stocks: A list of strings, the list of commodities to invest in. Returns: A list of directives. \"\"\" account = 'Assets:CC:Investment' account_cash = join(account, 'Cash') account_gains = 'Income:CC:Investment:Gains' account_dividends = 'Income:CC:Investment:Dividends' accounts_stocks = ['Assets:CC:Investment:{}'.format(commodity) for commodity in stocks] open_entries = parse(\"\"\" {date_begin} open {account}:Cash CCY {date_begin} open {account_gains} CCY {date_begin} open {account_dividends} CCY \"\"\", **locals()) for stock in stocks: open_entries.extend(parse(\"\"\" {date_begin} open {account}:{stock} {stock} \"\"\", **locals())) # Figure out dates at which dividends should be distributed, near the end of # each quarter. days_to = datetime.timedelta(days=3*90-10) dividend_dates = [] for quarter_begin in iter_quarters(date_begin, date_end): end_of_quarter = quarter_begin + days_to if not (date_begin < end_of_quarter < date_end): continue dividend_dates.append(end_of_quarter) # Iterate over all the dates, but merging in the postings for the cash # account. min_amount = D('1000.00') round_amount = D('100.00') commission = D('8.95') round_units = D('1') frac_invest = D('1.00') frac_dividend = D('0.004') p_daily_buy = 1./15 # days p_daily_sell = 1./90 # days stocks_inventory = inventory.Inventory() new_entries = [] dividend_date_iter = iter(dividend_dates) next_dividend_date = next(dividend_date_iter) for date, balances in iter_dates_with_balance(date_begin, date_end, entries, [account_cash]): # Check if we should insert a dividend. Note that we could not factor # this out because we want to explicitly reinvest the cash dividends and # we also want the dividends to be proportional to the amount of # invested stock, so one feeds on the other and vice-versa. if next_dividend_date and date > next_dividend_date: # Compute the total balances for the stock accounts in order to # create a realistic dividend. total = inventory.Inventory() for account_stock in accounts_stocks: total.add_inventory(balances[account_stock]) # Create an entry offering dividends of 1% of the portfolio. portfolio_cost = total.reduce(convert.get_cost).get_currency_units('CCY').number amount_cash = (frac_dividend * portfolio_cost).quantize(D('0.01')) amount_cash_neg = -amount_cash dividend = parse(\"\"\" {next_dividend_date} * \"Dividends on portfolio\" {account}:Cash {amount_cash:.2f} CCY {account_dividends} {amount_cash_neg:.2f} CCY \"\"\", **locals())[0] new_entries.append(dividend) # Advance the next dividend date. try: next_dividend_date = next(dividend_date_iter) except StopIteration: next_dividend_date = None # If the balance is high, buy with high probability. balance = balances[account_cash] total_cash = balance.get_currency_units('CCY').number assert total_cash >= ZERO, ('Cash balance is negative: {}'.format(total_cash)) invest_cash = total_cash * frac_invest - commission if invest_cash > min_amount: if random.random() < p_daily_buy: commodities = random.sample(stocks, random.randint(1, len(stocks))) lot_amount = round_to(invest_cash / len(commodities), round_amount) invested_amount = ZERO for stock in commodities: # Find the price at that date. _, price = prices.get_price(price_map, (stock, 'CCY'), date) units = round_to((lot_amount / price), round_units) if units <= ZERO: continue amount_cash = -(units * price + commission) # logging.info('Buying %s %s @ %s CCY = %s CCY', # units, stock, price, units * price) buy = parse(\"\"\" {date} * \"Buy shares of {stock}\" {account}:Cash {amount_cash:.2f} CCY {account}:{stock} {units:.0f} {stock} {{{price:.2f} CCY}} Expenses:Financial:Commissions {commission:.2f} CCY \"\"\", **locals())[0] new_entries.append(buy) account_stock = ':'.join([account, stock]) balances[account_cash].add_position(buy.postings[0]) balances[account_stock].add_position(buy.postings[1]) stocks_inventory.add_position(buy.postings[1]) # Don't sell on days you buy. continue # Otherwise, sell with low probability. if not stocks_inventory.is_empty() and random.random() < p_daily_sell: # Choose the lot with the highest gain or highest loss. gains = [] for position in stocks_inventory.get_positions(): base_quote = (position.units.currency, position.cost.currency) _, price = prices.get_price(price_map, base_quote, date) if price == position.cost.number: continue # Skip lots without movement. market_value = position.units.number * price book_value = convert.get_cost(position).number gain = market_value - book_value gains.append((gain, market_value, price, position)) if not gains: continue # Sell either biggest winner or biggest loser. biggest = bool(random.random() < 0.5) lot_tuple = sorted(gains)[0 if biggest else -1] gain, market_value, price, sell_position = lot_tuple #logging.info('Selling {} for {}'.format(sell_position, market_value)) sell_position = -sell_position stock = sell_position.units.currency amount_cash = market_value - commission amount_gain = -gain sell = parse(\"\"\" {date} * \"Sell shares of {stock}\" {account}:{stock} {sell_position} @ {price:.2f} CCY {account}:Cash {amount_cash:.2f} CCY Expenses:Financial:Commissions {commission:.2f} CCY {account_gains} {amount_gain:.2f} CCY \"\"\", **locals())[0] new_entries.append(sell) balances[account_cash].add_position(sell.postings[1]) stocks_inventory.add_position(sell.postings[0]) continue return open_entries + new_entries beancount.scripts.example.generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit) \uf0c1 Generate more dense expenses for a trip. Parameters: date_begin \u2013 A datetime.date instance, the beginning of the trip. date_end \u2013 A datetime.date instance, the end of the trip. tag \u2013 A string, the name of the tag. config \u2013 A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city \u2013 A string, the capitalized name of the destination city. home_city \u2013 A string, the name of the home city. account_credit \u2013 A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. Source code in beancount/scripts/example.py def generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit): \"\"\"Generate more dense expenses for a trip. Args: date_begin: A datetime.date instance, the beginning of the trip. date_end: A datetime.date instance, the end of the trip. tag: A string, the name of the tag. config: A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city: A string, the capitalized name of the destination city. home_city: A string, the name of the home city. account_credit: A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. \"\"\" p_day_generate = 0.3 new_entries = [] for date in date_iter(date_begin, date_end): for payee, account_expense, (mu, sigma3) in config: if random.random() < p_day_generate: amount = random.normalvariate(mu, sigma3 / 3.) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{payee}\" \"\" #{tag} {account_credit} {amount_neg:.2f} CCY {account_expense} {amount:.2f} CCY \"\"\", **locals())) # Consume the vacation days. vacation_hrs = (date_end - date_begin).days * 8 # hrs/day new_entries.extend(parse(\"\"\" {date_end} * \"Consume vacation days\" Assets:CC:Employer1:Vacation -{vacation_hrs:.2f} VACHR Expenses:Vacation {vacation_hrs:.2f} VACHR \"\"\", **locals())) # Generate events for the trip. new_entries.extend(parse(\"\"\" {date_begin} event \"location\" \"{trip_city}\" {date_end} event \"location\" \"{home_city}\" \"\"\", **locals())) return new_entries beancount.scripts.example.get_minimum_balance(entries, account, currency) \uf0c1 Compute the minimum balance of the given account according to the entries history. Parameters: entries \u2013 A list of directives. account \u2013 An account string. currency \u2013 A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. Source code in beancount/scripts/example.py def get_minimum_balance(entries, account, currency): \"\"\"Compute the minimum balance of the given account according to the entries history. Args: entries: A list of directives. account: An account string. currency: A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. \"\"\" min_amount = ZERO for _, balances in postings_for(entries, [account]): balance = balances[account] current = balance.get_currency_units(currency).number if current < min_amount: min_amount = current return min_amount beancount.scripts.example.iter_dates_with_balance(date_begin, date_end, entries, accounts) \uf0c1 Iterate over dates, including the balances of the postings iterator. Parameters: postings_iter \u2013 An iterator of postings as per postings_for(). date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. Source code in beancount/scripts/example.py def iter_dates_with_balance(date_begin, date_end, entries, accounts): \"\"\"Iterate over dates, including the balances of the postings iterator. Args: postings_iter: An iterator of postings as per postings_for(). date_begin: The start date. date_end: The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. \"\"\" balances = collections.defaultdict(inventory.Inventory) merged_txn_postings = iter(merge_postings(entries, accounts)) txn_posting = next(merged_txn_postings, None) for date in date_iter(date_begin, date_end): while txn_posting and txn_posting.txn.date == date: posting = txn_posting.posting balances[posting.account].add_position(posting) txn_posting = next(merged_txn_postings, None) yield date, balances beancount.scripts.example.iter_quarters(date_begin, date_end) \uf0c1 Iterate over all quarters between begin and end dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. Source code in beancount/scripts/example.py def iter_quarters(date_begin, date_end): \"\"\"Iterate over all quarters between begin and end dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. \"\"\" quarter = (date_begin.year, (date_begin.month-1)//3) quarter_last = (date_end.year, (date_end.month-1)//3) assert quarter <= quarter_last while True: year, trimester = quarter yield datetime.date(year, trimester*3 + 1, 1) if quarter == quarter_last: break trimester = (trimester + 1) % 4 if trimester == 0: year += 1 quarter = (year, trimester) beancount.scripts.example.merge_postings(entries, accounts) \uf0c1 Merge all the postings from the given account names. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. Source code in beancount/scripts/example.py def merge_postings(entries, accounts): \"\"\"Merge all the postings from the given account names. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. \"\"\" real_root = realization.realize(entries) merged_postings = [] for account in accounts: real_account = realization.get(real_root, account) if real_account is None: continue merged_postings.extend(txn_posting for txn_posting in real_account.txn_postings if isinstance(txn_posting, data.TxnPosting)) merged_postings.sort(key=lambda txn_posting: txn_posting.txn.date) return merged_postings beancount.scripts.example.parse(input_string, **replacements) \uf0c1 Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Parameters: input_string \u2013 Beancount input text. **replacements \u2013 A dict of keywords to replace to their values. Returns: A list of directive objects. Source code in beancount/scripts/example.py def parse(input_string, **replacements): \"\"\"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Args: input_string: Beancount input text. **replacements: A dict of keywords to replace to their values. Returns: A list of directive objects. \"\"\" if replacements: class IgnoreFormatter(string.Formatter): def check_unused_args(self, used_args, args, kwargs): pass formatter = IgnoreFormatter() formatted_string = formatter.format(input_string, **replacements) else: formatted_string = input_string entries, errors, options_map = parser.parse_string(textwrap.dedent(formatted_string)) if errors: printer.print_errors(errors, file=sys.stderr) raise ValueError(\"Parsed text has errors\") # Interpolation. entries, unused_balance_errors = booking.book(entries, options_map) return data.sorted(entries) beancount.scripts.example.postings_for(entries, accounts, before=False) \uf0c1 Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. before \u2013 A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts after applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. Source code in beancount/scripts/example.py def postings_for(entries, accounts, before=False): \"\"\"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. before: A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts _after_ applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. \"\"\" assert isinstance(accounts, list) merged_txn_postings = merge_postings(entries, accounts) balances = collections.defaultdict(inventory.Inventory) for txn_posting in merged_txn_postings: if before: yield txn_posting, balances posting = txn_posting.posting balances[posting.account].add_position(posting) if not before: yield txn_posting, balances beancount.scripts.example.price_series(start, mu, sigma) \uf0c1 Generate a price series based on a simple stochastic model. Parameters: start \u2013 The beginning value. mu \u2013 The per-step drift, in units of value. sigma \u2013 Volatility of the changes. Yields: Floats, at each step. Source code in beancount/scripts/example.py def price_series(start, mu, sigma): \"\"\"Generate a price series based on a simple stochastic model. Args: start: The beginning value. mu: The per-step drift, in units of value. sigma: Volatility of the changes. Yields: Floats, at each step. \"\"\" value = start while 1: yield value value += random.normalvariate(mu, sigma) * value beancount.scripts.example.replace(string, replacements, strip=False) \uf0c1 Apply word-boundaried regular expression replacements to an indented string. Parameters: string \u2013 Some input template string. replacements \u2013 A dict of regexp to replacement value. strip \u2013 A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. Source code in beancount/scripts/example.py def replace(string, replacements, strip=False): \"\"\"Apply word-boundaried regular expression replacements to an indented string. Args: string: Some input template string. replacements: A dict of regexp to replacement value. strip: A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. \"\"\" output = textwrap.dedent(string) if strip: output = output.strip() for from_, to_ in replacements.items(): if not isinstance(to_, str) and not callable(to_): to_ = str(to_) output = re.sub(r'\\b{}\\b'.format(from_), to_, output) return output beancount.scripts.example.validate_output(contents, positive_accounts, currency) \uf0c1 Check that the output file validates. Parameters: contents \u2013 A string, the output file. positive_accounts \u2013 A list of strings, account names to check for non-negative balances. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 If the output does not validate. Source code in beancount/scripts/example.py def validate_output(contents, positive_accounts, currency): \"\"\"Check that the output file validates. Args: contents: A string, the output file. positive_accounts: A list of strings, account names to check for non-negative balances. currency: A string, the currency to check minimums for. Raises: AssertionError: If the output does not validate. \"\"\" loaded_entries, _, _ = loader.load_string( contents, log_errors=sys.stderr, extra_validations=validation.HARDCORE_VALIDATIONS) # Sanity checks: Check that the checking balance never goes below zero. for account in positive_accounts: check_non_negative(loaded_entries, account, currency) beancount.scripts.example.write_example_file(date_birth, date_begin, date_end, reformat, file) \uf0c1 Generate the example file. Parameters: date_birth \u2013 A datetime.date instance, the birth date of our character. date_begin \u2013 A datetime.date instance, the beginning date at which to generate transactions. date_end \u2013 A datetime.date instance, the end date at which to generate transactions. reformat \u2013 A boolean, true if we should apply global reformatting to this file. file \u2013 A file object, where to write out the output. Source code in beancount/scripts/example.py def write_example_file(date_birth, date_begin, date_end, reformat, file): \"\"\"Generate the example file. Args: date_birth: A datetime.date instance, the birth date of our character. date_begin: A datetime.date instance, the beginning date at which to generate transactions. date_end: A datetime.date instance, the end date at which to generate transactions. reformat: A boolean, true if we should apply global reformatting to this file. file: A file object, where to write out the output. \"\"\" # The following code entirely writes out the output to generic names, such # as \"Employer1\", \"Bank1\", and \"CCY\" (for principal currency). Those names # are purposely chosen to be unique, and only near the very end do we make # renamings to more specific and realistic names. # Name of the checking account. account_opening = 'Equity:Opening-Balances' account_payable = 'Liabilities:AccountsPayable' account_checking = 'Assets:CC:Bank1:Checking' account_credit = 'Liabilities:CC:CreditCard1' account_retirement = 'Assets:CC:Retirement' account_investing = 'Assets:CC:Investment:Cash' # Commodities. commodity_entries = generate_commodity_entries(date_birth) # Estimate the rent. rent_amount = round_to(ANNUAL_SALARY / RENT_DIVISOR, RENT_INCREMENT) # Get a random employer. employer_name, employer_address = random.choice(EMPLOYERS) logging.info(\"Generating Salary Employment Income\") income_entries = generate_employment_income(employer_name, employer_address, ANNUAL_SALARY, account_checking, join(account_retirement, 'Cash'), date_begin, date_end) logging.info(\"Generating Expenses from Banking Accounts\") banking_expenses = generate_banking_expenses(date_begin, date_end, account_checking, rent_amount) logging.info(\"Generating Regular Expenses via Credit Card\") credit_regular_entries = generate_regular_credit_expenses( date_birth, date_begin, date_end, account_credit, account_checking) logging.info(\"Generating Credit Card Expenses for Trips\") trip_entries = [] destinations = sorted(TRIP_DESTINATIONS.items()) destinations.extend(destinations) random.shuffle(destinations) for (date_trip_begin, date_trip_end), (destination_name, config) in zip( compute_trip_dates(date_begin, date_end), destinations): # Compute a suitable tag. tag = 'trip-{}-{}'.format(destination_name.lower().replace(' ', '-'), date_trip_begin.year) #logging.info(\"%s -- %s %s\", tag, date_trip_begin, date_trip_end) # Remove regular entries during this trip. credit_regular_entries = [entry for entry in credit_regular_entries if not(date_trip_begin <= entry.date < date_trip_end)] # Generate entries for the trip. this_trip_entries = generate_trip_entries( date_trip_begin, date_trip_end, tag, config, destination_name.replace('-', ' ').title(), HOME_NAME, account_credit) trip_entries.extend(this_trip_entries) logging.info(\"Generating Credit Card Payment Entries\") credit_payments = generate_clearing_entries( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end, bymonthday=7), 0, 4), \"CreditCard1\", \"Paying off credit card\", credit_regular_entries, account_credit, account_checking) credit_entries = credit_regular_entries + trip_entries + credit_payments logging.info(\"Generating Tax Filings and Payments\") tax_preamble = generate_tax_preamble(date_birth) # Figure out all the years we need tax accounts for. years = set() for account_name in getters.get_accounts(income_entries): match = re.match(r'Expenses:Taxes:Y(\\d\\d\\d\\d)', account_name) if match: years.add(int(match.group(1))) taxes = [(year, generate_tax_accounts(year, date_end)) for year in sorted(years)] tax_entries = tax_preamble + functools.reduce(operator.add, (entries for _, entries in taxes)) logging.info(\"Generating Opening of Banking Accounts\") # Open banking accounts and gift the checking account with a balance that # will offset all the amounts to ensure a positive balance throughout its # lifetime. entries_for_banking = data.sorted(income_entries + banking_expenses + credit_entries + tax_entries) minimum = get_minimum_balance(entries_for_banking, account_checking, 'CCY') banking_entries = generate_banking(entries_for_banking, date_begin, date_end, max(-minimum, ZERO)) logging.info(\"Generating Transfers to Investment Account\") banking_transfers = generate_outgoing_transfers( data.sorted(income_entries + banking_entries + banking_expenses + credit_entries + tax_entries), account_checking, account_investing, transfer_minimum=D('200'), transfer_threshold=D('3000'), transfer_increment=D('500')) logging.info(\"Generating Prices\") # Generate price entries for investment currencies and create a price map to # use for later for generating investment transactions. funds_allocation = {'MFUND1': 0.40, 'MFUND2': 0.60} stocks = ['STK1', 'STK2', 'STK3', 'STK4'] price_entries = generate_prices(date_begin, date_end, sorted(funds_allocation.keys()) + stocks, 'CCY') price_map = prices.build_price_map(price_entries) logging.info(\"Generating Employer Match Contribution\") account_match = 'Income:US:Employer1:Match401k' retirement_match = generate_retirement_employer_match(income_entries, join(account_retirement, 'Cash'), account_match) logging.info(\"Generating Retirement Investments\") retirement_entries = generate_retirement_investments( income_entries + retirement_match, account_retirement, sorted(funds_allocation.items()), price_map) logging.info(\"Generating Taxes Investments\") investment_entries = generate_taxable_investment(date_begin, date_end, banking_transfers, price_map, stocks) logging.info(\"Generating Expense Accounts\") expense_accounts_entries = generate_expense_accounts(date_birth) logging.info(\"Generating Equity Accounts\") equity_entries = generate_open_entries(date_birth, [account_opening, account_payable]) logging.info(\"Generating Balance Checks\") credit_checks = generate_balance_checks(credit_entries, account_credit, date_random_seq(date_begin, date_end, 20, 30)) banking_checks = generate_balance_checks(data.sorted(income_entries + banking_entries + banking_expenses + banking_transfers + credit_entries + tax_entries), account_checking, date_random_seq(date_begin, date_end, 20, 30)) logging.info(\"Outputting and Formatting Entries\") dcontext = display_context.DisplayContext() default_int_digits = 8 for currency, precision in {'USD': 2, 'CAD': 2, 'VACHR':0, 'IRAUSD': 2, 'VBMPX': 3, 'RGAGX': 3, 'ITOT': 0, 'VEA': 0, 'VHT': 0, 'GLD': 0}.items(): int_digits = default_int_digits if precision > 0: int_digits += 1 + precision dcontext.update(D('{{:0{}.{}f}}'.format(int_digits, precision).format(0)), currency) output = io.StringIO() def output_section(title, entries): output.write('\\n\\n\\n{}\\n\\n'.format(title)) printer.print_entries(data.sorted(entries), dcontext, file=output) output.write(FILE_PREAMBLE.format(**locals())) output_section('* Commodities', commodity_entries) output_section('* Equity Accounts', equity_entries) output_section('* Banking', data.sorted(banking_entries + banking_expenses + banking_transfers + banking_checks)) output_section('* Credit-Cards', data.sorted(credit_entries + credit_checks)) output_section('* Taxable Investments', investment_entries) output_section('* Retirement Investments', data.sorted(retirement_entries + retirement_match)) output_section('* Sources of Income', income_entries) output_section('* Taxes', tax_preamble) for year, entries in taxes: output_section('** Tax Year {}'.format(year), entries) output_section('* Expenses', expense_accounts_entries) output_section('* Prices', price_entries) output_section('* Cash', []) logging.info(\"Contextualizing to Realistic Names\") contents, replacements = contextualize_file(output.getvalue(), employer_name) if reformat: contents = format.align_beancount(contents) logging.info(\"Writing contents\") file.write(contents) logging.info(\"Validating Results\") validate_output(contents, [replace(account, replacements) for account in [account_checking]], replace('CCY', replacements)) beancount.scripts.format \uf0c1 Align a beancount/ledger input file's numbers. This reformats at beancount or ledger input file so that the amounts in the postings are all aligned to the same column. The currency should match. Note: this does not parse the Beancount ledger. It simply uses regular expressions and text manipulations to do its work. beancount.scripts.format.align_beancount(contents, prefix_width=None, num_width=None, currency_column=None) \uf0c1 Reformat Beancount input to align all the numbers at the same column. Parameters: contents \u2013 A string, Beancount input syntax to reformat. prefix_width \u2013 An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width \u2013 An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column \u2013 An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. Source code in beancount/scripts/format.py def align_beancount(contents, prefix_width=None, num_width=None, currency_column=None): \"\"\"Reformat Beancount input to align all the numbers at the same column. Args: contents: A string, Beancount input syntax to reformat. prefix_width: An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width: An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column: An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. \"\"\" # Find all lines that have a number in them and calculate the maximum length # of the stripped prefix and the number. match_pairs = [] for line in contents.splitlines(): match = re.match( r'([^\";]*?)\\s+([-+]?\\s*[\\d,]+(?:\\.\\d*)?)\\s+({}\\b.*)'.format(amount.CURRENCY_RE), line) if match: prefix, number, rest = match.groups() match_pairs.append((prefix, number, rest)) else: match_pairs.append((line, None, None)) # Normalize whitespace before lines that has some indent and an account # name. norm_match_pairs = normalize_indent_whitespace(match_pairs) if currency_column: output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: num_of_spaces = currency_column - len(prefix) - len(number) - 4 spaces = ' ' * num_of_spaces output.write(prefix + spaces + ' ' + number + ' ' + rest) output.write('\\n') return output.getvalue() # Compute the maximum widths. filtered_pairs = [(prefix, number) for prefix, number, _ in match_pairs if number is not None] if filtered_pairs: max_prefix_width = max(len(prefix) for prefix, _ in filtered_pairs) max_num_width = max(len(number) for _, number in filtered_pairs) else: max_prefix_width = 0 max_num_width = 0 # Use user-supplied overrides, if available if prefix_width: max_prefix_width = prefix_width if num_width: max_num_width = num_width # Create a format that will admit the maximum width of all prefixes equally. line_format = '{{:<{prefix_width}}} {{:>{num_width}}} {{}}'.format( prefix_width=max_prefix_width, num_width=max_num_width) # Process each line to an output buffer. output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: output.write(line_format.format(prefix.rstrip(), number, rest)) output.write('\\n') formatted_contents = output.getvalue() # Ensure that the file before and after have only whitespace differences. # This is a sanity check, to make really sure we never change anything but whitespace, # so it's safe. # open('/tmp/before', 'w').write(re.sub(r'[ \\t]+', ' ', contents)) # open('/tmp/after', 'w').write(re.sub(r'[ \\t]+', ' ', formatted_contents)) old_stripped = re.sub(r'[ \\t\\n]+', ' ', contents.rstrip()) new_stripped = re.sub(r'[ \\t\\n]+', ' ', formatted_contents.rstrip()) assert (old_stripped == new_stripped), (old_stripped, new_stripped) return formatted_contents beancount.scripts.format.compute_most_frequent(iterable) \uf0c1 Compute the frequencies of the given elements and return the most frequent. Parameters: iterable \u2013 A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. Source code in beancount/scripts/format.py def compute_most_frequent(iterable): \"\"\"Compute the frequencies of the given elements and return the most frequent. Args: iterable: A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. \"\"\" frequencies = collections.Counter(iterable) if not frequencies: return None counts = sorted((count, element) for element, count in frequencies.items()) # Note: In case of a tie, this chooses the longest width. # We could eventually make this an option. return counts[-1][1] beancount.scripts.format.normalize_indent_whitespace(match_pairs) \uf0c1 Normalize whitespace before lines that has some indent and an account name. Parameters: match_pairs \u2013 A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. Source code in beancount/scripts/format.py def normalize_indent_whitespace(match_pairs): \"\"\"Normalize whitespace before lines that has some indent and an account name. Args: match_pairs: A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. \"\"\" # Compute most frequent account name prefix. match_posting = re.compile(r'([ \\t]+)({}.*)'.format(account.ACCOUNT_RE)).match width = compute_most_frequent( len(match.group(1)) for match in (match_posting(prefix) for prefix, _, _ in match_pairs) if match is not None) norm_format = ' ' * (width or 0) + '{}' # Make the necessary adjustments. adjusted_pairs = [] for tup in match_pairs: prefix, number, rest = tup match = match_posting(prefix) if match is not None: tup = (norm_format.format(match.group(2)), number, rest) adjusted_pairs.append(tup) return adjusted_pairs beancount.scripts.sql \uf0c1 Convert a Beancount ledger into an SQL database. beancount.scripts.sql.BalanceWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.BalanceWriter.type ( tuple ) \uf0c1 Balance(meta, date, account, amount, tolerance, diff_amount) beancount.scripts.sql.BalanceWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.BalanceWriter.type.__new__(_cls, meta, date, account, amount, tolerance, diff_amount) special staticmethod \uf0c1 Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount) beancount.scripts.sql.BalanceWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.BalanceWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.amount.number, entry.amount.currency, entry.diff_amount.currency if entry.diff_amount else None, entry.diff_amount.currency if entry.diff_amount else None) beancount.scripts.sql.CloseWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.CloseWriter.type ( tuple ) \uf0c1 Close(meta, date, account) beancount.scripts.sql.CloseWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.CloseWriter.type.__new__(_cls, meta, date, account) special staticmethod \uf0c1 Create new instance of Close(meta, date, account) beancount.scripts.sql.CloseWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.CloseWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account,) beancount.scripts.sql.DirectiveWriter \uf0c1 A base class for writers of directives. This is used to factor out code for all the simple directives types (all types except Transaction). beancount.scripts.sql.DirectiveWriter.__call__(self, connection, entries) special \uf0c1 Create a table for a directives. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def __call__(self, connection, entries): \"\"\"Create a table for a directives. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: columns_text = ','.join(self.columns.strip().splitlines()) connection.execute(\"\"\" CREATE TABLE {name}_detail ( id INTEGER PRIMARY KEY, {columns} ); \"\"\".format(name=self.name, columns=columns_text)) connection.execute(\"\"\" CREATE VIEW {name} AS SELECT * FROM entry JOIN {name}_detail USING (id); \"\"\".format(name=self.name)) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, self.type): continue # Store common data. connection.execute(\"\"\" INSERT INTO entry VALUES (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, self.name, entry.meta[\"filename\"], entry.meta[\"lineno\"])) # Store detail data. detail_data = self.get_detail(entry) row_data = (eid,) + detail_data query = \"\"\" INSERT INTO {name}_detail VALUES ({placeholder}); \"\"\".format(name=self.name, placeholder=','.join(['?'] * (1 + len(detail_data)))) connection.execute(query, row_data) beancount.scripts.sql.DirectiveWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): \"\"\"Provide data to store for details table. Args: entry: An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. \"\"\" raise NotImplementedError beancount.scripts.sql.DocumentWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.DocumentWriter.type ( tuple ) \uf0c1 Document(meta, date, account, filename, tags, links) beancount.scripts.sql.DocumentWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.DocumentWriter.type.__new__(_cls, meta, date, account, filename, tags, links) special staticmethod \uf0c1 Create new instance of Document(meta, date, account, filename, tags, links) beancount.scripts.sql.DocumentWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.DocumentWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.filename) beancount.scripts.sql.EventWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.EventWriter.type ( tuple ) \uf0c1 Event(meta, date, type, description) beancount.scripts.sql.EventWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.EventWriter.type.__new__(_cls, meta, date, type, description) special staticmethod \uf0c1 Create new instance of Event(meta, date, type, description) beancount.scripts.sql.EventWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.EventWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.type, entry.description) beancount.scripts.sql.NoteWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.NoteWriter.type ( tuple ) \uf0c1 Note(meta, date, account, comment) beancount.scripts.sql.NoteWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.NoteWriter.type.__new__(_cls, meta, date, account, comment) special staticmethod \uf0c1 Create new instance of Note(meta, date, account, comment) beancount.scripts.sql.NoteWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.NoteWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.comment) beancount.scripts.sql.OpenWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.OpenWriter.type ( tuple ) \uf0c1 Open(meta, date, account, currencies, booking) beancount.scripts.sql.OpenWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.OpenWriter.type.__new__(_cls, meta, date, account, currencies, booking) special staticmethod \uf0c1 Create new instance of Open(meta, date, account, currencies, booking) beancount.scripts.sql.OpenWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.OpenWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, ','.join(entry.currencies or [])) beancount.scripts.sql.PadWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.PadWriter.type ( tuple ) \uf0c1 Pad(meta, date, account, source_account) beancount.scripts.sql.PadWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.PadWriter.type.__new__(_cls, meta, date, account, source_account) special staticmethod \uf0c1 Create new instance of Pad(meta, date, account, source_account) beancount.scripts.sql.PadWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.PadWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.source_account) beancount.scripts.sql.PriceWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.PriceWriter.type ( tuple ) \uf0c1 Price(meta, date, currency, amount) beancount.scripts.sql.PriceWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.PriceWriter.type.__new__(_cls, meta, date, currency, amount) special staticmethod \uf0c1 Create new instance of Price(meta, date, currency, amount) beancount.scripts.sql.PriceWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.PriceWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.currency, entry.amount.number, entry.amount.currency) beancount.scripts.sql.QueryWriter ( DirectiveWriter ) \uf0c1 beancount.scripts.sql.QueryWriter.type ( tuple ) \uf0c1 Query(meta, date, name, query_string) beancount.scripts.sql.QueryWriter.type.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.scripts.sql.QueryWriter.type.__new__(_cls, meta, date, name, query_string) special staticmethod \uf0c1 Create new instance of Query(meta, date, name, query_string) beancount.scripts.sql.QueryWriter.type.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.scripts.sql.QueryWriter.get_detail(self, entry) \uf0c1 Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.name, entry.query_string) beancount.scripts.sql.adapt_decimal(number) \uf0c1 Adapt a Decimal instance to a string for creating queries. Parameters: number \u2013 An instance of Decimal. Returns: A string. Source code in beancount/scripts/sql.py def adapt_decimal(number): \"\"\"Adapt a Decimal instance to a string for creating queries. Args: number: An instance of Decimal. Returns: A string. \"\"\" return str(number) beancount.scripts.sql.convert_decimal(string) \uf0c1 Convert a Decimal string to a Decimal instance. Parameters: string \u2013 A decimal number in a string. Returns: An instance of Decimal. Source code in beancount/scripts/sql.py def convert_decimal(string): \"\"\"Convert a Decimal string to a Decimal instance. Args: string: A decimal number in a string. Returns: An instance of Decimal. \"\"\" return Decimal(string) beancount.scripts.sql.output_common(connection, unused_entries) \uf0c1 Create a table of common data for all entries. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_common(connection, unused_entries): \"\"\"Create a table of common data for all entries. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE entry ( id INTEGER PRIMARY KEY, date DATE, type CHARACTER(8), source_filename STRING, source_lineno INTEGER ); \"\"\") beancount.scripts.sql.output_transactions(connection, entries) \uf0c1 Create a table for transactions and fill in the data. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_transactions(connection, entries): \"\"\"Create a table for transactions and fill in the data. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE transactions_detail ( id INTEGER PRIMARY KEY, flag CHARACTER(1), payee VARCHAR, narration VARCHAR, tags VARCHAR, -- Comma-separated links VARCHAR -- Comma-separated ); \"\"\") connection.execute(\"\"\" CREATE VIEW transactions AS SELECT * FROM entry JOIN transactions_detail USING (id); \"\"\") connection.execute(\"\"\" CREATE TABLE postings ( posting_id INTEGER PRIMARY KEY, id INTEGER, flag CHARACTER(1), account VARCHAR, number DECIMAL(16, 6), currency CHARACTER(10), cost_number DECIMAL(16, 6), cost_currency CHARACTER(10), cost_date DATE, cost_label VARCHAR, price_number DECIMAL(16, 6), price_currency CHARACTER(10), FOREIGN KEY(id) REFERENCES entries(id) ); \"\"\") postings_count = iter(itertools.count()) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue connection.execute(\"\"\" insert into entry values (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, 'txn', entry.meta[\"filename\"], entry.meta[\"lineno\"])) connection.execute(\"\"\" insert into transactions_detail values (?, ?, ?, ?, ?, ?); \"\"\", (eid, entry.flag, entry.payee, entry.narration, ','.join(entry.tags or ()), ','.join(entry.links or ()))) for posting in entry.postings: pid = next(postings_count) units = posting.units cost = posting.cost price = posting.price connection.execute(\"\"\" INSERT INTO postings VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \"\"\", (pid, eid, posting.flag, posting.account, units.number, units.currency, cost.number if cost else None, cost.currency if cost else None, cost.date if cost else None, cost.label if cost else None, price.number if price else None, price.currency if price else None)) beancount.scripts.sql.setup_decimal_support() \uf0c1 Setup sqlite3 to support conversions to/from Decimal numbers. Source code in beancount/scripts/sql.py def setup_decimal_support(): \"\"\"Setup sqlite3 to support conversions to/from Decimal numbers. \"\"\" dbapi.register_adapter(Decimal, adapt_decimal) dbapi.register_converter(\"decimal\", convert_decimal) beancount.scripts.tutorial \uf0c1 Write output files for the tutorial commands.","title":"beancount.scripts"},{"location":"api_reference/beancount.scripts.html#beancountscripts","text":"Implementation of the various scripts available from bin. This is structured this way because we want all the significant codes under a single directory, for analysis, grepping and unit testing.","title":"beancount.scripts"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake","text":"Bake a Beancount input file's web files to a directory hierarchy. You provide a Beancount filename, an output directory, and this script runs a server and a scraper that puts all the files in the directory, and if your output name has an archive suffix, we automatically the fetched directory contents to the archive and delete them.","title":"bake"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.archive","text":"Archive the directory to the given tar/gz archive filename. Parameters: command_template \u2013 A string, the command template to format with in order to compute the command to run. directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. quiet \u2013 A boolean, True to suppress output. Exceptions: IOError \u2013 if the directory does not exist or if the archive name already Source code in beancount/scripts/bake.py def archive(command_template, directory, archive, quiet=False): \"\"\"Archive the directory to the given tar/gz archive filename. Args: command_template: A string, the command template to format with in order to compute the command to run. directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. quiet: A boolean, True to suppress output. Raises: IOError: if the directory does not exist or if the archive name already exists. \"\"\" directory = path.abspath(directory) archive = path.abspath(archive) if not path.exists(directory): raise IOError(\"Directory to archive '{}' does not exist\".format( directory)) if path.exists(archive): raise IOError(\"Output archive name '{}' already exists\".format( archive)) command = command_template.format(directory=directory, dirname=path.dirname(directory), basename=path.basename(directory), archive=archive) pipe = subprocess.Popen(shlex.split(command), shell=False, cwd=path.dirname(directory), stdout=subprocess.PIPE if quiet else None, stderr=subprocess.PIPE if quiet else None) _, _ = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Archive failure\")","title":"archive()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.archive_zip","text":"Archive the directory to the given tar/gz archive filename. Parameters: directory \u2013 A string, the name of the directory to archive. archive \u2013 A string, the name of the file to output. Source code in beancount/scripts/bake.py def archive_zip(directory, archive): \"\"\"Archive the directory to the given tar/gz archive filename. Args: directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. \"\"\" # Figure out optimal level of compression among the supported ones in this # installation. for spec, compression in [ ('lzma', zipfile.ZIP_LZMA), ('bz2', zipfile.ZIP_BZIP2), ('zlib', zipfile.ZIP_DEFLATED)]: if importlib.util.find_spec(spec): zip_compression = compression break else: # Default is no compression. zip_compression = zipfile.ZIP_STORED with file_utils.chdir(directory), zipfile.ZipFile( archive, 'w', compression=zip_compression) as archfile: for root, dirs, files in os.walk(directory): for filename in files: relpath = path.relpath(path.join(root, filename), directory) archfile.write(relpath)","title":"archive_zip()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.bake_to_directory","text":"Serve and bake a Beancount's web to a directory. Parameters: webargs \u2013 An argparse parsed options object with the web app arguments. output_dir \u2013 A directory name. We don't check here whether it exists or not. quiet \u2013 A boolean, True to suppress web server fetch log. render_all_pages \u2013 If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. Source code in beancount/scripts/bake.py def bake_to_directory(webargs, output_dir, render_all_pages=True): \"\"\"Serve and bake a Beancount's web to a directory. Args: webargs: An argparse parsed options object with the web app arguments. output_dir: A directory name. We don't check here whether it exists or not. quiet: A boolean, True to suppress web server fetch log. render_all_pages: If true, fetch the full set of pages, not just the subset that is palatable. Returns: True on success, False otherwise. \"\"\" callback = functools.partial(save_scraped_document, output_dir) if render_all_pages: ignore_regexps = None else: regexps = [ # Skip the context pages, too slow. r'/context/', # Skip the link pages, too slow. r'/link/', # Skip the component pages... too many. r'/view/component/', # Skip served documents. r'/.*/doc/', # Skip monthly pages. r'/view/year/\\d\\d\\d\\d/month/', ] ignore_regexps = '({})'.format('|'.join(regexps)) processed_urls, skipped_urls = web.scrape_webapp(webargs, callback, ignore_regexps)","title":"bake_to_directory()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.normalize_filename","text":"Convert URL paths to filenames. Add .html extension if needed. Parameters: url \u2013 A string, the url to convert. Returns: A string, possibly with an extension appended. Source code in beancount/scripts/bake.py def normalize_filename(url): \"\"\"Convert URL paths to filenames. Add .html extension if needed. Args: url: A string, the url to convert. Returns: A string, possibly with an extension appended. \"\"\" if url.endswith('/'): return path.join(url, 'index.html') elif BINARY_MATCH(url): return url else: return url if url.endswith('.html') else (url + '.html')","title":"normalize_filename()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.relativize_links","text":"Make all the links in the contents string relative to an URL. Parameters: html \u2013 An lxml document node. current_url \u2013 A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. Source code in beancount/scripts/bake.py def relativize_links(html, current_url): \"\"\"Make all the links in the contents string relative to an URL. Args: html: An lxml document node. current_url: A string, the URL of the current page, a path to. a file or a directory. If the path represents a directory, the path ends with a /. \"\"\" current_dir = path.dirname(current_url) for element, attribute, link, pos in lxml.html.iterlinks(html): if path.isabs(link): relative_link = path.relpath(normalize_filename(link), current_dir) element.set(attribute, relative_link)","title":"relativize_links()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.remove_links","text":"Convert a list of anchors () from an HTML tree to spans (). Parameters: html \u2013 An lxml document node. targets \u2013 A set of string, targets to be removed. Source code in beancount/scripts/bake.py def remove_links(html, targets): \"\"\"Convert a list of anchors () from an HTML tree to spans (). Args: html: An lxml document node. targets: A set of string, targets to be removed. \"\"\" for element, attribute, link, pos in lxml.html.iterlinks(html): if link in targets: del element.attrib[attribute] element.tag = 'span' element.set('class', 'removed-link')","title":"remove_links()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.bake.save_scraped_document","text":"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Parameters: output_dir \u2013 A string, the output directory to write. url \u2013 A string, the originally requested URL. response \u2013 An http response as per urlopen. contents \u2013 Bytes, the content of a response. html_root \u2013 An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls \u2013 A set of the links from the file that were skipped. Source code in beancount/scripts/bake.py def save_scraped_document(output_dir, url, response, contents, html_root, skipped_urls): \"\"\"Callback function to process a document being scraped. This converts the document to have relative links and writes out the file to the output directory. Args: output_dir: A string, the output directory to write. url: A string, the originally requested URL. response: An http response as per urlopen. contents: Bytes, the content of a response. html_root: An lxml root node for the document, optionally. If this is provided, this avoid you having to reprocess it (for performance reasons). skipped_urls: A set of the links from the file that were skipped. \"\"\" if response.status != 200: logging.error(\"Invalid status: %s\", response.status) # Ignore directories. if url.endswith('/'): return # Note that we're saving the file under the non-redirected URL, because this # will have to be opened using files and there are no redirects that way. if response.info().get_content_type() == 'text/html': if html_root is None: html_root = lxml.html.document_fromstring(contents) remove_links(html_root, skipped_urls) relativize_links(html_root, url) contents = lxml.html.tostring(html_root, method=\"html\") # Compute output filename and write out the relativized contents. output_filename = path.join(output_dir, normalize_filename(url).lstrip('/')) os.makedirs(path.dirname(output_filename), exist_ok=True) with open(output_filename, 'wb') as outfile: outfile.write(contents)","title":"save_scraped_document()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.check","text":"Parse, check and realize a beancount input file. This also measures the time it takes to run all these steps.","title":"check"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps","text":"Check the installation dependencies and report the version numbers of each. This is meant to be used as an error diagnostic tool.","title":"deps"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_cdecimal","text":"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_cdecimal(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" # Note: this code mirrors and should be kept in-sync with that at the top of # beancount.core.number. # Try the built-in installation. import decimal if is_fast_decimal(decimal): return ('cdecimal', '{} (built-in)'.format(decimal.__version__), True) # Try an explicitly installed version. try: import cdecimal if is_fast_decimal(cdecimal): return ('cdecimal', getattr(cdecimal, '__version__', 'OKAY'), True) except ImportError: pass # Not found. return ('cdecimal', None, False)","title":"check_cdecimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_dependencies","text":"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. Source code in beancount/scripts/deps.py def check_dependencies(): \"\"\"Check the runtime dependencies and report their version numbers. Returns: A list of pairs of (package-name, version-number, sufficient) whereby if a package has not been installed, its 'version-number' will be set to None. Otherwise, it will be a string with the version number in it. 'sufficient' will be True if the version if sufficient for this installation of Beancount. \"\"\" return [ # Check for a complete installation of Python itself. check_python(), check_cdecimal(), # Modules we really do need installed. check_import('dateutil'), check_import('bottle'), check_import('ply', module_name='ply.yacc', min_version='3.4'), check_import('lxml', module_name='lxml.etree', min_version='3'), # Optionally required to upload data to Google Drive. check_import('googleapiclient'), check_import('oauth2client'), check_import('httplib2'), # Optionally required to support various price source fetchers. check_import('requests', min_version='2.0'), # Optionally required to support imports (identify, extract, file) code. check_python_magic(), check_import('beautifulsoup4', module_name='bs4', min_version='4'), ]","title":"check_dependencies()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_import","text":"Check that a particular module name is installed. Parameters: package_name \u2013 A string, the name of the package and module to be imported to verify this works. This should have a version attribute on it. min_version \u2013 If not None, a string, the minimum version number we require. module_name \u2013 The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_import(package_name, min_version=None, module_name=None): \"\"\"Check that a particular module name is installed. Args: package_name: A string, the name of the package and module to be imported to verify this works. This should have a __version__ attribute on it. min_version: If not None, a string, the minimum version number we require. module_name: The name of the module to import if it differs from the package name. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" if module_name is None: module_name = package_name try: __import__(module_name) module = sys.modules[module_name] if min_version is not None: version = module.__version__ assert isinstance(version, str) is_sufficient = (parse_version(version) >= parse_version(min_version) if min_version else True) else: version, is_sufficient = None, True except ImportError: version, is_sufficient = None, False return (package_name, version, is_sufficient)","title":"check_import()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_python","text":"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python(): \"\"\"Check that Python 3.3 or above is installed. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" return ('python3', '.'.join(map(str, sys.version_info[:3])), sys.version_info[:2] >= (3, 3))","title":"check_python()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.check_python_magic","text":"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). Source code in beancount/scripts/deps.py def check_python_magic(): \"\"\"Check that a recent-enough version of python-magic is installed. python-magic is an interface to libmagic, which is used by the 'file' tool and UNIX to identify file types. Note that there are two Python wrappers which provide the 'magic' import: python-magic and filemagic. The former is what we need, which appears to be more recently maintained. Returns: A triple of (package-name, version-number, sufficient) as per check_dependencies(). \"\"\" try: import magic # Check that python-magic and not filemagic is installed. if not hasattr(magic, 'from_file'): # 'filemagic' is installed; install python-magic. raise ImportError return ('python-magic', 'OK', True) except ImportError: return ('python-magic', None, False)","title":"check_python_magic()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.is_fast_decimal","text":"Return true if a fast C decimal implementation is installed. Source code in beancount/scripts/deps.py def is_fast_decimal(decimal_module): \"Return true if a fast C decimal implementation is installed.\" return isinstance(decimal_module.Decimal().sqrt, types.BuiltinFunctionType)","title":"is_fast_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.list_dependencies","text":"Check the dependencies and produce a listing on the given file. Parameters: file \u2013 A file object to write the output to. Source code in beancount/scripts/deps.py def list_dependencies(file=sys.stderr): \"\"\"Check the dependencies and produce a listing on the given file. Args: file: A file object to write the output to. \"\"\" print(\"Dependencies:\") for package, version, sufficient in check_dependencies(): print(\" {:16}: {} {}\".format( package, version or 'NOT INSTALLED', \"(INSUFFICIENT)\" if version and not sufficient else \"\"), file=file)","title":"list_dependencies()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.deps.parse_version","text":"Parse the version string into a comparable tuple. Source code in beancount/scripts/deps.py def parse_version(version_str: str) -> str: \"\"\"Parse the version string into a comparable tuple.\"\"\" return [int(v) for v in version_str.split('.')]","title":"parse_version()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories","text":"Check that document directories mirror a list of accounts correctly.","title":"directories"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.ValidateDirectoryError","text":"A directory validation error.","title":"ValidateDirectoryError"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.validate_directories","text":"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: entries \u2013 A list of directives. document_dirs \u2013 A list of string, the directory roots to walk and validate. Source code in beancount/scripts/directories.py def validate_directories(entries, document_dirs): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: entries: A list of directives. document_dirs: A list of string, the directory roots to walk and validate. \"\"\" # Get the list of accounts declared in the ledge. accounts = getters.get_accounts(entries) # For each of the roots, validate the hierarchy of directories. for document_dir in document_dirs: errors = validate_directory(accounts, document_dir) for error in errors: print(\"ERROR: {}\".format(error))","title":"validate_directories()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.directories.validate_directory","text":"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Parameters: account \u2013 A set or dict of account names. document_dir \u2013 A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. Source code in beancount/scripts/directories.py def validate_directory(accounts, document_dir): \"\"\"Check a directory hierarchy against a list of valid accounts. Walk the directory hierarchy, and for all directories with names matching that of accounts (with \":\" replaced with \"/\"), check that they refer to an account name declared in the given list. Args: account: A set or dict of account names. document_dir: A string, the root directory to walk and validate. Returns: An errors for each invalid directory name found. \"\"\" # Generate all parent accounts in the account_set we're checking against, so # that parent directories with no corresponding account don't warn. accounts_with_parents = set(accounts) for account_ in accounts: while True: parent = account.parent(account_) if not parent: break if parent in accounts_with_parents: break accounts_with_parents.add(parent) account_ = parent errors = [] for directory, account_name, _, _ in account.walk(document_dir): if account_name not in accounts_with_parents: errors.append(ValidateDirectoryError( \"Invalid directory '{}': no corresponding account '{}'\".format( directory, account_name))) return errors","title":"validate_directory()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor","text":"Debugging tool for those finding bugs in Beancount. This tool is able to dump lexer/parser state, and will provide other services in the name of debugging.","title":"doctor"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError","text":"RenderError(source, message, entry)","title":"RenderError"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/doctor.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__new__","text":"Create new instance of RenderError(source, message, entry)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.RenderError.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/doctor.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_checkdeps","text":"Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.')","title":"do_checkdeps()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_context","text":"Describe the context that a particular transaction is applied to. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). Source code in beancount/scripts/doctor.py def do_context(filename, args): \"\"\"Describe the context that a particular transaction is applied to. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be a string which contains either a lineno integer or a filename:lineno combination (which can be used if the location is not in the top-level file). \"\"\" from beancount.reports import context from beancount import loader # Check we have the required number of arguments. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") # Load the input files. entries, errors, options_map = loader.load_file(filename) # Parse the arguments, get the line number. match = re.match(r\"(.+):(\\d+)$\", args[0]) if match: search_filename = path.abspath(match.group(1)) lineno = int(match.group(2)) elif re.match(r\"(\\d+)$\", args[0]): # Note: Make sure to use the absolute filename used by the parser to # resolve the file. search_filename = options_map['filename'] lineno = int(args[0]) else: raise SystemExit(\"Invalid format for location.\") str_context = context.render_file_context(entries, options_map, search_filename, lineno) sys.stdout.write(str_context)","title":"do_context()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_deps","text":"Report on the runtime dependencies. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_deps(*unused_args): \"\"\"Report on the runtime dependencies. Args: unused_args: Ignored. \"\"\" from beancount.scripts import deps deps.list_dependencies(sys.stdout) print('') print('Use \"pip3 install \" to install new packages.')","title":"do_deps()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_directories","text":"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Parameters: filename \u2013 A string, the Beancount input filename. args \u2013 The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. Source code in beancount/scripts/doctor.py def do_directories(filename, args): \"\"\"Validate a directory hierarchy against a ledger's account names. Read a ledger's list of account names and check that all the capitalized subdirectory names under the given roots match the account names. Args: filename: A string, the Beancount input filename. args: The rest of the arguments provided on the command-line, which in this case will be interpreted as the names of root directories to validate against the accounts in the given ledger. \"\"\" from beancount import loader from beancount.scripts import directories entries, _, __ = loader.load_file(filename) directories.validate_directories(entries, args)","title":"do_directories()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_display_context","text":"Print out the precision inferred from the parsed numbers in the input file. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_display_context(filename, args): \"\"\"Print out the precision inferred from the parsed numbers in the input file. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount import loader entries, errors, options_map = loader.load_file(filename) dcontext = options_map['dcontext'] sys.stdout.write(str(dcontext))","title":"do_display_context()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_dump_lexer","text":"Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text)))","title":"do_dump_lexer()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_lex","text":"Dump the lexer output for a Beancount syntax file. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_lex(filename, unused_args): \"\"\"Dump the lexer output for a Beancount syntax file. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import lexer for token, lineno, text, obj in lexer.lex_iter(filename): sys.stdout.write('{:12} {:6d} {}\\n'.format( '(None)' if token is None else token, lineno, repr(text)))","title":"do_lex()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_linked","text":"Print out a list of transactions linked to the one at the given line. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_linked(filename, args): \"\"\"Print out a list of transactions linked to the one at the given line. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import options from beancount.parser import printer from beancount.core import account_types from beancount.core import inventory from beancount.core import data from beancount.core import realization from beancount import loader # Parse the arguments, get the line number. if len(args) != 1: raise SystemExit(\"Missing line number argument.\") lineno = int(args[0]) # Load the input file. entries, errors, options_map = loader.load_file(filename) # Find the closest entry. closest_entry = data.find_closest(entries, options_map['filename'], lineno) # Find its links. if closest_entry is None: raise SystemExit(\"No entry could be found before {}:{}\".format(filename, lineno)) links = (closest_entry.links if isinstance(closest_entry, data.Transaction) else data.EMPTY_SET) if not links: linked_entries = [closest_entry] else: # Find all linked entries. # # Note that there is an option here: You can either just look at the links # on the closest entry, or you can include the links of the linked # transactions as well. Whichever one you want depends on how you use your # links. Best would be to query the user (in Emacs) when there are many # links present. follow_links = True if not follow_links: linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] else: links = set(links) linked_entries = [] while True: num_linked = len(linked_entries) linked_entries = [entry for entry in entries if (isinstance(entry, data.Transaction) and entry.links and entry.links & links)] if len(linked_entries) == num_linked: break for entry in linked_entries: if entry.links: links.update(entry.links) # Render linked entries (in date order) as errors (for Emacs). errors = [RenderError(entry.meta, '', entry) for entry in linked_entries] printer.print_errors(errors) # Print out balances. real_root = realization.realize(linked_entries) dformat = options_map['dcontext'].build(alignment=display_context.Align.DOT, reserved=2) realization.dump_balances(real_root, dformat, file=sys.stdout) # Print out net income change. acctypes = options.get_account_types(options_map) net_income = inventory.Inventory() for real_node in realization.iter_children(real_root): if account_types.is_income_statement_account(real_node.account, acctypes): net_income.add_inventory(real_node.balance) print() print('Net Income: {}'.format(-net_income))","title":"do_linked()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_list_options","text":"Print out a list of the available options. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_list_options(*unused_args): \"\"\"Print out a list of the available options. Args: unused_args: Ignored. \"\"\" from beancount.parser import options print(options.list_options())","title":"do_list_options()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_missing_open","text":"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Parameters: filename \u2013 A string, which consists in the filename. args \u2013 A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. Source code in beancount/scripts/doctor.py def do_missing_open(filename, args): \"\"\"Print out Open directives that are missing for the given input file. This can be useful during demos in order to quickly generate all the required Open directives without having to type them manually. Args: filename: A string, which consists in the filename. args: A tuple of the rest of arguments. We're expecting the first argument to be an integer as a string. \"\"\" from beancount.parser import printer from beancount.core import data from beancount.core import getters from beancount import loader entries, errors, options_map = loader.load_file(filename) # Get accounts usage and open directives. first_use_map, _ = getters.get_accounts_use_map(entries) open_close_map = getters.get_account_open_close(entries) new_entries = [] for account, first_use_date in first_use_map.items(): if account not in open_close_map: new_entries.append( data.Open(data.new_metadata(filename, 0), first_use_date, account, None, None)) dcontext = options_map['dcontext'] printer.print_entries(data.sorted(new_entries), dcontext)","title":"do_missing_open()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_parse","text":"Run the parser in debug mode. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_parse(filename, unused_args): \"\"\"Run the parser in debug mode. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import parser entries, errors, _ = parser.parse_file(filename, yydebug=1)","title":"do_parse()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_print_options","text":"Print out the actual options parsed from a file. Parameters: unused_args \u2013 Ignored. Source code in beancount/scripts/doctor.py def do_print_options(filename, *args): \"\"\"Print out the actual options parsed from a file. Args: unused_args: Ignored. \"\"\" from beancount import loader _, __, options_map = loader.load_file(filename) for key, value in sorted(options_map.items()): print('{}: {}'.format(key, value))","title":"do_print_options()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_roundtrip","text":"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Parameters: filename \u2013 A string, the Beancount input filename. Source code in beancount/scripts/doctor.py def do_roundtrip(filename, unused_args): \"\"\"Round-trip test on arbitrary Ledger. Read a Ledger's transactions, print them out, re-read them again and compare them. Both sets of parsed entries should be equal. Both printed files are output to disk, so you can also run diff on them yourself afterwards. Args: filename: A string, the Beancount input filename. \"\"\" from beancount.parser import printer from beancount.core import compare from beancount import loader round1_filename = round2_filename = None try: logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s') logging.info(\"Read the entries\") entries, errors, options_map = loader.load_file(filename) printer.print_errors(errors, file=sys.stderr) logging.info(\"Print them out to a file\") basename, extension = path.splitext(filename) round1_filename = ''.join([basename, '.roundtrip1', extension]) with open(round1_filename, 'w') as outfile: printer.print_entries(entries, file=outfile) logging.info(\"Read the entries from that file\") # Note that we don't want to run any of the auto-generation here, but # parsing now returns incomplete objects and we assume idempotence on a # file that was output from the printer after having been processed, so # it shouldn't add anything new. That is, a processed file printed and # resolve when parsed again should contain the same entries, i.e. # nothing new should be generated. entries_roundtrip, errors, options_map = loader.load_file(round1_filename) # Print out the list of errors from parsing the results. if errors: print(',----------------------------------------------------------------------') printer.print_errors(errors, file=sys.stdout) print('`----------------------------------------------------------------------') logging.info(\"Print what you read to yet another file\") round2_filename = ''.join([basename, '.roundtrip2', extension]) with open(round2_filename, 'w') as outfile: printer.print_entries(entries_roundtrip, file=outfile) logging.info(\"Compare the original entries with the re-read ones\") same, missing1, missing2 = compare.compare_entries(entries, entries_roundtrip) if same: logging.info('Entries are the same. Congratulations.') else: logging.error('Entries differ!') print() print('\\n\\nMissing from original:') for entry in entries: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() print('\\n\\nMissing from round-trip:') for entry in missing2: print(entry) print(compare.hash_entry(entry)) print(printer.format_entry(entry)) print() finally: for rfilename in (round1_filename, round2_filename): if path.exists(rfilename): os.remove(rfilename)","title":"do_roundtrip()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.do_validate_html","text":"Validate all the HTML files under a directory hierarchy. Parameters: directory \u2013 A string, the root directory whose contents to validate. args \u2013 A tuple of the rest of arguments. Source code in beancount/scripts/doctor.py def do_validate_html(directory, args): \"\"\"Validate all the HTML files under a directory hierarchy. Args: directory: A string, the root directory whose contents to validate. args: A tuple of the rest of arguments. \"\"\" from beancount.web import scrape files, missing, empty = scrape.validate_local_links_in_dir(directory) logging.info('%d files processed', len(files)) for target in missing: logging.error('Missing %s', target) for target in empty: logging.error('Empty %s', target)","title":"do_validate_html()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.doctor.get_commands","text":"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). Source code in beancount/scripts/doctor.py def get_commands(): \"\"\"Return a list of available commands in this file. Returns: A list of pairs of (command-name string, docstring). \"\"\" commands = [] for attr_name, attr_value in globals().items(): match = re.match('do_(.*)', attr_name) if match: commands.append((match.group(1), misc_utils.first_paragraph(attr_value.__doc__))) return commands","title":"get_commands()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example","text":"Generate a decently-sized example history, based on some rules. This script is used to generate some meaningful input to Beancount, input that looks as realistic as possible for a moderately complex mock individual. This can also be used as an input generator for a stress test for performance evaluation.","title":"example"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.check_non_negative","text":"Check that the balance of the given account never goes negative. Parameters: entries \u2013 A list of directives. account \u2013 An account string, the account to check the balance for. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 if the balance goes negative. Source code in beancount/scripts/example.py def check_non_negative(entries, account, currency): \"\"\"Check that the balance of the given account never goes negative. Args: entries: A list of directives. account: An account string, the account to check the balance for. currency: A string, the currency to check minimums for. Raises: AssertionError: if the balance goes negative. \"\"\" previous_date = None for txn_posting, balances in postings_for(data.sorted(entries), [account], before=True): balance = balances[account] date = txn_posting.txn.date if date != previous_date: assert all(pos.units.number >= ZERO for pos in balance.get_positions()), ( \"Negative balance: {} at: {}\".format(balance, txn_posting.txn.date)) previous_date = date","title":"check_non_negative()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.compute_trip_dates","text":"Generate dates at reasonable intervals for trips during the given time period. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of dates for the trips within the period. Source code in beancount/scripts/example.py def compute_trip_dates(date_begin, date_end): \"\"\"Generate dates at reasonable intervals for trips during the given time period. Args: date_begin: The start date. date_end: The end date. Yields: Pairs of dates for the trips within the period. \"\"\" # Min and max number of days remaining at home. days_at_home = (4*30, 13*30) # Length of trip. days_trip = (8, 22) # Number of days to ensure no trip at the beginning and the end. days_buffer = 21 date_begin += datetime.timedelta(days=days_buffer) date_end -= datetime.timedelta(days=days_buffer) date = date_begin while 1: duration_at_home = datetime.timedelta(days=random.randint(*days_at_home)) duration_trip = datetime.timedelta(days=random.randint(*days_trip)) date_trip_begin = date + duration_at_home date_trip_end = date_trip_begin + duration_trip if date_trip_end >= date_end: break yield (date_trip_begin, date_trip_end) date = date_trip_end","title":"compute_trip_dates()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.contextualize_file","text":"Replace generic strings in the generated file with realistic strings. Parameters: contents \u2013 A string, the generic file contents. Returns: A string, the contextualized version. Source code in beancount/scripts/example.py def contextualize_file(contents, employer): \"\"\"Replace generic strings in the generated file with realistic strings. Args: contents: A string, the generic file contents. Returns: A string, the contextualized version. \"\"\" replacements = { 'CC': 'US', 'Bank1': 'BofA', 'Bank1_Institution': 'Bank of America', 'Bank1_Address': '123 America Street, LargeTown, USA', 'Bank1_Phone': '+1.012.345.6789', 'CreditCard1': 'Chase:Slate', 'CreditCard2': 'Amex:BlueCash', 'Employer1': employer, 'Retirement': 'Vanguard', 'Retirement_Institution': 'Vanguard Group', 'Retirement_Address': \"P.O. Box 1110, Valley Forge, PA 19482-1110\", 'Retirement_Phone': \"+1.800.523.1188\", 'Investment': 'ETrade', # Commodities 'CCY': 'USD', 'VACHR': 'VACHR', 'DEFCCY': 'IRAUSD', 'MFUND1': 'VBMPX', 'MFUND2': 'RGAGX', 'STK1': 'ITOT', 'STK2': 'VEA', 'STK3': 'VHT', 'STK4': 'GLD', } new_contents = replace(contents, replacements) return new_contents, replacements","title":"contextualize_file()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.date_iter","text":"Generate a sequence of dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_iter(date_begin, date_end): \"\"\"Generate a sequence of dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date. \"\"\" assert date_begin <= date_end date = date_begin one_day = datetime.timedelta(days=1) while date < date_end: date += one_day yield date","title":"date_iter()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.date_random_seq","text":"Generate a sequence of dates with some random increase in days. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. days_min \u2013 The minimum number of days to advance on each iteration. days_max \u2013 The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. Source code in beancount/scripts/example.py def date_random_seq(date_begin, date_end, days_min, days_max): \"\"\"Generate a sequence of dates with some random increase in days. Args: date_begin: The start date. date_end: The end date. days_min: The minimum number of days to advance on each iteration. days_max: The maximum number of days to advance on each iteration. Yields: Instances of datetime.date. \"\"\" assert days_min > 0 assert days_min <= days_max date = date_begin while date < date_end: nb_days_forward = random.randint(days_min, days_max) date += datetime.timedelta(days=nb_days_forward) if date >= date_end: break yield date","title":"date_random_seq()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.delay_dates","text":"Delay the dates from the given iterator by some uniformly drawn number of days. Parameters: date_iter \u2013 An iterator of datetime.date instances. delay_days_min \u2013 The minimum amount of advance days for the transaction. delay_days_max \u2013 The maximum amount of advance days for the transaction. Yields: datetime.date instances. Source code in beancount/scripts/example.py def delay_dates(date_iter, delay_days_min, delay_days_max): \"\"\"Delay the dates from the given iterator by some uniformly drawn number of days. Args: date_iter: An iterator of datetime.date instances. delay_days_min: The minimum amount of advance days for the transaction. delay_days_max: The maximum amount of advance days for the transaction. Yields: datetime.date instances. \"\"\" dates = list(date_iter) last_date = dates[-1] last_date = last_date.date() if isinstance(last_date, datetime.datetime) else last_date for dtime in dates: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime date += datetime.timedelta(days=random.randint(delay_days_min, delay_days_max)) if date >= last_date: break yield date","title":"delay_dates()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_balance_checks","text":"Generate balance check entries to the given frequency. Parameters: entries \u2013 A list of directives that contain all the transactions for the accounts. account \u2013 The name of the account for which to generate. date_iter \u2013 Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. Source code in beancount/scripts/example.py def generate_balance_checks(entries, account, date_iter): \"\"\"Generate balance check entries to the given frequency. Args: entries: A list of directives that contain all the transactions for the accounts. account: The name of the account for which to generate. date_iter: Iterator of dates. We generate balance checks at these dates. Returns: A list of balance check entries. \"\"\" balance_checks = [] date_iter = iter(date_iter) next_date = next(date_iter) with misc_utils.swallow(StopIteration): for txn_posting, balance in postings_for(entries, [account], before=True): while txn_posting.txn.date >= next_date: amount = balance[account].get_currency_units('CCY').number balance_checks.extend(parse(\"\"\" {next_date} balance {account} {amount} CCY \"\"\", **locals())) next_date = next(date_iter) return balance_checks","title":"generate_balance_checks()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_banking","text":"Generate a checking account opening. Parameters: entries \u2013 A list of entries which affect this account. date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. amount_initial \u2013 A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking(entries, date_begin, date_end, amount_initial): \"\"\"Generate a checking account opening. Args: entries: A list of entries which affect this account. date_begin: A date instance, the beginning date. date_end: A date instance, the end date. amount_initial: A Decimal instance, the amount to initialize the checking account with. Returns: A list of directives. \"\"\" amount_initial_neg = -amount_initial new_entries = parse(\"\"\" {date_begin} open Assets:CC:Bank1 institution: \"Bank1_Institution\" address: \"Bank1_Address\" phone: \"Bank1_Phone\" {date_begin} open Assets:CC:Bank1:Checking CCY account: \"00234-48574897\" ;; {date_begin} open Assets:CC:Bank1:Savings CCY {date_begin} * \"Opening Balance for checking account\" Assets:CC:Bank1:Checking {amount_initial} CCY Equity:Opening-Balances {amount_initial_neg} CCY \"\"\", **locals()) date_balance = date_begin + datetime.timedelta(days=1) account = 'Assets:CC:Bank1:Checking' for txn_posting, balances in postings_for(data.sorted(entries + new_entries), [account], before=True): if txn_posting.txn.date >= date_balance: break amount_balance = balances[account].get_currency_units('CCY').number bal_entries = parse(\"\"\" {date_balance} balance Assets:CC:Bank1:Checking {amount_balance} CCY \"\"\", **locals()) return new_entries + bal_entries","title":"generate_banking()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_banking_expenses","text":"Generate expenses paid out of a checking account, typically living expenses. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. account \u2013 The checking account to generate expenses to. rent_amount \u2013 The amount of rent. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_banking_expenses(date_begin, date_end, account, rent_amount): \"\"\"Generate expenses paid out of a checking account, typically living expenses. Args: date_begin: The start date. date_end: The end date. account: The checking account to generate expenses to. rent_amount: The amount of rent. Returns: A list of directives. \"\"\" fee_expenses = generate_periodic_expenses( rrule.rrule(rrule.MONTHLY, bymonthday=4, dtstart=date_begin, until=date_end), \"BANK FEES\", \"Monthly bank fee\", account, 'Expenses:Financial:Fees', lambda: D('4.00')) rent_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 2, 5), \"RiverBank Properties\", \"Paying the rent\", account, 'Expenses:Home:Rent', lambda: random.normalvariate(float(rent_amount), 0)) electricity_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 7, 8), \"EDISON POWER\", \"\", account, 'Expenses:Home:Electricity', lambda: random.normalvariate(65, 0)) internet_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 20, 22), \"Wine-Tarner Cable\", \"\", account, 'Expenses:Home:Internet', lambda: random.normalvariate(80, 0.10)) phone_expenses = generate_periodic_expenses( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end), 17, 19), \"Verizon Wireless\", \"\", account, 'Expenses:Home:Phone', lambda: random.normalvariate(60, 10)) return data.sorted(fee_expenses + rent_expenses + electricity_expenses + internet_expenses + phone_expenses)","title":"generate_banking_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_clearing_entries","text":"Generate entries to clear the value of an account. Parameters: date_iter \u2013 An iterator of datetime.date instances. payee \u2013 A string, the payee name to use on the transactions. narration \u2013 A string, the narration to use on the transactions. entries \u2013 A list of entries. account_clear \u2013 The account to clear. account_from \u2013 The source account to clear 'account_clear' from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_clearing_entries(date_iter, payee, narration, entries, account_clear, account_from): \"\"\"Generate entries to clear the value of an account. Args: date_iter: An iterator of datetime.date instances. payee: A string, the payee name to use on the transactions. narration: A string, the narration to use on the transactions. entries: A list of entries. account_clear: The account to clear. account_from: The source account to clear 'account_clear' from. Returns: A list of directives. \"\"\" # The next date we're looking for. next_date = next(iter(date_iter)) # Iterate over all the postings of the account to clear. new_entries = [] for txn_posting, balances in postings_for(entries, [account_clear]): balance_clear = balances[account_clear] # Check if we need to clear. if next_date <= txn_posting.txn.date: pos_amount = balance_clear.get_currency_units('CCY') neg_amount = -pos_amount new_entries.extend(parse(\"\"\" {next_date} * \"{payee}\" \"{narration}\" {account_clear} {neg_amount.number:.2f} CCY {account_from} {pos_amount.number:.2f} CCY \"\"\", **locals())) balance_clear.add_amount(neg_amount) # Advance to the next date we're looking for. try: next_date = next(iter(date_iter)) except StopIteration: break return new_entries","title":"generate_clearing_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_commodity_entries","text":"Create a list of Commodity entries for all the currencies we're using. Parameters: date_birth \u2013 A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. Source code in beancount/scripts/example.py def generate_commodity_entries(date_birth): \"\"\"Create a list of Commodity entries for all the currencies we're using. Args: date_birth: A datetime.date instance, the date of birth of the user. Returns: A list of Commodity entries for all the commodities in use. \"\"\" return parse(\"\"\" 1792-01-01 commodity USD name: \"US Dollar\" export: \"CASH\" {date_birth} commodity VACHR name: \"Employer Vacation Hours\" export: \"IGNORE\" {date_birth} commodity IRAUSD name: \"US 401k and IRA Contributions\" export: \"IGNORE\" 2009-05-01 commodity RGAGX name: \"American Funds The Growth Fund of America Class R-6\" export: \"MUTF:RGAGX\" price: \"USD:google/MUTF:RGAGX\" 1995-09-18 commodity VBMPX name: \"Vanguard Total Bond Market Index Fund Institutional Plus Shares\" export: \"MUTF:VBMPX\" price: \"USD:google/MUTF:VBMPX\" 2004-01-20 commodity ITOT name: \"iShares Core S&P Total U.S. Stock Market ETF\" export: \"NYSEARCA:ITOT\" price: \"USD:google/NYSEARCA:ITOT\" 2007-07-20 commodity VEA name: \"Vanguard FTSE Developed Markets ETF\" export: \"NYSEARCA:VEA\" price: \"USD:google/NYSEARCA:VEA\" 2004-01-26 commodity VHT name: \"Vanguard Health Care ETF\" export: \"NYSEARCA:VHT\" price: \"USD:google/NYSEARCA:VHT\" 2004-11-01 commodity GLD name: \"SPDR Gold Trust (ETF)\" export: \"NYSEARCA:GLD\" price: \"USD:google/NYSEARCA:GLD\" 1900-01-01 commodity VMMXX export: \"MUTF:VMMXX (MONEY:USD)\" \"\"\", **locals())","title":"generate_commodity_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_employment_income","text":"Generate bi-weekly entries for payroll salary income. Parameters: employer_name \u2013 A string, the human-readable name of the employer. employer_address \u2013 A string, the address of the employer. annual_salary \u2013 A Decimal, the annual salary of the employee. account_deposit \u2013 An account string, the account to deposit the salary to. account_retirement \u2013 An account string, the account to deposit retirement contributions to. date_begin \u2013 The start date. date_end \u2013 The end date. Returns: A list of directives, including open directives for the account. Source code in beancount/scripts/example.py def generate_employment_income(employer_name, employer_address, annual_salary, account_deposit, account_retirement, date_begin, date_end): \"\"\"Generate bi-weekly entries for payroll salary income. Args: employer_name: A string, the human-readable name of the employer. employer_address: A string, the address of the employer. annual_salary: A Decimal, the annual salary of the employee. account_deposit: An account string, the account to deposit the salary to. account_retirement: An account string, the account to deposit retirement contributions to. date_begin: The start date. date_end: The end date. Returns: A list of directives, including open directives for the account. \"\"\" preamble = parse(\"\"\" {date_begin} event \"employer\" \"{employer_name}, {employer_address}\" {date_begin} open Income:CC:Employer1:Salary CCY ;{date_begin} open Income:CC:Employer1:AnnualBonus CCY {date_begin} open Income:CC:Employer1:GroupTermLife CCY {date_begin} open Income:CC:Employer1:Vacation VACHR {date_begin} open Assets:CC:Employer1:Vacation VACHR {date_begin} open Expenses:Vacation VACHR {date_begin} open Expenses:Health:Life:GroupTermLife {date_begin} open Expenses:Health:Medical:Insurance {date_begin} open Expenses:Health:Dental:Insurance {date_begin} open Expenses:Health:Vision:Insurance ;{date_begin} open Expenses:Vacation:Employer \"\"\", **locals()) date_prev = None contrib_retirement = ZERO contrib_socsec = ZERO biweekly_pay = annual_salary / 26 gross = biweekly_pay medicare = gross * D('0.0231') federal = gross * D('0.2303') state = gross * D('0.0791') city = gross * D('0.0379') sdi = D('1.12') lifeinsurance = D('24.32') dental = D('2.90') medical = D('27.38') vision = D('42.30') fixed = (medicare + federal + state + city + sdi + dental + medical + vision) # Calculate vacation hours per-pay. with decimal.localcontext() as ctx: ctx.prec = 4 vacation_hrs = (ANNUAL_VACATION_DAYS * D('8')) / D('26') transactions = [] for dtime in misc_utils.skipiter( rrule.rrule(rrule.WEEKLY, byweekday=rrule.TH, dtstart=date_begin, until=date_end), 2): date = dtime.date() year = date.year if not date_prev or date_prev.year != date.year: contrib_retirement = RETIREMENT_LIMITS.get(date.year, RETIREMENT_LIMITS[None]) contrib_socsec = D('7000') date_prev = date retirement_uncapped = math.ceil((gross * D('0.25')) / 100) * 100 retirement = min(contrib_retirement, retirement_uncapped) contrib_retirement -= retirement socsec_uncapped = gross * D('0.0610') socsec = min(contrib_socsec, socsec_uncapped) contrib_socsec -= socsec with decimal.localcontext() as ctx: ctx.prec = 6 deposit = (gross - retirement - fixed - socsec) retirement_neg = -retirement gross_neg = -gross lifeinsurance_neg = -lifeinsurance vacation_hrs_neg = -vacation_hrs template = \"\"\" {date} * \"{employer_name}\" \"Payroll\" {account_deposit} {deposit:.2f} CCY {account_retirement} {retirement:.2f} CCY Assets:CC:Federal:PreTax401k {retirement_neg:.2f} DEFCCY Expenses:Taxes:Y{year}:CC:Federal:PreTax401k {retirement:.2f} DEFCCY Income:CC:Employer1:Salary {gross_neg:.2f} CCY Income:CC:Employer1:GroupTermLife {lifeinsurance_neg:.2f} CCY Expenses:Health:Life:GroupTermLife {lifeinsurance:.2f} CCY Expenses:Health:Dental:Insurance {dental} CCY Expenses:Health:Medical:Insurance {medical} CCY Expenses:Health:Vision:Insurance {vision} CCY Expenses:Taxes:Y{year}:CC:Medicare {medicare:.2f} CCY Expenses:Taxes:Y{year}:CC:Federal {federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {state:.2f} CCY Expenses:Taxes:Y{year}:CC:CityNYC {city:.2f} CCY Expenses:Taxes:Y{year}:CC:SDI {sdi:.2f} CCY Expenses:Taxes:Y{year}:CC:SocSec {socsec:.2f} CCY Assets:CC:Employer1:Vacation {vacation_hrs:.2f} VACHR Income:CC:Employer1:Vacation {vacation_hrs_neg:.2f} VACHR \"\"\" if retirement == ZERO: # Remove retirement lines. template = '\\n'.join(line for line in template.splitlines() if not re.search(r'\\bretirement\\b', line)) transactions.extend(parse(template, **locals())) return preamble + transactions","title":"generate_employment_income()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_expense_accounts","text":"Generate directives for expense accounts. Parameters: date_birth \u2013 Birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_expense_accounts(date_birth): \"\"\"Generate directives for expense accounts. Args: date_birth: Birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" {date_birth} open Expenses:Food:Groceries {date_birth} open Expenses:Food:Restaurant {date_birth} open Expenses:Food:Coffee {date_birth} open Expenses:Food:Alcohol {date_birth} open Expenses:Transport:Tram {date_birth} open Expenses:Home:Rent {date_birth} open Expenses:Home:Electricity {date_birth} open Expenses:Home:Internet {date_birth} open Expenses:Home:Phone {date_birth} open Expenses:Financial:Fees {date_birth} open Expenses:Financial:Commissions \"\"\", **locals())","title":"generate_expense_accounts()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_open_entries","text":"Generate a list of Open entries for the given accounts: Parameters: date \u2013 A datetime.date instance for the open entries. accounts \u2013 A list of account strings. currency \u2013 An optional currency constraint. Returns: A list of Open directives. Source code in beancount/scripts/example.py def generate_open_entries(date, accounts, currency=None): \"\"\"Generate a list of Open entries for the given accounts: Args: date: A datetime.date instance for the open entries. accounts: A list of account strings. currency: An optional currency constraint. Returns: A list of Open directives. \"\"\" assert isinstance(accounts, (list, tuple)) return parse(''.join( '{date} open {account} {currency}\\n'.format(date=date, account=account, currency=currency or '') for account in accounts))","title":"generate_open_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_outgoing_transfers","text":"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Parameters: entries \u2013 A list of existing entries that affect this account so far. The generated entries will also affect this account. account \u2013 An account string, the account to monitor. account_out \u2013 An account string, the savings account to make transfers to. transfer_minimum \u2013 The minimum amount of funds to always leave in this account after a transfer. transfer_threshold \u2013 The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment \u2013 A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. Source code in beancount/scripts/example.py def generate_outgoing_transfers(entries, account, account_out, transfer_minimum, transfer_threshold, transfer_increment): \"\"\"Generate transfers of accumulated funds out of an account. This monitors the balance of an account and when it is beyond a threshold, generate out transfers form that account to another account. Args: entries: A list of existing entries that affect this account so far. The generated entries will also affect this account. account: An account string, the account to monitor. account_out: An account string, the savings account to make transfers to. transfer_minimum: The minimum amount of funds to always leave in this account after a transfer. transfer_threshold: The minimum amount of funds to be able to transfer out without breaking the minimum. transfer_increment: A Decimal, the increment to round transfers to. Returns: A list of new directives, the transfers to add to the given account. \"\"\" last_date = entries[-1].date # Reverse the balance amounts taking into account the minimum balance for # all time in the future. amounts = [(balances[account].get_currency_units('CCY').number, txn_posting) for txn_posting, balances in postings_for(entries, [account])] reversed_amounts = [] last_amount, _ = amounts[-1] for current_amount, _ in reversed(amounts): if current_amount < last_amount: reversed_amounts.append(current_amount) last_amount = current_amount else: reversed_amounts.append(last_amount) capped_amounts = reversed(reversed_amounts) # Create transfers outward where the future allows it. new_entries = [] offset_amount = ZERO for current_amount, (_, txn_posting) in zip(capped_amounts, amounts): if txn_posting.txn.date >= last_date: break adjusted_amount = current_amount - offset_amount if adjusted_amount > (transfer_minimum + transfer_threshold): amount_transfer = round_to(adjusted_amount - transfer_minimum, transfer_increment) date = txn_posting.txn.date + datetime.timedelta(days=1) amount_transfer_neg = -amount_transfer new_entries.extend(parse(\"\"\" {date} * \"Transfering accumulated savings to other account\" {account} {amount_transfer_neg:2f} CCY {account_out} {amount_transfer:2f} CCY \"\"\", **locals())) offset_amount += amount_transfer return new_entries","title":"generate_outgoing_transfers()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_periodic_expenses","text":"Generate periodic expense transactions. Parameters: date_iter \u2013 An iterator for dates or datetimes. payee \u2013 A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration \u2013 A string, the narration to use on the transactions. account_from \u2013 An account string the debited account. account_to \u2013 An account string the credited account. amount_generator \u2013 A callable object to generate variates. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_periodic_expenses(date_iter, payee, narration, account_from, account_to, amount_generator): \"\"\"Generate periodic expense transactions. Args: date_iter: An iterator for dates or datetimes. payee: A string, the payee name to use on the transactions, or a set of such strings to randomly choose from narration: A string, the narration to use on the transactions. account_from: An account string the debited account. account_to: An account string the credited account. amount_generator: A callable object to generate variates. Returns: A list of directives. \"\"\" new_entries = [] for dtime in date_iter: date = dtime.date() if isinstance(dtime, datetime.datetime) else dtime amount = D(amount_generator()) txn_payee = (payee if isinstance(payee, str) else random.choice(payee)) txn_narration = (narration if isinstance(narration, str) else random.choice(narration)) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{txn_payee}\" \"{txn_narration}\" {account_from} {amount_neg:.2f} CCY {account_to} {amount:.2f} CCY \"\"\", **locals())) return new_entries","title":"generate_periodic_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_prices","text":"Generate weekly or monthly price entries for the given currencies. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. currencies \u2013 A list of currency strings to generate prices for. cost_currency \u2013 A string, the cost currency. Returns: A list of Price directives. Source code in beancount/scripts/example.py def generate_prices(date_begin, date_end, currencies, cost_currency): \"\"\"Generate weekly or monthly price entries for the given currencies. Args: date_begin: The start date. date_end: The end date. currencies: A list of currency strings to generate prices for. cost_currency: A string, the cost currency. Returns: A list of Price directives. \"\"\" digits = D('0.01') entries = [] counter = itertools.count() for currency in currencies: start_price = random.uniform(30, 200) growth = random.uniform(0.02, 0.13) # %/year mu = growth * (7 / 365) sigma = random.uniform(0.005, 0.02) # Vol for dtime, price_float in zip(rrule.rrule(rrule.WEEKLY, byweekday=rrule.FR, dtstart=date_begin, until=date_end), price_series(start_price, mu, sigma)): price = D(price_float).quantize(digits) meta = data.new_metadata(generate_prices.__name__, next(counter)) entry = data.Price(meta, dtime.date(), currency, amount.Amount(price, cost_currency)) entries.append(entry) return entries","title":"generate_prices()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_regular_credit_expenses","text":"Generate expenses paid out of a credit card account, including payments to the credit card. Parameters: date_birth \u2013 The user's birth date. date_begin \u2013 The start date. date_end \u2013 The end date. account_credit \u2013 The credit card account to generate expenses against. account_checking \u2013 The checking account to generate payments from. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_regular_credit_expenses(date_birth, date_begin, date_end, account_credit, account_checking): \"\"\"Generate expenses paid out of a credit card account, including payments to the credit card. Args: date_birth: The user's birth date. date_begin: The start date. date_end: The end date. account_credit: The credit card account to generate expenses against. account_checking: The checking account to generate payments from. Returns: A list of directives. \"\"\" restaurant_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 1, 5), RESTAURANT_NAMES, RESTAURANT_NARRATIONS, account_credit, 'Expenses:Food:Restaurant', lambda: min(random.lognormvariate(math.log(30), math.log(1.5)), random.randint(200, 220))) groceries_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 5, 20), GROCERIES_NAMES, \"Buying groceries\", account_credit, 'Expenses:Food:Groceries', lambda: min(random.lognormvariate(math.log(80), math.log(1.3)), random.randint(250, 300))) subway_expenses = generate_periodic_expenses( date_random_seq(date_begin, date_end, 27, 33), \"Metro Transport Authority\", \"Tram tickets\", account_credit, 'Expenses:Transport:Tram', lambda: D('120.00')) credit_expenses = data.sorted(restaurant_expenses + groceries_expenses + subway_expenses) # Entries to open accounts. credit_preamble = generate_open_entries(date_birth, [account_credit], 'CCY') return data.sorted(credit_preamble + credit_expenses)","title":"generate_regular_credit_expenses()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_retirement_employer_match","text":"Generate employer matching contributions into a retirement account. Parameters: entries \u2013 A list of directives that cover the retirement account. account_invest \u2013 The name of the retirement cash account. account_income \u2013 The name of the income account. Returns: A list of new entries generated for employer contributions. Source code in beancount/scripts/example.py def generate_retirement_employer_match(entries, account_invest, account_income): \"\"\"Generate employer matching contributions into a retirement account. Args: entries: A list of directives that cover the retirement account. account_invest: The name of the retirement cash account. account_income: The name of the income account. Returns: A list of new entries generated for employer contributions. \"\"\" match_frac = D('0.50') new_entries = parse(\"\"\" {date} open {account_income} CCY \"\"\", date=entries[0].date, account_income=account_income) for txn_posting, balances in postings_for(entries, [account_invest]): amount = txn_posting.posting.units.number * match_frac amount_neg = -amount date = txn_posting.txn.date + ONE_DAY new_entries.extend(parse(\"\"\" {date} * \"Employer match for contribution\" {account_invest} {amount:.2f} CCY {account_income} {amount_neg:.2f} CCY \"\"\", **locals())) return new_entries","title":"generate_retirement_employer_match()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_retirement_investments","text":"Invest money deposited to the given retirement account. Parameters: entries \u2013 A list of directives account \u2013 The root account for all retirement investment sub-accounts. commodities_items \u2013 A list of (commodity, fraction to be invested in) items. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. Source code in beancount/scripts/example.py def generate_retirement_investments(entries, account, commodities_items, price_map): \"\"\"Invest money deposited to the given retirement account. Args: entries: A list of directives account: The root account for all retirement investment sub-accounts. commodities_items: A list of (commodity, fraction to be invested in) items. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). Returns: A list of new directives for the given investments. This also generates account opening directives for the desired investment commodities. \"\"\" open_entries = [] account_cash = join(account, 'Cash') date_origin = entries[0].date open_entries.extend(parse(\"\"\" {date_origin} open {account} CCY institution: \"Retirement_Institution\" address: \"Retirement_Address\" phone: \"Retirement_Phone\" {date_origin} open {account_cash} CCY number: \"882882\" \"\"\", **locals())) for currency, _ in commodities_items: open_entries.extend(parse(\"\"\" {date_origin} open {account}:{currency} {currency} number: \"882882\" \"\"\", **locals())) new_entries = [] for txn_posting, balances in postings_for(entries, [account_cash]): balance = balances[account_cash] amount_to_invest = balance.get_currency_units('CCY').number # Find the date the following Monday, the date to invest. txn_date = txn_posting.txn.date while txn_date.weekday() != calendar.MONDAY: txn_date += ONE_DAY amount_invested = ZERO for commodity, fraction in commodities_items: amount_fraction = amount_to_invest * D(fraction) # Find the price at that date. _, price = prices.get_price(price_map, (commodity, 'CCY'), txn_date) units = (amount_fraction / price).quantize(D('0.001')) amount_cash = (units * price).quantize(D('0.01')) amount_cash_neg = -amount_cash new_entries.extend(parse(\"\"\" {txn_date} * \"Investing {fraction:.0%} of cash in {commodity}\" {account}:{commodity} {units:.3f} {commodity} {{{price:.2f} CCY}} {account}:Cash {amount_cash_neg:.2f} CCY \"\"\", **locals())) balance.add_amount(amount.Amount(-amount_cash, 'CCY')) return data.sorted(open_entries + new_entries)","title":"generate_retirement_investments()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_tax_accounts","text":"Generate accounts and contribution directives for a particular tax year. Parameters: year \u2013 An integer, the year we're to generate this for. date_max \u2013 The maximum date to produce an entry for. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_accounts(year, date_max): \"\"\"Generate accounts and contribution directives for a particular tax year. Args: year: An integer, the year we're to generate this for. date_max: The maximum date to produce an entry for. Returns: A list of directives. \"\"\" date_year = datetime.date(year, 1, 1) date_filing = (datetime.date(year + 1, 3, 20) + datetime.timedelta(days=random.randint(0, 5))) date_federal = (date_filing + datetime.timedelta(days=random.randint(0, 4))) date_state = (date_filing + datetime.timedelta(days=random.randint(0, 4))) quantum = D('0.01') amount_federal = D(max(random.normalvariate(500, 120), 12)).quantize(quantum) amount_federal_neg = -amount_federal amount_state = D(max(random.normalvariate(300, 100), 10)).quantize(quantum) amount_state_neg = -amount_state amount_payable = -(amount_federal + amount_state) amount_limit = RETIREMENT_LIMITS.get(year, RETIREMENT_LIMITS[None]) amount_limit_neg = -amount_limit entries = parse(\"\"\" ;; Open tax accounts for that year. {date_year} open Expenses:Taxes:Y{year}:CC:Federal:PreTax401k DEFCCY {date_year} open Expenses:Taxes:Y{year}:CC:Medicare CCY {date_year} open Expenses:Taxes:Y{year}:CC:Federal CCY {date_year} open Expenses:Taxes:Y{year}:CC:CityNYC CCY {date_year} open Expenses:Taxes:Y{year}:CC:SDI CCY {date_year} open Expenses:Taxes:Y{year}:CC:State CCY {date_year} open Expenses:Taxes:Y{year}:CC:SocSec CCY ;; Check that the tax amounts have been fully used. {date_year} balance Assets:CC:Federal:PreTax401k 0 DEFCCY {date_year} * \"Allowed contributions for one year\" Income:CC:Federal:PreTax401k {amount_limit_neg} DEFCCY Assets:CC:Federal:PreTax401k {amount_limit} DEFCCY {date_filing} * \"Filing taxes for {year}\" Expenses:Taxes:Y{year}:CC:Federal {amount_federal:.2f} CCY Expenses:Taxes:Y{year}:CC:State {amount_state:.2f} CCY Liabilities:AccountsPayable {amount_payable:.2f} CCY {date_federal} * \"FEDERAL TAXPYMT\" Assets:CC:Bank1:Checking {amount_federal_neg:.2f} CCY Liabilities:AccountsPayable {amount_federal:.2f} CCY {date_state} * \"STATE TAX & FINANC PYMT\" Assets:CC:Bank1:Checking {amount_state_neg:.2f} CCY Liabilities:AccountsPayable {amount_state:.2f} CCY \"\"\", **locals()) return [entry for entry in entries if entry.date < date_max]","title":"generate_tax_accounts()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_tax_preamble","text":"Generate tax declarations not specific to any particular year. Parameters: date_birth \u2013 A date instance, the birth date of the character. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_tax_preamble(date_birth): \"\"\"Generate tax declarations not specific to any particular year. Args: date_birth: A date instance, the birth date of the character. Returns: A list of directives. \"\"\" return parse(\"\"\" ;; Tax accounts not specific to a year. {date_birth} open Income:CC:Federal:PreTax401k DEFCCY {date_birth} open Assets:CC:Federal:PreTax401k DEFCCY \"\"\", **locals())","title":"generate_tax_preamble()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_taxable_investment","text":"Generate opening directives and transactions for an investment account. Parameters: date_begin \u2013 A date instance, the beginning date. date_end \u2013 A date instance, the end date. entries \u2013 A list of entries that contains at least the transfers to the investment account's cash account. price_map \u2013 A dict of prices, as per beancount.core.prices.build_price_map(). stocks \u2013 A list of strings, the list of commodities to invest in. Returns: A list of directives. Source code in beancount/scripts/example.py def generate_taxable_investment(date_begin, date_end, entries, price_map, stocks): \"\"\"Generate opening directives and transactions for an investment account. Args: date_begin: A date instance, the beginning date. date_end: A date instance, the end date. entries: A list of entries that contains at least the transfers to the investment account's cash account. price_map: A dict of prices, as per beancount.core.prices.build_price_map(). stocks: A list of strings, the list of commodities to invest in. Returns: A list of directives. \"\"\" account = 'Assets:CC:Investment' account_cash = join(account, 'Cash') account_gains = 'Income:CC:Investment:Gains' account_dividends = 'Income:CC:Investment:Dividends' accounts_stocks = ['Assets:CC:Investment:{}'.format(commodity) for commodity in stocks] open_entries = parse(\"\"\" {date_begin} open {account}:Cash CCY {date_begin} open {account_gains} CCY {date_begin} open {account_dividends} CCY \"\"\", **locals()) for stock in stocks: open_entries.extend(parse(\"\"\" {date_begin} open {account}:{stock} {stock} \"\"\", **locals())) # Figure out dates at which dividends should be distributed, near the end of # each quarter. days_to = datetime.timedelta(days=3*90-10) dividend_dates = [] for quarter_begin in iter_quarters(date_begin, date_end): end_of_quarter = quarter_begin + days_to if not (date_begin < end_of_quarter < date_end): continue dividend_dates.append(end_of_quarter) # Iterate over all the dates, but merging in the postings for the cash # account. min_amount = D('1000.00') round_amount = D('100.00') commission = D('8.95') round_units = D('1') frac_invest = D('1.00') frac_dividend = D('0.004') p_daily_buy = 1./15 # days p_daily_sell = 1./90 # days stocks_inventory = inventory.Inventory() new_entries = [] dividend_date_iter = iter(dividend_dates) next_dividend_date = next(dividend_date_iter) for date, balances in iter_dates_with_balance(date_begin, date_end, entries, [account_cash]): # Check if we should insert a dividend. Note that we could not factor # this out because we want to explicitly reinvest the cash dividends and # we also want the dividends to be proportional to the amount of # invested stock, so one feeds on the other and vice-versa. if next_dividend_date and date > next_dividend_date: # Compute the total balances for the stock accounts in order to # create a realistic dividend. total = inventory.Inventory() for account_stock in accounts_stocks: total.add_inventory(balances[account_stock]) # Create an entry offering dividends of 1% of the portfolio. portfolio_cost = total.reduce(convert.get_cost).get_currency_units('CCY').number amount_cash = (frac_dividend * portfolio_cost).quantize(D('0.01')) amount_cash_neg = -amount_cash dividend = parse(\"\"\" {next_dividend_date} * \"Dividends on portfolio\" {account}:Cash {amount_cash:.2f} CCY {account_dividends} {amount_cash_neg:.2f} CCY \"\"\", **locals())[0] new_entries.append(dividend) # Advance the next dividend date. try: next_dividend_date = next(dividend_date_iter) except StopIteration: next_dividend_date = None # If the balance is high, buy with high probability. balance = balances[account_cash] total_cash = balance.get_currency_units('CCY').number assert total_cash >= ZERO, ('Cash balance is negative: {}'.format(total_cash)) invest_cash = total_cash * frac_invest - commission if invest_cash > min_amount: if random.random() < p_daily_buy: commodities = random.sample(stocks, random.randint(1, len(stocks))) lot_amount = round_to(invest_cash / len(commodities), round_amount) invested_amount = ZERO for stock in commodities: # Find the price at that date. _, price = prices.get_price(price_map, (stock, 'CCY'), date) units = round_to((lot_amount / price), round_units) if units <= ZERO: continue amount_cash = -(units * price + commission) # logging.info('Buying %s %s @ %s CCY = %s CCY', # units, stock, price, units * price) buy = parse(\"\"\" {date} * \"Buy shares of {stock}\" {account}:Cash {amount_cash:.2f} CCY {account}:{stock} {units:.0f} {stock} {{{price:.2f} CCY}} Expenses:Financial:Commissions {commission:.2f} CCY \"\"\", **locals())[0] new_entries.append(buy) account_stock = ':'.join([account, stock]) balances[account_cash].add_position(buy.postings[0]) balances[account_stock].add_position(buy.postings[1]) stocks_inventory.add_position(buy.postings[1]) # Don't sell on days you buy. continue # Otherwise, sell with low probability. if not stocks_inventory.is_empty() and random.random() < p_daily_sell: # Choose the lot with the highest gain or highest loss. gains = [] for position in stocks_inventory.get_positions(): base_quote = (position.units.currency, position.cost.currency) _, price = prices.get_price(price_map, base_quote, date) if price == position.cost.number: continue # Skip lots without movement. market_value = position.units.number * price book_value = convert.get_cost(position).number gain = market_value - book_value gains.append((gain, market_value, price, position)) if not gains: continue # Sell either biggest winner or biggest loser. biggest = bool(random.random() < 0.5) lot_tuple = sorted(gains)[0 if biggest else -1] gain, market_value, price, sell_position = lot_tuple #logging.info('Selling {} for {}'.format(sell_position, market_value)) sell_position = -sell_position stock = sell_position.units.currency amount_cash = market_value - commission amount_gain = -gain sell = parse(\"\"\" {date} * \"Sell shares of {stock}\" {account}:{stock} {sell_position} @ {price:.2f} CCY {account}:Cash {amount_cash:.2f} CCY Expenses:Financial:Commissions {commission:.2f} CCY {account_gains} {amount_gain:.2f} CCY \"\"\", **locals())[0] new_entries.append(sell) balances[account_cash].add_position(sell.postings[1]) stocks_inventory.add_position(sell.postings[0]) continue return open_entries + new_entries","title":"generate_taxable_investment()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.generate_trip_entries","text":"Generate more dense expenses for a trip. Parameters: date_begin \u2013 A datetime.date instance, the beginning of the trip. date_end \u2013 A datetime.date instance, the end of the trip. tag \u2013 A string, the name of the tag. config \u2013 A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city \u2013 A string, the capitalized name of the destination city. home_city \u2013 A string, the name of the home city. account_credit \u2013 A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. Source code in beancount/scripts/example.py def generate_trip_entries(date_begin, date_end, tag, config, trip_city, home_city, account_credit): \"\"\"Generate more dense expenses for a trip. Args: date_begin: A datetime.date instance, the beginning of the trip. date_end: A datetime.date instance, the end of the trip. tag: A string, the name of the tag. config: A list of (payee name, account name, (mu, 3sigma)), where mu is the mean of the prices to generate and 3sigma is 3 times the standard deviation. trip_city: A string, the capitalized name of the destination city. home_city: A string, the name of the home city. account_credit: A string, the name of the credit card account to pay the expenses from. Returns: A list of entries for the trip, all tagged with the given tag. \"\"\" p_day_generate = 0.3 new_entries = [] for date in date_iter(date_begin, date_end): for payee, account_expense, (mu, sigma3) in config: if random.random() < p_day_generate: amount = random.normalvariate(mu, sigma3 / 3.) amount_neg = -amount new_entries.extend(parse(\"\"\" {date} * \"{payee}\" \"\" #{tag} {account_credit} {amount_neg:.2f} CCY {account_expense} {amount:.2f} CCY \"\"\", **locals())) # Consume the vacation days. vacation_hrs = (date_end - date_begin).days * 8 # hrs/day new_entries.extend(parse(\"\"\" {date_end} * \"Consume vacation days\" Assets:CC:Employer1:Vacation -{vacation_hrs:.2f} VACHR Expenses:Vacation {vacation_hrs:.2f} VACHR \"\"\", **locals())) # Generate events for the trip. new_entries.extend(parse(\"\"\" {date_begin} event \"location\" \"{trip_city}\" {date_end} event \"location\" \"{home_city}\" \"\"\", **locals())) return new_entries","title":"generate_trip_entries()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.get_minimum_balance","text":"Compute the minimum balance of the given account according to the entries history. Parameters: entries \u2013 A list of directives. account \u2013 An account string. currency \u2013 A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. Source code in beancount/scripts/example.py def get_minimum_balance(entries, account, currency): \"\"\"Compute the minimum balance of the given account according to the entries history. Args: entries: A list of directives. account: An account string. currency: A currency string, for which we want to compute the minimum. Returns: A Decimal number, the minimum amount throughout the history of this account. \"\"\" min_amount = ZERO for _, balances in postings_for(entries, [account]): balance = balances[account] current = balance.get_currency_units(currency).number if current < min_amount: min_amount = current return min_amount","title":"get_minimum_balance()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.iter_dates_with_balance","text":"Iterate over dates, including the balances of the postings iterator. Parameters: postings_iter \u2013 An iterator of postings as per postings_for(). date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. Source code in beancount/scripts/example.py def iter_dates_with_balance(date_begin, date_end, entries, accounts): \"\"\"Iterate over dates, including the balances of the postings iterator. Args: postings_iter: An iterator of postings as per postings_for(). date_begin: The start date. date_end: The end date. Yields: Pairs of (data, balances) objects, with date: A datetime.date instance balances: An Inventory object, representing the current balance. You can modify the inventory object to feed back changes in the balance. \"\"\" balances = collections.defaultdict(inventory.Inventory) merged_txn_postings = iter(merge_postings(entries, accounts)) txn_posting = next(merged_txn_postings, None) for date in date_iter(date_begin, date_end): while txn_posting and txn_posting.txn.date == date: posting = txn_posting.posting balances[posting.account].add_position(posting) txn_posting = next(merged_txn_postings, None) yield date, balances","title":"iter_dates_with_balance()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.iter_quarters","text":"Iterate over all quarters between begin and end dates. Parameters: date_begin \u2013 The start date. date_end \u2013 The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. Source code in beancount/scripts/example.py def iter_quarters(date_begin, date_end): \"\"\"Iterate over all quarters between begin and end dates. Args: date_begin: The start date. date_end: The end date. Yields: Instances of datetime.date at the beginning of the quarters. This will include the quarter of the beginning date and of the end date. \"\"\" quarter = (date_begin.year, (date_begin.month-1)//3) quarter_last = (date_end.year, (date_end.month-1)//3) assert quarter <= quarter_last while True: year, trimester = quarter yield datetime.date(year, trimester*3 + 1, 1) if quarter == quarter_last: break trimester = (trimester + 1) % 4 if trimester == 0: year += 1 quarter = (year, trimester)","title":"iter_quarters()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.merge_postings","text":"Merge all the postings from the given account names. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. Source code in beancount/scripts/example.py def merge_postings(entries, accounts): \"\"\"Merge all the postings from the given account names. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. Yields: A list of TxnPosting's for all the accounts, in sorted order. \"\"\" real_root = realization.realize(entries) merged_postings = [] for account in accounts: real_account = realization.get(real_root, account) if real_account is None: continue merged_postings.extend(txn_posting for txn_posting in real_account.txn_postings if isinstance(txn_posting, data.TxnPosting)) merged_postings.sort(key=lambda txn_posting: txn_posting.txn.date) return merged_postings","title":"merge_postings()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.parse","text":"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Parameters: input_string \u2013 Beancount input text. **replacements \u2013 A dict of keywords to replace to their values. Returns: A list of directive objects. Source code in beancount/scripts/example.py def parse(input_string, **replacements): \"\"\"Parse some input string and assert no errors. This parse function does not just create the object, it also triggers local interpolation to fill in the missing amounts. Args: input_string: Beancount input text. **replacements: A dict of keywords to replace to their values. Returns: A list of directive objects. \"\"\" if replacements: class IgnoreFormatter(string.Formatter): def check_unused_args(self, used_args, args, kwargs): pass formatter = IgnoreFormatter() formatted_string = formatter.format(input_string, **replacements) else: formatted_string = input_string entries, errors, options_map = parser.parse_string(textwrap.dedent(formatted_string)) if errors: printer.print_errors(errors, file=sys.stderr) raise ValueError(\"Parsed text has errors\") # Interpolation. entries, unused_balance_errors = booking.book(entries, options_map) return data.sorted(entries)","title":"parse()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.postings_for","text":"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Parameters: entries \u2013 A list of directives. accounts \u2013 A list of account strings to get the balances for. before \u2013 A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts after applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. Source code in beancount/scripts/example.py def postings_for(entries, accounts, before=False): \"\"\"Realize the entries and get the list of postings for the given accounts. All the non-Posting directives are already filtered out. Args: entries: A list of directives. accounts: A list of account strings to get the balances for. before: A boolean, if true, yield the balance before the position is applied. The default is to yield the balance after applying the position. Yields: Tuples of: posting: An instance of TxnPosting balances: A dict of Inventory balances for the given accounts _after_ applying the posting. These inventory objects can be mutated to adjust the balance due to generated transactions to be applied later. \"\"\" assert isinstance(accounts, list) merged_txn_postings = merge_postings(entries, accounts) balances = collections.defaultdict(inventory.Inventory) for txn_posting in merged_txn_postings: if before: yield txn_posting, balances posting = txn_posting.posting balances[posting.account].add_position(posting) if not before: yield txn_posting, balances","title":"postings_for()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.price_series","text":"Generate a price series based on a simple stochastic model. Parameters: start \u2013 The beginning value. mu \u2013 The per-step drift, in units of value. sigma \u2013 Volatility of the changes. Yields: Floats, at each step. Source code in beancount/scripts/example.py def price_series(start, mu, sigma): \"\"\"Generate a price series based on a simple stochastic model. Args: start: The beginning value. mu: The per-step drift, in units of value. sigma: Volatility of the changes. Yields: Floats, at each step. \"\"\" value = start while 1: yield value value += random.normalvariate(mu, sigma) * value","title":"price_series()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.replace","text":"Apply word-boundaried regular expression replacements to an indented string. Parameters: string \u2013 Some input template string. replacements \u2013 A dict of regexp to replacement value. strip \u2013 A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. Source code in beancount/scripts/example.py def replace(string, replacements, strip=False): \"\"\"Apply word-boundaried regular expression replacements to an indented string. Args: string: Some input template string. replacements: A dict of regexp to replacement value. strip: A boolean, true if we should strip the input. Returns: The input string with the replacements applied to it, with the indentation removed. \"\"\" output = textwrap.dedent(string) if strip: output = output.strip() for from_, to_ in replacements.items(): if not isinstance(to_, str) and not callable(to_): to_ = str(to_) output = re.sub(r'\\b{}\\b'.format(from_), to_, output) return output","title":"replace()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.validate_output","text":"Check that the output file validates. Parameters: contents \u2013 A string, the output file. positive_accounts \u2013 A list of strings, account names to check for non-negative balances. currency \u2013 A string, the currency to check minimums for. Exceptions: AssertionError \u2013 If the output does not validate. Source code in beancount/scripts/example.py def validate_output(contents, positive_accounts, currency): \"\"\"Check that the output file validates. Args: contents: A string, the output file. positive_accounts: A list of strings, account names to check for non-negative balances. currency: A string, the currency to check minimums for. Raises: AssertionError: If the output does not validate. \"\"\" loaded_entries, _, _ = loader.load_string( contents, log_errors=sys.stderr, extra_validations=validation.HARDCORE_VALIDATIONS) # Sanity checks: Check that the checking balance never goes below zero. for account in positive_accounts: check_non_negative(loaded_entries, account, currency)","title":"validate_output()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.example.write_example_file","text":"Generate the example file. Parameters: date_birth \u2013 A datetime.date instance, the birth date of our character. date_begin \u2013 A datetime.date instance, the beginning date at which to generate transactions. date_end \u2013 A datetime.date instance, the end date at which to generate transactions. reformat \u2013 A boolean, true if we should apply global reformatting to this file. file \u2013 A file object, where to write out the output. Source code in beancount/scripts/example.py def write_example_file(date_birth, date_begin, date_end, reformat, file): \"\"\"Generate the example file. Args: date_birth: A datetime.date instance, the birth date of our character. date_begin: A datetime.date instance, the beginning date at which to generate transactions. date_end: A datetime.date instance, the end date at which to generate transactions. reformat: A boolean, true if we should apply global reformatting to this file. file: A file object, where to write out the output. \"\"\" # The following code entirely writes out the output to generic names, such # as \"Employer1\", \"Bank1\", and \"CCY\" (for principal currency). Those names # are purposely chosen to be unique, and only near the very end do we make # renamings to more specific and realistic names. # Name of the checking account. account_opening = 'Equity:Opening-Balances' account_payable = 'Liabilities:AccountsPayable' account_checking = 'Assets:CC:Bank1:Checking' account_credit = 'Liabilities:CC:CreditCard1' account_retirement = 'Assets:CC:Retirement' account_investing = 'Assets:CC:Investment:Cash' # Commodities. commodity_entries = generate_commodity_entries(date_birth) # Estimate the rent. rent_amount = round_to(ANNUAL_SALARY / RENT_DIVISOR, RENT_INCREMENT) # Get a random employer. employer_name, employer_address = random.choice(EMPLOYERS) logging.info(\"Generating Salary Employment Income\") income_entries = generate_employment_income(employer_name, employer_address, ANNUAL_SALARY, account_checking, join(account_retirement, 'Cash'), date_begin, date_end) logging.info(\"Generating Expenses from Banking Accounts\") banking_expenses = generate_banking_expenses(date_begin, date_end, account_checking, rent_amount) logging.info(\"Generating Regular Expenses via Credit Card\") credit_regular_entries = generate_regular_credit_expenses( date_birth, date_begin, date_end, account_credit, account_checking) logging.info(\"Generating Credit Card Expenses for Trips\") trip_entries = [] destinations = sorted(TRIP_DESTINATIONS.items()) destinations.extend(destinations) random.shuffle(destinations) for (date_trip_begin, date_trip_end), (destination_name, config) in zip( compute_trip_dates(date_begin, date_end), destinations): # Compute a suitable tag. tag = 'trip-{}-{}'.format(destination_name.lower().replace(' ', '-'), date_trip_begin.year) #logging.info(\"%s -- %s %s\", tag, date_trip_begin, date_trip_end) # Remove regular entries during this trip. credit_regular_entries = [entry for entry in credit_regular_entries if not(date_trip_begin <= entry.date < date_trip_end)] # Generate entries for the trip. this_trip_entries = generate_trip_entries( date_trip_begin, date_trip_end, tag, config, destination_name.replace('-', ' ').title(), HOME_NAME, account_credit) trip_entries.extend(this_trip_entries) logging.info(\"Generating Credit Card Payment Entries\") credit_payments = generate_clearing_entries( delay_dates(rrule.rrule(rrule.MONTHLY, dtstart=date_begin, until=date_end, bymonthday=7), 0, 4), \"CreditCard1\", \"Paying off credit card\", credit_regular_entries, account_credit, account_checking) credit_entries = credit_regular_entries + trip_entries + credit_payments logging.info(\"Generating Tax Filings and Payments\") tax_preamble = generate_tax_preamble(date_birth) # Figure out all the years we need tax accounts for. years = set() for account_name in getters.get_accounts(income_entries): match = re.match(r'Expenses:Taxes:Y(\\d\\d\\d\\d)', account_name) if match: years.add(int(match.group(1))) taxes = [(year, generate_tax_accounts(year, date_end)) for year in sorted(years)] tax_entries = tax_preamble + functools.reduce(operator.add, (entries for _, entries in taxes)) logging.info(\"Generating Opening of Banking Accounts\") # Open banking accounts and gift the checking account with a balance that # will offset all the amounts to ensure a positive balance throughout its # lifetime. entries_for_banking = data.sorted(income_entries + banking_expenses + credit_entries + tax_entries) minimum = get_minimum_balance(entries_for_banking, account_checking, 'CCY') banking_entries = generate_banking(entries_for_banking, date_begin, date_end, max(-minimum, ZERO)) logging.info(\"Generating Transfers to Investment Account\") banking_transfers = generate_outgoing_transfers( data.sorted(income_entries + banking_entries + banking_expenses + credit_entries + tax_entries), account_checking, account_investing, transfer_minimum=D('200'), transfer_threshold=D('3000'), transfer_increment=D('500')) logging.info(\"Generating Prices\") # Generate price entries for investment currencies and create a price map to # use for later for generating investment transactions. funds_allocation = {'MFUND1': 0.40, 'MFUND2': 0.60} stocks = ['STK1', 'STK2', 'STK3', 'STK4'] price_entries = generate_prices(date_begin, date_end, sorted(funds_allocation.keys()) + stocks, 'CCY') price_map = prices.build_price_map(price_entries) logging.info(\"Generating Employer Match Contribution\") account_match = 'Income:US:Employer1:Match401k' retirement_match = generate_retirement_employer_match(income_entries, join(account_retirement, 'Cash'), account_match) logging.info(\"Generating Retirement Investments\") retirement_entries = generate_retirement_investments( income_entries + retirement_match, account_retirement, sorted(funds_allocation.items()), price_map) logging.info(\"Generating Taxes Investments\") investment_entries = generate_taxable_investment(date_begin, date_end, banking_transfers, price_map, stocks) logging.info(\"Generating Expense Accounts\") expense_accounts_entries = generate_expense_accounts(date_birth) logging.info(\"Generating Equity Accounts\") equity_entries = generate_open_entries(date_birth, [account_opening, account_payable]) logging.info(\"Generating Balance Checks\") credit_checks = generate_balance_checks(credit_entries, account_credit, date_random_seq(date_begin, date_end, 20, 30)) banking_checks = generate_balance_checks(data.sorted(income_entries + banking_entries + banking_expenses + banking_transfers + credit_entries + tax_entries), account_checking, date_random_seq(date_begin, date_end, 20, 30)) logging.info(\"Outputting and Formatting Entries\") dcontext = display_context.DisplayContext() default_int_digits = 8 for currency, precision in {'USD': 2, 'CAD': 2, 'VACHR':0, 'IRAUSD': 2, 'VBMPX': 3, 'RGAGX': 3, 'ITOT': 0, 'VEA': 0, 'VHT': 0, 'GLD': 0}.items(): int_digits = default_int_digits if precision > 0: int_digits += 1 + precision dcontext.update(D('{{:0{}.{}f}}'.format(int_digits, precision).format(0)), currency) output = io.StringIO() def output_section(title, entries): output.write('\\n\\n\\n{}\\n\\n'.format(title)) printer.print_entries(data.sorted(entries), dcontext, file=output) output.write(FILE_PREAMBLE.format(**locals())) output_section('* Commodities', commodity_entries) output_section('* Equity Accounts', equity_entries) output_section('* Banking', data.sorted(banking_entries + banking_expenses + banking_transfers + banking_checks)) output_section('* Credit-Cards', data.sorted(credit_entries + credit_checks)) output_section('* Taxable Investments', investment_entries) output_section('* Retirement Investments', data.sorted(retirement_entries + retirement_match)) output_section('* Sources of Income', income_entries) output_section('* Taxes', tax_preamble) for year, entries in taxes: output_section('** Tax Year {}'.format(year), entries) output_section('* Expenses', expense_accounts_entries) output_section('* Prices', price_entries) output_section('* Cash', []) logging.info(\"Contextualizing to Realistic Names\") contents, replacements = contextualize_file(output.getvalue(), employer_name) if reformat: contents = format.align_beancount(contents) logging.info(\"Writing contents\") file.write(contents) logging.info(\"Validating Results\") validate_output(contents, [replace(account, replacements) for account in [account_checking]], replace('CCY', replacements))","title":"write_example_file()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format","text":"Align a beancount/ledger input file's numbers. This reformats at beancount or ledger input file so that the amounts in the postings are all aligned to the same column. The currency should match. Note: this does not parse the Beancount ledger. It simply uses regular expressions and text manipulations to do its work.","title":"format"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.align_beancount","text":"Reformat Beancount input to align all the numbers at the same column. Parameters: contents \u2013 A string, Beancount input syntax to reformat. prefix_width \u2013 An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width \u2013 An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column \u2013 An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. Source code in beancount/scripts/format.py def align_beancount(contents, prefix_width=None, num_width=None, currency_column=None): \"\"\"Reformat Beancount input to align all the numbers at the same column. Args: contents: A string, Beancount input syntax to reformat. prefix_width: An integer, the width in characters to render the account name to. If this is not specified, a good value is selected automatically from the contents of the file. num_width: An integer, the width to render each number. If this is not specified, a good value is selected automatically from the contents of the file. currency_column: An integer, the column at which to align the currencies. If given, this overrides the other options. Returns: A string, reformatted Beancount input with all the number aligned. No other changes than whitespace changes should be present between that return value and the input contents. \"\"\" # Find all lines that have a number in them and calculate the maximum length # of the stripped prefix and the number. match_pairs = [] for line in contents.splitlines(): match = re.match( r'([^\";]*?)\\s+([-+]?\\s*[\\d,]+(?:\\.\\d*)?)\\s+({}\\b.*)'.format(amount.CURRENCY_RE), line) if match: prefix, number, rest = match.groups() match_pairs.append((prefix, number, rest)) else: match_pairs.append((line, None, None)) # Normalize whitespace before lines that has some indent and an account # name. norm_match_pairs = normalize_indent_whitespace(match_pairs) if currency_column: output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: num_of_spaces = currency_column - len(prefix) - len(number) - 4 spaces = ' ' * num_of_spaces output.write(prefix + spaces + ' ' + number + ' ' + rest) output.write('\\n') return output.getvalue() # Compute the maximum widths. filtered_pairs = [(prefix, number) for prefix, number, _ in match_pairs if number is not None] if filtered_pairs: max_prefix_width = max(len(prefix) for prefix, _ in filtered_pairs) max_num_width = max(len(number) for _, number in filtered_pairs) else: max_prefix_width = 0 max_num_width = 0 # Use user-supplied overrides, if available if prefix_width: max_prefix_width = prefix_width if num_width: max_num_width = num_width # Create a format that will admit the maximum width of all prefixes equally. line_format = '{{:<{prefix_width}}} {{:>{num_width}}} {{}}'.format( prefix_width=max_prefix_width, num_width=max_num_width) # Process each line to an output buffer. output = io.StringIO() for prefix, number, rest in norm_match_pairs: if number is None: output.write(prefix) else: output.write(line_format.format(prefix.rstrip(), number, rest)) output.write('\\n') formatted_contents = output.getvalue() # Ensure that the file before and after have only whitespace differences. # This is a sanity check, to make really sure we never change anything but whitespace, # so it's safe. # open('/tmp/before', 'w').write(re.sub(r'[ \\t]+', ' ', contents)) # open('/tmp/after', 'w').write(re.sub(r'[ \\t]+', ' ', formatted_contents)) old_stripped = re.sub(r'[ \\t\\n]+', ' ', contents.rstrip()) new_stripped = re.sub(r'[ \\t\\n]+', ' ', formatted_contents.rstrip()) assert (old_stripped == new_stripped), (old_stripped, new_stripped) return formatted_contents","title":"align_beancount()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.compute_most_frequent","text":"Compute the frequencies of the given elements and return the most frequent. Parameters: iterable \u2013 A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. Source code in beancount/scripts/format.py def compute_most_frequent(iterable): \"\"\"Compute the frequencies of the given elements and return the most frequent. Args: iterable: A collection of hashable elements. Returns: The most frequent element. If there are no elements in the iterable, return None. \"\"\" frequencies = collections.Counter(iterable) if not frequencies: return None counts = sorted((count, element) for element, count in frequencies.items()) # Note: In case of a tie, this chooses the longest width. # We could eventually make this an option. return counts[-1][1]","title":"compute_most_frequent()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.format.normalize_indent_whitespace","text":"Normalize whitespace before lines that has some indent and an account name. Parameters: match_pairs \u2013 A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. Source code in beancount/scripts/format.py def normalize_indent_whitespace(match_pairs): \"\"\"Normalize whitespace before lines that has some indent and an account name. Args: match_pairs: A list of (prefix, number, rest) tuples. Returns: Another list of (prefix, number, rest) tuples, where prefix may have been adjusted with a different whitespace prefix. \"\"\" # Compute most frequent account name prefix. match_posting = re.compile(r'([ \\t]+)({}.*)'.format(account.ACCOUNT_RE)).match width = compute_most_frequent( len(match.group(1)) for match in (match_posting(prefix) for prefix, _, _ in match_pairs) if match is not None) norm_format = ' ' * (width or 0) + '{}' # Make the necessary adjustments. adjusted_pairs = [] for tup in match_pairs: prefix, number, rest = tup match = match_posting(prefix) if match is not None: tup = (norm_format.format(match.group(2)), number, rest) adjusted_pairs.append(tup) return adjusted_pairs","title":"normalize_indent_whitespace()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql","text":"Convert a Beancount ledger into an SQL database.","title":"sql"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter","text":"","title":"BalanceWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type","text":"Balance(meta, date, account, amount, tolerance, diff_amount)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__new__","text":"Create new instance of Balance(meta, date, account, amount, tolerance, diff_amount)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.BalanceWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.amount.number, entry.amount.currency, entry.diff_amount.currency if entry.diff_amount else None, entry.diff_amount.currency if entry.diff_amount else None)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter","text":"","title":"CloseWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type","text":"Close(meta, date, account)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__new__","text":"Create new instance of Close(meta, date, account)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.CloseWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account,)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter","text":"A base class for writers of directives. This is used to factor out code for all the simple directives types (all types except Transaction).","title":"DirectiveWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter.__call__","text":"Create a table for a directives. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def __call__(self, connection, entries): \"\"\"Create a table for a directives. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: columns_text = ','.join(self.columns.strip().splitlines()) connection.execute(\"\"\" CREATE TABLE {name}_detail ( id INTEGER PRIMARY KEY, {columns} ); \"\"\".format(name=self.name, columns=columns_text)) connection.execute(\"\"\" CREATE VIEW {name} AS SELECT * FROM entry JOIN {name}_detail USING (id); \"\"\".format(name=self.name)) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, self.type): continue # Store common data. connection.execute(\"\"\" INSERT INTO entry VALUES (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, self.name, entry.meta[\"filename\"], entry.meta[\"lineno\"])) # Store detail data. detail_data = self.get_detail(entry) row_data = (eid,) + detail_data query = \"\"\" INSERT INTO {name}_detail VALUES ({placeholder}); \"\"\".format(name=self.name, placeholder=','.join(['?'] * (1 + len(detail_data)))) connection.execute(query, row_data)","title":"__call__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DirectiveWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): \"\"\"Provide data to store for details table. Args: entry: An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. \"\"\" raise NotImplementedError","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter","text":"","title":"DocumentWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type","text":"Document(meta, date, account, filename, tags, links)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__new__","text":"Create new instance of Document(meta, date, account, filename, tags, links)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.DocumentWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.filename)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter","text":"","title":"EventWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type","text":"Event(meta, date, type, description)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__new__","text":"Create new instance of Event(meta, date, type, description)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.EventWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.type, entry.description)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter","text":"","title":"NoteWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type","text":"Note(meta, date, account, comment)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__new__","text":"Create new instance of Note(meta, date, account, comment)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.NoteWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.comment)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter","text":"","title":"OpenWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type","text":"Open(meta, date, account, currencies, booking)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__new__","text":"Create new instance of Open(meta, date, account, currencies, booking)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.OpenWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, ','.join(entry.currencies or []))","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter","text":"","title":"PadWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type","text":"Pad(meta, date, account, source_account)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__new__","text":"Create new instance of Pad(meta, date, account, source_account)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PadWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.account, entry.source_account)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter","text":"","title":"PriceWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type","text":"Price(meta, date, currency, amount)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__new__","text":"Create new instance of Price(meta, date, currency, amount)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.PriceWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.currency, entry.amount.number, entry.amount.currency)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter","text":"","title":"QueryWriter"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type","text":"Query(meta, date, name, query_string)","title":"type"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/scripts/sql.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__new__","text":"Create new instance of Query(meta, date, name, query_string)","title":"__new__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.type.__repr__","text":"Return a nicely formatted representation string Source code in beancount/scripts/sql.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.QueryWriter.get_detail","text":"Provide data to store for details table. Parameters: entry \u2013 An instance of the desired directive. Returns: A tuple of the values corresponding to the columns declared in the 'columns' attribute. Source code in beancount/scripts/sql.py def get_detail(self, entry): return (entry.name, entry.query_string)","title":"get_detail()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.adapt_decimal","text":"Adapt a Decimal instance to a string for creating queries. Parameters: number \u2013 An instance of Decimal. Returns: A string. Source code in beancount/scripts/sql.py def adapt_decimal(number): \"\"\"Adapt a Decimal instance to a string for creating queries. Args: number: An instance of Decimal. Returns: A string. \"\"\" return str(number)","title":"adapt_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.convert_decimal","text":"Convert a Decimal string to a Decimal instance. Parameters: string \u2013 A decimal number in a string. Returns: An instance of Decimal. Source code in beancount/scripts/sql.py def convert_decimal(string): \"\"\"Convert a Decimal string to a Decimal instance. Args: string: A decimal number in a string. Returns: An instance of Decimal. \"\"\" return Decimal(string)","title":"convert_decimal()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.output_common","text":"Create a table of common data for all entries. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_common(connection, unused_entries): \"\"\"Create a table of common data for all entries. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE entry ( id INTEGER PRIMARY KEY, date DATE, type CHARACTER(8), source_filename STRING, source_lineno INTEGER ); \"\"\")","title":"output_common()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.output_transactions","text":"Create a table for transactions and fill in the data. Parameters: connection \u2013 A DBAPI-2.0 Connection object. entries \u2013 A list of directives. Source code in beancount/scripts/sql.py def output_transactions(connection, entries): \"\"\"Create a table for transactions and fill in the data. Args: connection: A DBAPI-2.0 Connection object. entries: A list of directives. \"\"\" with connection: connection.execute(\"\"\" CREATE TABLE transactions_detail ( id INTEGER PRIMARY KEY, flag CHARACTER(1), payee VARCHAR, narration VARCHAR, tags VARCHAR, -- Comma-separated links VARCHAR -- Comma-separated ); \"\"\") connection.execute(\"\"\" CREATE VIEW transactions AS SELECT * FROM entry JOIN transactions_detail USING (id); \"\"\") connection.execute(\"\"\" CREATE TABLE postings ( posting_id INTEGER PRIMARY KEY, id INTEGER, flag CHARACTER(1), account VARCHAR, number DECIMAL(16, 6), currency CHARACTER(10), cost_number DECIMAL(16, 6), cost_currency CHARACTER(10), cost_date DATE, cost_label VARCHAR, price_number DECIMAL(16, 6), price_currency CHARACTER(10), FOREIGN KEY(id) REFERENCES entries(id) ); \"\"\") postings_count = iter(itertools.count()) with connection: for eid, entry in enumerate(entries): if not isinstance(entry, data.Transaction): continue connection.execute(\"\"\" insert into entry values (?, ?, ?, ?, ?); \"\"\", (eid, entry.date, 'txn', entry.meta[\"filename\"], entry.meta[\"lineno\"])) connection.execute(\"\"\" insert into transactions_detail values (?, ?, ?, ?, ?, ?); \"\"\", (eid, entry.flag, entry.payee, entry.narration, ','.join(entry.tags or ()), ','.join(entry.links or ()))) for posting in entry.postings: pid = next(postings_count) units = posting.units cost = posting.cost price = posting.price connection.execute(\"\"\" INSERT INTO postings VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); \"\"\", (pid, eid, posting.flag, posting.account, units.number, units.currency, cost.number if cost else None, cost.currency if cost else None, cost.date if cost else None, cost.label if cost else None, price.number if price else None, price.currency if price else None))","title":"output_transactions()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.sql.setup_decimal_support","text":"Setup sqlite3 to support conversions to/from Decimal numbers. Source code in beancount/scripts/sql.py def setup_decimal_support(): \"\"\"Setup sqlite3 to support conversions to/from Decimal numbers. \"\"\" dbapi.register_adapter(Decimal, adapt_decimal) dbapi.register_converter(\"decimal\", convert_decimal)","title":"setup_decimal_support()"},{"location":"api_reference/beancount.scripts.html#beancount.scripts.tutorial","text":"Write output files for the tutorial commands.","title":"tutorial"},{"location":"api_reference/beancount.tools.html","text":"beancount.tools \uf0c1 Standalone tools that aren't linked to Beancount but that are useful with it. The beancount.scripts package contains the implementation of scripts which invoke the Beancount library code. This beancount.tools package implements other tools which aren't directly invoking Beancount library code and which could be theoretically copied and used independently. However, these are to be distributed with Beancount and in order to maintain all the source code together they are put in this package and invokes from stubs under beancount/bin/, just like the other scripts. beancount.tools.treeify \uf0c1 Identify a column of text that contains hierarchical id and treeify that column. This script will inspect a text file and attempt to find a vertically left-aligned column of text that contains identifiers with multiple components, such as \"Assets:US:Bank:Checking\", and replace those by a tree-like structure rendered in ASCII, inserting new empty lines where necessary to create the tree. Note: If your paths have spaces in them, this will not work. Space is used as a delimiter to detect the end of a column. You can customize the delimiter with an option. beancount.tools.treeify.Node ( list ) \uf0c1 A node with a name attribute, a list of line numbers and a list of children (from its parent class). beancount.tools.treeify.Node.__repr__(self) special \uf0c1 Return str(self). Source code in beancount/tools/treeify.py def __str__(self): return ''.format(self.name, [node.name for node in self]) beancount.tools.treeify.create_tree(column_matches, regexp_split) \uf0c1 Build up a tree from a list of matches. Parameters: column_matches \u2013 A list of (line-number, name) pairs. regexp_split \u2013 A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. Source code in beancount/tools/treeify.py def create_tree(column_matches, regexp_split): \"\"\"Build up a tree from a list of matches. Args: column_matches: A list of (line-number, name) pairs. regexp_split: A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. \"\"\" root = Node('') for no, name in column_matches: parts = re.split(regexp_split, name) node = root for part in parts: last_node = node[-1] if node else None if last_node is None or last_node.name != part: last_node = Node(part) node.append(last_node) node = last_node node.nos.append(no) return root beancount.tools.treeify.dump_tree(node, file=<_io.StringIO object at 0x749444f21780>, prefix='') \uf0c1 Render a tree as a tree. Parameters: node \u2013 An instance of Node. file \u2013 A file object to write to. prefix \u2013 A prefix string for each of the lines of the children. Source code in beancount/tools/treeify.py def dump_tree(node, file=sys.stdout, prefix=''): \"\"\"Render a tree as a tree. Args: node: An instance of Node. file: A file object to write to. prefix: A prefix string for each of the lines of the children. \"\"\" file.write(prefix) file.write(node.name) file.write('\\n') for child in node: dump_tree(child, file, prefix + '... ') beancount.tools.treeify.enum_tree_by_input_line_num(tree_lines) \uf0c1 Accumulate the lines of a tree until a line number is found. Parameters: tree_lines \u2013 A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). Source code in beancount/tools/treeify.py def enum_tree_by_input_line_num(tree_lines): \"\"\"Accumulate the lines of a tree until a line number is found. Args: tree_lines: A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). \"\"\" pending = [] for first_line, cont_line, node in tree_lines: if not node.nos: pending.append((first_line, node)) else: line = first_line for no in node.nos: pending.append((line, node)) line = cont_line yield (no, pending) pending = [] if pending: yield (None, pending) beancount.tools.treeify.find_column(lines, pattern, delimiter) \uf0c1 Find a valid column with hierarchical data in the text lines. Parameters: lines \u2013 A list of strings, the contents of the input. pattern \u2013 A regular expression for the hierarchical entries. delimiter \u2013 A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches \u2013 A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. Source code in beancount/tools/treeify.py def find_column(lines, pattern, delimiter): \"\"\"Find a valid column with hierarchical data in the text lines. Args: lines: A list of strings, the contents of the input. pattern: A regular expression for the hierarchical entries. delimiter: A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches: A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. \"\"\" # A mapping of the line beginning position to its match object. beginnings = collections.defaultdict(list) pattern_and_whitespace = \"({})(?P{}.|$)\".format(pattern, delimiter) for no, line in enumerate(lines): for match in re.finditer(pattern_and_whitespace, line): beginnings[match.start()].append((no, line, match)) # For each potential column found, verify that it is valid. A valid column # will have the maximum of its content text not overlap with any of the # following text. We assume that a column will have been formatted to full # width and that no text following the line overlap with the column, even in # its trailing whitespace. # # In other words, the following example is a violation because \"10,990.74\" # overlaps with the end of \"Insurance\" and so this would not be recognized # as a valid column: # # Expenses:Food:Restaurant 10,990.74 USD # Expenses:Health:Dental:Insurance 208.80 USD # for leftmost_column, column_matches in sorted(beginnings.items()): # Compute the location of the rightmost column of text. rightmost_column = max(match.end(1) for _, _, match in column_matches) # Compute the leftmost location of the content following the column text # and past its whitespace. following_column = min(match.end() if match.group('ws') else 10000 for _, _, match in column_matches) if rightmost_column < following_column: # We process only the very first match. return_matches = [(no, match.group(1).rstrip()) for no, _, match in column_matches] return return_matches, leftmost_column, rightmost_column beancount.tools.treeify.render_tree(root) \uf0c1 Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. Source code in beancount/tools/treeify.py def render_tree(root): \"\"\"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root.name, root, True)] while stack: prefix, name, node, is_last = stack.pop(-1) if node is root: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(node) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (node is root and not name): lines.append((first + name, cont + cont_name, node)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. if node: child_items = reversed(node) child_iter = iter(child_items) child_node = next(child_iter) stack.append((cont, child_node.name, child_node, True)) for child_node in child_iter: stack.append((cont, child_node.name, child_node, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), node) for (first_line, cont_line, node) in lines], max_width beancount.tools.treeify_test \uf0c1 Unit tests for treeify tool. beancount.tools.treeify_test.TestTreeifyBase ( TestCase ) \uf0c1 beancount.tools.treeify_test.TestTreeifyBase.treeify(self, string, expect_errors=False, options=None) \uf0c1 Run treeify on the given string and assert no errors. Parameters: string \u2013 A string, the input contents to run on. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. Source code in beancount/tools/treeify_test.py def treeify(self, string, expect_errors=False, options=None): \"\"\"Run treeify on the given string and assert no errors. Args: string: A string, the input contents to run on. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. \"\"\" returncode, output, errors = treeify(string, options) actual_errors = returncode != 0 or bool(errors) if actual_errors != expect_errors: if expect_errors: self.fail(\"Missing expected errors\") else: self.fail(\"Unexpected errors: {}\".format(errors)) return output beancount.tools.treeify_test.TestTreeifyBase.treeify_equal(self, string, expected, expect_errors=False, options=None) \uf0c1 Assert an expected treeification result. Parameters: string \u2013 An expected input contents string to treeify. expected \u2013 A string, the expected treeified output. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. Source code in beancount/tools/treeify_test.py def treeify_equal(self, string, expected, expect_errors=False, options=None): \"\"\"Assert an expected treeification result. Args: string: An expected input contents string to treeify. expected: A string, the expected treeified output. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. \"\"\" input_ = textwrap.dedent(string) output = self.treeify(input_, expect_errors, options) expected = textwrap.dedent(expected) if DEBUG: print('-(input)----------------------------------') print(input_) print('-(output)---------------------------------') print(output) print('-(expected)-------------------------------') print(expected) print('------------------------------------------') self.assertEqual(expected, output) return output beancount.tools.treeify_test.treeify(string, options=None) \uf0c1 Run treeify on the string. Parameters: string \u2013 The input string to feed treeify. options \u2013 An optional list of options for the subprogram. Returns: The treeified string. Source code in beancount/tools/treeify_test.py def treeify(string, options=None): \"\"\"Run treeify on the string. Args: string: The input string to feed treeify. options: An optional list of options for the subprogram. Returns: The treeified string. \"\"\" pipe = subprocess.Popen([PROGRAM] + (options or []), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, errors = pipe.communicate(string.encode('utf-8')) return (pipe.returncode, output.decode('utf-8') if output else '', errors.decode('utf-8') if errors else '')","title":"beancount.tools"},{"location":"api_reference/beancount.tools.html#beancounttools","text":"Standalone tools that aren't linked to Beancount but that are useful with it. The beancount.scripts package contains the implementation of scripts which invoke the Beancount library code. This beancount.tools package implements other tools which aren't directly invoking Beancount library code and which could be theoretically copied and used independently. However, these are to be distributed with Beancount and in order to maintain all the source code together they are put in this package and invokes from stubs under beancount/bin/, just like the other scripts.","title":"beancount.tools"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify","text":"Identify a column of text that contains hierarchical id and treeify that column. This script will inspect a text file and attempt to find a vertically left-aligned column of text that contains identifiers with multiple components, such as \"Assets:US:Bank:Checking\", and replace those by a tree-like structure rendered in ASCII, inserting new empty lines where necessary to create the tree. Note: If your paths have spaces in them, this will not work. Space is used as a delimiter to detect the end of a column. You can customize the delimiter with an option.","title":"treeify"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.Node","text":"A node with a name attribute, a list of line numbers and a list of children (from its parent class).","title":"Node"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.Node.__repr__","text":"Return str(self). Source code in beancount/tools/treeify.py def __str__(self): return ''.format(self.name, [node.name for node in self])","title":"__repr__()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.create_tree","text":"Build up a tree from a list of matches. Parameters: column_matches \u2013 A list of (line-number, name) pairs. regexp_split \u2013 A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. Source code in beancount/tools/treeify.py def create_tree(column_matches, regexp_split): \"\"\"Build up a tree from a list of matches. Args: column_matches: A list of (line-number, name) pairs. regexp_split: A regular expression string, to use for splitting the names of components. Returns: An instance of Node, the root node of the created tree. \"\"\" root = Node('') for no, name in column_matches: parts = re.split(regexp_split, name) node = root for part in parts: last_node = node[-1] if node else None if last_node is None or last_node.name != part: last_node = Node(part) node.append(last_node) node = last_node node.nos.append(no) return root","title":"create_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.dump_tree","text":"Render a tree as a tree. Parameters: node \u2013 An instance of Node. file \u2013 A file object to write to. prefix \u2013 A prefix string for each of the lines of the children. Source code in beancount/tools/treeify.py def dump_tree(node, file=sys.stdout, prefix=''): \"\"\"Render a tree as a tree. Args: node: An instance of Node. file: A file object to write to. prefix: A prefix string for each of the lines of the children. \"\"\" file.write(prefix) file.write(node.name) file.write('\\n') for child in node: dump_tree(child, file, prefix + '... ')","title":"dump_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.enum_tree_by_input_line_num","text":"Accumulate the lines of a tree until a line number is found. Parameters: tree_lines \u2013 A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). Source code in beancount/tools/treeify.py def enum_tree_by_input_line_num(tree_lines): \"\"\"Accumulate the lines of a tree until a line number is found. Args: tree_lines: A list of lines as returned by render_tree. Yields: Pairs of (line number, list of (line, node)). \"\"\" pending = [] for first_line, cont_line, node in tree_lines: if not node.nos: pending.append((first_line, node)) else: line = first_line for no in node.nos: pending.append((line, node)) line = cont_line yield (no, pending) pending = [] if pending: yield (None, pending)","title":"enum_tree_by_input_line_num()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.find_column","text":"Find a valid column with hierarchical data in the text lines. Parameters: lines \u2013 A list of strings, the contents of the input. pattern \u2013 A regular expression for the hierarchical entries. delimiter \u2013 A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches \u2013 A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. Source code in beancount/tools/treeify.py def find_column(lines, pattern, delimiter): \"\"\"Find a valid column with hierarchical data in the text lines. Args: lines: A list of strings, the contents of the input. pattern: A regular expression for the hierarchical entries. delimiter: A regular expression that dictates how we detect the end of a column. Normally this is a single space. If the patterns contain spaces, you will need to increase this. Returns: A tuple of matches: A list of (line-number, name) tuples where 'name' is the hierarchical string to treeify and line-number is an integer, the line number where this applies. left: An integer, the leftmost column. right: An integer, the rightmost column. Note that not all line numbers may be present, so you may need to skip some. However, they are in guaranteed in sorted order. \"\"\" # A mapping of the line beginning position to its match object. beginnings = collections.defaultdict(list) pattern_and_whitespace = \"({})(?P{}.|$)\".format(pattern, delimiter) for no, line in enumerate(lines): for match in re.finditer(pattern_and_whitespace, line): beginnings[match.start()].append((no, line, match)) # For each potential column found, verify that it is valid. A valid column # will have the maximum of its content text not overlap with any of the # following text. We assume that a column will have been formatted to full # width and that no text following the line overlap with the column, even in # its trailing whitespace. # # In other words, the following example is a violation because \"10,990.74\" # overlaps with the end of \"Insurance\" and so this would not be recognized # as a valid column: # # Expenses:Food:Restaurant 10,990.74 USD # Expenses:Health:Dental:Insurance 208.80 USD # for leftmost_column, column_matches in sorted(beginnings.items()): # Compute the location of the rightmost column of text. rightmost_column = max(match.end(1) for _, _, match in column_matches) # Compute the leftmost location of the content following the column text # and past its whitespace. following_column = min(match.end() if match.group('ws') else 10000 for _, _, match in column_matches) if rightmost_column < following_column: # We process only the very first match. return_matches = [(no, match.group(1).rstrip()) for no, _, match in column_matches] return return_matches, leftmost_column, rightmost_column","title":"find_column()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify.render_tree","text":"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line \u2013 A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. Source code in beancount/tools/treeify.py def render_tree(root): \"\"\"Render a tree of nodes. Returns: A list of tuples of (first_line, continuation_line, node) where first_line: A string, the first line to render, which includes the account name. continuation_line: A string, further line to render if necessary. node: The Node instance which corresponds to this line. and an integer, the width of the new columns. \"\"\" # Compute all the lines ahead of time in order to calculate the width. lines = [] # Start with the root node. We push the constant prefix before this node, # the account name, and the RealAccount instance. We will maintain a stack # of children nodes to render. stack = [('', root.name, root, True)] while stack: prefix, name, node, is_last = stack.pop(-1) if node is root: # For the root node, we don't want to render any prefix. first = cont = '' else: # Compute the string that precedes the name directly and the one below # that for the continuation lines. # | # @@@ Bank1 <---------------- # @@@ | # | |-- Checking if is_last: first = prefix + PREFIX_LEAF_1 cont = prefix + PREFIX_LEAF_C else: first = prefix + PREFIX_CHILD_1 cont = prefix + PREFIX_CHILD_C # Compute the name to render for continuation lines. # | # |-- Bank1 # | @@@ <---------------- # | |-- Checking if len(node) > 0: cont_name = PREFIX_CHILD_C else: cont_name = PREFIX_LEAF_C # Add a line for this account. if not (node is root and not name): lines.append((first + name, cont + cont_name, node)) # Push the children onto the stack, being careful with ordering and # marking the last node as such. if node: child_items = reversed(node) child_iter = iter(child_items) child_node = next(child_iter) stack.append((cont, child_node.name, child_node, True)) for child_node in child_iter: stack.append((cont, child_node.name, child_node, False)) if not lines: return lines # Compute the maximum width of the lines and convert all of them to the same # maximal width. This makes it easy on the client. max_width = max(len(first_line) for first_line, _, __ in lines) line_format = '{{:{width}}}'.format(width=max_width) return [(line_format.format(first_line), line_format.format(cont_line), node) for (first_line, cont_line, node) in lines], max_width","title":"render_tree()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test","text":"Unit tests for treeify tool.","title":"treeify_test"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase","text":"","title":"TestTreeifyBase"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase.treeify","text":"Run treeify on the given string and assert no errors. Parameters: string \u2013 A string, the input contents to run on. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. Source code in beancount/tools/treeify_test.py def treeify(self, string, expect_errors=False, options=None): \"\"\"Run treeify on the given string and assert no errors. Args: string: A string, the input contents to run on. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The converted output string. This fails the test if there were any errors. \"\"\" returncode, output, errors = treeify(string, options) actual_errors = returncode != 0 or bool(errors) if actual_errors != expect_errors: if expect_errors: self.fail(\"Missing expected errors\") else: self.fail(\"Unexpected errors: {}\".format(errors)) return output","title":"treeify()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.TestTreeifyBase.treeify_equal","text":"Assert an expected treeification result. Parameters: string \u2013 An expected input contents string to treeify. expected \u2013 A string, the expected treeified output. expect_errors \u2013 A boolean, true if we should expect there to be errors. options \u2013 An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. Source code in beancount/tools/treeify_test.py def treeify_equal(self, string, expected, expect_errors=False, options=None): \"\"\"Assert an expected treeification result. Args: string: An expected input contents string to treeify. expected: A string, the expected treeified output. expect_errors: A boolean, true if we should expect there to be errors. options: An optional list of options for the subprogram. Returns: The actual output string. This fails the test if there the expected output differed from the actual. \"\"\" input_ = textwrap.dedent(string) output = self.treeify(input_, expect_errors, options) expected = textwrap.dedent(expected) if DEBUG: print('-(input)----------------------------------') print(input_) print('-(output)---------------------------------') print(output) print('-(expected)-------------------------------') print(expected) print('------------------------------------------') self.assertEqual(expected, output) return output","title":"treeify_equal()"},{"location":"api_reference/beancount.tools.html#beancount.tools.treeify_test.treeify","text":"Run treeify on the string. Parameters: string \u2013 The input string to feed treeify. options \u2013 An optional list of options for the subprogram. Returns: The treeified string. Source code in beancount/tools/treeify_test.py def treeify(string, options=None): \"\"\"Run treeify on the string. Args: string: The input string to feed treeify. options: An optional list of options for the subprogram. Returns: The treeified string. \"\"\" pipe = subprocess.Popen([PROGRAM] + (options or []), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, errors = pipe.communicate(string.encode('utf-8')) return (pipe.returncode, output.decode('utf-8') if output else '', errors.decode('utf-8') if errors else '')","title":"treeify()"},{"location":"api_reference/beancount.utils.html","text":"beancount.utils \uf0c1 Generic utility packages and functions. beancount.utils.bisect_key \uf0c1 A version of bisect that accepts a custom key function, like the sorting ones do. beancount.utils.bisect_key.bisect_left_with_key(sequence, value, key=None) \uf0c1 Find the last element before the given value in a sorted list. Parameters: sequence \u2013 A sorted sequence of elements. value \u2013 The value to search for. key \u2013 An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. Source code in beancount/utils/bisect_key.py def bisect_left_with_key(sequence, value, key=None): \"\"\"Find the last element before the given value in a sorted list. Args: sequence: A sorted sequence of elements. value: The value to search for. key: An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. \"\"\" # pylint: disable=invalid-name if key is None: key = lambda x: x # Identity. lo = 0 hi = len(sequence) while lo < hi: mid = (lo + hi) // 2 if key(sequence[mid]) < value: lo = mid + 1 else: hi = mid return lo beancount.utils.bisect_key.bisect_right_with_key(a, x, key, lo=0, hi=None) \uf0c1 Like bisect.bisect_right, but with a key lookup parameter. Parameters: a \u2013 The list to search in. x \u2013 The element to search for. key \u2013 A function, to extract the value from the list. lo \u2013 The smallest index to search. hi \u2013 The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. Source code in beancount/utils/bisect_key.py def bisect_right_with_key(a, x, key, lo=0, hi=None): \"\"\"Like bisect.bisect_right, but with a key lookup parameter. Args: a: The list to search in. x: The element to search for. key: A function, to extract the value from the list. lo: The smallest index to search. hi: The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. \"\"\" # pylint: disable=invalid-name if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 if x < key(a[mid]): hi = mid else: lo = mid+1 return lo beancount.utils.csv_utils \uf0c1 Utilities for reading and writing CSV files. beancount.utils.csv_utils.as_rows(string) \uf0c1 Split a string as rows of a CSV file. Parameters: string \u2013 A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. Source code in beancount/utils/csv_utils.py def as_rows(string): \"\"\"Split a string as rows of a CSV file. Args: string: A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. \"\"\" return list(csv.reader(io.StringIO(textwrap.dedent(string)))) beancount.utils.csv_utils.csv_clean_header(header_row) \uf0c1 Create a new class for the following rows from the header line. This both normalizes the header line and assign Parameters: header_row \u2013 A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. Source code in beancount/utils/csv_utils.py def csv_clean_header(header_row): \"\"\"Create a new class for the following rows from the header line. This both normalizes the header line and assign Args: header_row: A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. \"\"\" fieldnames = [] for index, column in enumerate(header_row): field = column.lower() field = re.sub(r'\\bp/l\\b', 'pnl', field) field = re.sub('[^a-z0-9]', '_', field) field = field.strip(' _') field = re.sub('__+', '_', field) if not field: field = 'col{}'.format(index) assert field not in fieldnames, field fieldnames.append(field) return fieldnames beancount.utils.csv_utils.csv_dict_reader(fileobj, **kw) \uf0c1 Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. Source code in beancount/utils/csv_utils.py def csv_dict_reader(fileobj, **kw): \"\"\"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. \"\"\" reader = csv.DictReader(fileobj, **kw) reader.fieldnames = csv_clean_header(reader.fieldnames) return reader beancount.utils.csv_utils.csv_split_sections(rows) \uf0c1 Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Parameters: rows \u2013 A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. Source code in beancount/utils/csv_utils.py def csv_split_sections(rows): \"\"\"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Args: rows: A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. \"\"\" sections = [] current_section = [] for row in rows: if row: current_section.append(row) else: sections.append(current_section) current_section = [] if current_section: sections.append(current_section) return sections beancount.utils.csv_utils.csv_split_sections_with_titles(rows) \uf0c1 Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Parameters: rows \u2013 A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). Source code in beancount/utils/csv_utils.py def csv_split_sections_with_titles(rows): \"\"\"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Args: rows: A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). \"\"\" sections_map = {} for index, section in enumerate(csv_split_sections(rows)): # Skip too short sections, cannot possibly be a title. if len(section) < 2: continue if len(section[0]) == 1 and len(section[1]) != 1: name = section[0][0] section = section[1:] else: name = 'Section {}'.format(index) sections_map[name] = section return sections_map beancount.utils.csv_utils.csv_tuple_reader(fileobj, **kw) \uf0c1 Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. Source code in beancount/utils/csv_utils.py def csv_tuple_reader(fileobj, **kw): \"\"\"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. \"\"\" reader = csv.reader(fileobj, **kw) ireader = iter(reader) fieldnames = csv_clean_header(next(ireader)) Tuple = collections.namedtuple('Row', fieldnames) for row in ireader: try: yield Tuple(*row) except TypeError: # If there's an error, it's usually from a line that has a 'END OF # LINE' marker at the end, or some comment line. assert len(row) in (0, 1) beancount.utils.csv_utils.iter_sections(fileobj, separating_predicate=None) \uf0c1 For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Parameters: fileobj \u2013 A file object to read from. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. Source code in beancount/utils/csv_utils.py def iter_sections(fileobj, separating_predicate=None): \"\"\"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Args: fileobj: A file object to read from. separating_predicate: A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) lineiter = iter(fileobj) for line in lineiter: if separating_predicate(line): iterator = itertools.chain((line,), iter_until_empty(lineiter, separating_predicate)) yield iterator for _ in iterator: pass beancount.utils.csv_utils.iter_until_empty(iterator, separating_predicate=None) \uf0c1 An iterator of lines that will stop at the first empty line. Parameters: iterator \u2013 An iterator of lines. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. Source code in beancount/utils/csv_utils.py def iter_until_empty(iterator, separating_predicate=None): \"\"\"An iterator of lines that will stop at the first empty line. Args: iterator: An iterator of lines. separating_predicate: A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) for line in iterator: if not separating_predicate(line): break yield line beancount.utils.date_utils \uf0c1 Parse the date from various formats. beancount.utils.date_utils.intimezone(tz_value) \uf0c1 Temporarily reset the value of TZ. This is used for testing. Parameters: tz_value ( str ) \u2013 The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. Source code in beancount/utils/date_utils.py @contextlib.contextmanager def intimezone(tz_value: str): \"\"\"Temporarily reset the value of TZ. This is used for testing. Args: tz_value: The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. \"\"\" tz_old = os.environ.get('TZ', None) os.environ['TZ'] = tz_value time.tzset() try: yield finally: if tz_old is None: del os.environ['TZ'] else: os.environ['TZ'] = tz_old time.tzset() beancount.utils.date_utils.iter_dates(start_date, end_date) \uf0c1 Yield all the dates between 'start_date' and 'end_date'. Parameters: start_date \u2013 An instance of datetime.date. end_date \u2013 An instance of datetime.date. Yields: Instances of datetime.date. Source code in beancount/utils/date_utils.py def iter_dates(start_date, end_date): \"\"\"Yield all the dates between 'start_date' and 'end_date'. Args: start_date: An instance of datetime.date. end_date: An instance of datetime.date. Yields: Instances of datetime.date. \"\"\" oneday = datetime.timedelta(days=1) date = start_date while date < end_date: yield date date += oneday beancount.utils.date_utils.next_month(date) \uf0c1 Compute the date at the beginning of the following month from the given date. Parameters: date \u2013 A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. Source code in beancount/utils/date_utils.py def next_month(date): \"\"\"Compute the date at the beginning of the following month from the given date. Args: date: A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. \"\"\" # Compute the date at the beginning of the following month. year = date.year month = date.month + 1 if date.month == 12: year += 1 month = 1 return datetime.date(year, month, 1) beancount.utils.date_utils.parse_date_liberally(string, parse_kwargs_dict=None) \uf0c1 Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Parameters: string \u2013 A string to parse. parse_kwargs_dict \u2013 Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. Source code in beancount/utils/date_utils.py def parse_date_liberally(string, parse_kwargs_dict=None): \"\"\"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Args: string: A string to parse. parse_kwargs_dict: Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. \"\"\" # At the moment, rely on the most excellent dateutil. if parse_kwargs_dict is None: parse_kwargs_dict = {} return dateutil.parser.parse(string, **parse_kwargs_dict).date() beancount.utils.date_utils.render_ofx_date(dtime) \uf0c1 Render a datetime to the OFX format. Parameters: dtime \u2013 A datetime.datetime instance. Returns: A string, rendered to milliseconds. Source code in beancount/utils/date_utils.py def render_ofx_date(dtime): \"\"\"Render a datetime to the OFX format. Args: dtime: A datetime.datetime instance. Returns: A string, rendered to milliseconds. \"\"\" return '{}.{:03d}'.format(dtime.strftime('%Y%m%d%H%M%S'), int(dtime.microsecond / 1000)) beancount.utils.defdict \uf0c1 An instance of collections.defaultdict whose factory accepts a key. Note: This really ought to be an enhancement to Python itself. I should bother adding this in eventually. beancount.utils.defdict.DefaultDictWithKey ( defaultdict ) \uf0c1 A version of defaultdict whose factory accepts the key as an argument. Note: collections.defaultdict would be improved by supporting this directly, this is a common occurrence. beancount.utils.defdict.ImmutableDictWithDefault ( dict ) \uf0c1 An immutable dict which returns a default value for missing keys. This differs from a defaultdict in that it does not insert a missing default value when one is materialized (from a missing fetch), and furthermore, the set method is make unavailable to prevent mutation beyond construction. beancount.utils.defdict.ImmutableDictWithDefault.__setitem__(self, key, value) special \uf0c1 Disallow mutating the dict in the usual way. Source code in beancount/utils/defdict.py def __setitem__(self, key, value): \"\"\"Disallow mutating the dict in the usual way.\"\"\" raise NotImplementedError beancount.utils.defdict.ImmutableDictWithDefault.get(self, key, _=None) \uf0c1 Return the value for key if key is in the dictionary, else default. Source code in beancount/utils/defdict.py def get(self, key, _=None): return self.__getitem__(key) beancount.utils.encryption \uf0c1 Support for encrypted tests. beancount.utils.encryption.is_encrypted_file(filename) \uf0c1 Return true if the given filename contains an encrypted file. Parameters: filename \u2013 A path string. Returns: A boolean, true if the file contains an encrypted file. Source code in beancount/utils/encryption.py def is_encrypted_file(filename): \"\"\"Return true if the given filename contains an encrypted file. Args: filename: A path string. Returns: A boolean, true if the file contains an encrypted file. \"\"\" _, ext = path.splitext(filename) if ext == '.gpg': return True if ext == '.asc': with open(filename) as encfile: head = encfile.read(1024) if re.search('--BEGIN PGP MESSAGE--', head): return True return False beancount.utils.encryption.is_gpg_installed() \uf0c1 Return true if GPG 1.4.x or 2.x are installed, which is what we use and support. Source code in beancount/utils/encryption.py def is_gpg_installed(): \"\"\"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support.\"\"\" try: pipe = subprocess.Popen(['gpg', '--version'], shell=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = pipe.communicate() version_text = out.decode('utf8') return pipe.returncode == 0 and re.match(r'gpg \\(GnuPG\\) (1\\.4|2)\\.', version_text) except OSError: return False beancount.utils.encryption.read_encrypted_file(filename) \uf0c1 Decrypt and read an encrypted file without temporary storage. Parameters: filename \u2013 A string, the path to the encrypted file. Returns: A string, the contents of the file. Exceptions: OSError \u2013 If we could not properly decrypt the file. Source code in beancount/utils/encryption.py def read_encrypted_file(filename): \"\"\"Decrypt and read an encrypted file without temporary storage. Args: filename: A string, the path to the encrypted file. Returns: A string, the contents of the file. Raises: OSError: If we could not properly decrypt the file. \"\"\" command = ['gpg', '--batch', '--decrypt', path.realpath(filename)] pipe = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) contents, errors = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Could not decrypt file ({}): {}\".format(pipe.returncode, errors.decode('utf8'))) return contents.decode('utf-8') beancount.utils.file_type \uf0c1 Code that can guess a MIME type for a filename. This attempts to identify the mime-type of a file suing The built-in mimetypes library, then python-magic (if available), and finally some custom mappers for datatypes used in financial downloads, such as Quicken files. beancount.utils.file_type.guess_file_type(filename) \uf0c1 Attempt to guess the type of the input file. Parameters: filename \u2013 A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. Source code in beancount/utils/file_type.py def guess_file_type(filename): \"\"\"Attempt to guess the type of the input file. Args: filename: A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. \"\"\" # Try the standard mimetypes association. filetype, _ = mimetypes.guess_type(filename, False) if filetype: return filetype # Try out some extra types that only we know about. for regexp, mimetype in EXTRA_FILE_TYPES: if regexp.match(filename): return mimetype # Try out libmagic, if it is installed. if magic: filetype = magic.from_file(filename, mime=True) if isinstance(filetype, bytes): filetype = filetype.decode('utf8') return filetype else: raise ValueError((\"Could not identify the type of file '{}'; \" \"try installing python-magic\").format(filename)) beancount.utils.file_utils \uf0c1 File utilities. beancount.utils.file_utils.chdir(directory) \uf0c1 Temporarily chdir to the given directory. Parameters: directory \u2013 The directory to switch do. Returns: A context manager which restores the cwd after running. Source code in beancount/utils/file_utils.py @contextlib.contextmanager def chdir(directory): \"\"\"Temporarily chdir to the given directory. Args: directory: The directory to switch do. Returns: A context manager which restores the cwd after running. \"\"\" cwd = os.getcwd() os.chdir(directory) try: yield cwd finally: os.chdir(cwd) beancount.utils.file_utils.find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)) \uf0c1 Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Parameters: fords \u2013 A list of strings, file or directory names. ignore_dirs \u2013 A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. Source code in beancount/utils/file_utils.py def find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)): \"\"\"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Args: fords: A list of strings, file or directory names. ignore_dirs: A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. \"\"\" if isinstance(fords, str): fords = [fords] assert isinstance(fords, (list, tuple)) for ford in fords: if path.isdir(ford): for root, dirs, filenames in os.walk(ford): dirs[:] = sorted(dirname for dirname in dirs if dirname not in ignore_dirs) for filename in sorted(filenames): if filename in ignore_files: continue yield path.join(root, filename) elif path.isfile(ford) or path.islink(ford): yield ford elif not path.exists(ford): logging.error(\"File or directory '{}' does not exist.\".format(ford)) beancount.utils.file_utils.guess_file_format(filename, default=None) \uf0c1 Guess the file format from the filename. Parameters: filename \u2013 A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. Source code in beancount/utils/file_utils.py def guess_file_format(filename, default=None): \"\"\"Guess the file format from the filename. Args: filename: A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. \"\"\" if filename: if filename.endswith('.txt') or filename.endswith('.text'): format = 'text' elif filename.endswith('.csv'): format = 'csv' elif filename.endswith('.html') or filename.endswith('.xhtml'): format = 'html' else: format = default else: format = default return format beancount.utils.file_utils.path_greedy_split(filename) \uf0c1 Split a path, returning the longest possible extension. Parameters: filename \u2013 A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). Source code in beancount/utils/file_utils.py def path_greedy_split(filename): \"\"\"Split a path, returning the longest possible extension. Args: filename: A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). \"\"\" basename = path.basename(filename) index = basename.find('.') if index == -1: extension = None else: extension = basename[index:] basename = basename[:index] return (path.join(path.dirname(filename), basename), extension) beancount.utils.file_utils.touch_file(filename, *otherfiles) \uf0c1 Touch a file and wait until its timestamp has been changed. Parameters: filename \u2013 A string path, the name of the file to touch. otherfiles \u2013 A list of other files to ensure the timestamp is beyond of. Source code in beancount/utils/file_utils.py def touch_file(filename, *otherfiles): \"\"\"Touch a file and wait until its timestamp has been changed. Args: filename: A string path, the name of the file to touch. otherfiles: A list of other files to ensure the timestamp is beyond of. \"\"\" # Note: You could set os.stat_float_times() but then the main function would # have to set that up as well. It doesn't help so much, however, since # filesystems tend to have low resolutions, e.g. one second. orig_mtime_ns = max(os.stat(minfile).st_mtime_ns for minfile in (filename,) + otherfiles) delay_secs = 0.05 while True: with open(filename, 'a'): os.utime(filename) time.sleep(delay_secs) new_stat = os.stat(filename) if new_stat.st_mtime_ns > orig_mtime_ns: break beancount.utils.import_utils \uf0c1 Utilities for importing symbols programmatically. beancount.utils.import_utils.import_symbol(dotted_name) \uf0c1 Import a symbol in an arbitrary module. Parameters: dotted_name \u2013 A dotted path to a symbol. Returns: The object referenced by the given name. Exceptions: ImportError \u2013 If the module not not be imported. AttributeError \u2013 If the symbol could not be found in the module. Source code in beancount/utils/import_utils.py def import_symbol(dotted_name): \"\"\"Import a symbol in an arbitrary module. Args: dotted_name: A dotted path to a symbol. Returns: The object referenced by the given name. Raises: ImportError: If the module not not be imported. AttributeError: If the symbol could not be found in the module. \"\"\" comps = dotted_name.split('.') module_name = '.'.join(comps[:-1]) symbol_name = comps[-1] module = importlib.import_module(module_name) return getattr(module, symbol_name) beancount.utils.invariants \uf0c1 Functions to register auxiliary functions on a class' methods to check for invariants. This is intended to be used in a test, whereby your test will setup a class to automatically run invariant verification functions before and after each function call, to ensure some extra sanity checks that wouldn't be used in non-tests. Example: Instrument the Inventory class with the check_inventory_invariants() function. def setUp(module): instrument_invariants(Inventory, check_inventory_invariants, check_inventory_invariants) def tearDown(module): uninstrument_invariants(Inventory) beancount.utils.invariants.instrument_invariants(klass, prefun, postfun) \uf0c1 Instrument the class 'klass' with pre/post invariant checker functions. Parameters: klass \u2013 A class object, whose methods to be instrumented. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants pre-call. Source code in beancount/utils/invariants.py def instrument_invariants(klass, prefun, postfun): \"\"\"Instrument the class 'klass' with pre/post invariant checker functions. Args: klass: A class object, whose methods to be instrumented. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants pre-call. \"\"\" instrumented = {} for attrname, object_ in klass.__dict__.items(): if attrname.startswith('_'): continue if not isinstance(object_, types.FunctionType): continue instrumented[attrname] = object_ setattr(klass, attrname, invariant_check(object_, prefun, postfun)) klass.__instrumented = instrumented beancount.utils.invariants.invariant_check(method, prefun, postfun) \uf0c1 Decorate a method with the pre/post invariant checkers. Parameters: method \u2013 An unbound method to instrument. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants post-call. Returns: An unbound method, decorated. Source code in beancount/utils/invariants.py def invariant_check(method, prefun, postfun): \"\"\"Decorate a method with the pre/post invariant checkers. Args: method: An unbound method to instrument. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants post-call. Returns: An unbound method, decorated. \"\"\" reentrant = [] def new_method(self, *args, **kw): reentrant.append(None) if len(reentrant) == 1: prefun(self) result = method(self, *args, **kw) if len(reentrant) == 1: postfun(self) reentrant.pop() return result return new_method beancount.utils.invariants.uninstrument_invariants(klass) \uf0c1 Undo the instrumentation for invariants. Parameters: klass \u2013 A class object, whose methods to be uninstrumented. Source code in beancount/utils/invariants.py def uninstrument_invariants(klass): \"\"\"Undo the instrumentation for invariants. Args: klass: A class object, whose methods to be uninstrumented. \"\"\" instrumented = getattr(klass, '__instrumented', None) if instrumented: for attrname, object_ in instrumented.items(): setattr(klass, attrname, object_) del klass.__instrumented beancount.utils.memo \uf0c1 Memoization utilities. beancount.utils.memo.memoize_recent_fileobj(function, cache_filename, expiration=None) \uf0c1 Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Parameters: function \u2013 A callable object. cache_filename \u2013 A string, the path to the database file to cache to. expiration \u2013 The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. Source code in beancount/utils/memo.py def memoize_recent_fileobj(function, cache_filename, expiration=None): \"\"\"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Args: function: A callable object. cache_filename: A string, the path to the database file to cache to. expiration: The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. \"\"\" urlcache = shelve.open(cache_filename, 'c') urlcache.lock = threading.Lock() # Note: 'shelve' is not thread-safe. @functools.wraps(function) def memoized(*args, **kw): # Encode the arguments, including a date string in order to invalidate # results over some time. md5 = hashlib.md5() md5.update(str(args).encode('utf-8')) md5.update(str(sorted(kw.items())).encode('utf-8')) hash_ = md5.hexdigest() time_now = now() try: with urlcache.lock: time_orig, contents = urlcache[hash_] if expiration is not None and (time_now - time_orig) > expiration: raise KeyError except KeyError: fileobj = function(*args, **kw) if fileobj: contents = fileobj.read() with urlcache.lock: urlcache[hash_] = (time_now, contents) else: contents = None return io.BytesIO(contents) if contents else None return memoized beancount.utils.memo.now() \uf0c1 Indirection on datetime.datetime.now() for testing. Source code in beancount/utils/memo.py def now(): \"Indirection on datetime.datetime.now() for testing.\" return datetime.datetime.now() beancount.utils.misc_utils \uf0c1 Generic utility packages and functions. beancount.utils.misc_utils.LineFileProxy \uf0c1 A file object that will delegate writing full lines to another logging function. This may be used for writing data to a logging level without having to worry about lines. beancount.utils.misc_utils.LineFileProxy.__init__(self, line_writer, prefix=None, write_newlines=False) special \uf0c1 Construct a new line delegator file object proxy. Parameters: line_writer \u2013 A callable function, used to write to the delegated output. prefix \u2013 An optional string, the prefix to insert before every line. write_newlines \u2013 A boolean, true if we should output the newline characters. Source code in beancount/utils/misc_utils.py def __init__(self, line_writer, prefix=None, write_newlines=False): \"\"\"Construct a new line delegator file object proxy. Args: line_writer: A callable function, used to write to the delegated output. prefix: An optional string, the prefix to insert before every line. write_newlines: A boolean, true if we should output the newline characters. \"\"\" self.line_writer = line_writer self.prefix = prefix self.write_newlines = write_newlines self.data = [] beancount.utils.misc_utils.LineFileProxy.close(self) \uf0c1 Close the line delegator. Source code in beancount/utils/misc_utils.py def close(self): \"\"\"Close the line delegator.\"\"\" self.flush() beancount.utils.misc_utils.LineFileProxy.flush(self) \uf0c1 Flush the data to the line writer. Source code in beancount/utils/misc_utils.py def flush(self): \"\"\"Flush the data to the line writer.\"\"\" data = ''.join(self.data) if data: lines = data.splitlines() self.data = [lines.pop(-1)] if data[-1] != '\\n' else [] for line in lines: if self.prefix: line = self.prefix + line if self.write_newlines: line += '\\n' self.line_writer(line) beancount.utils.misc_utils.LineFileProxy.write(self, data) \uf0c1 Write some string data to the output. Parameters: data \u2013 A string, with or without newlines. Source code in beancount/utils/misc_utils.py def write(self, data): \"\"\"Write some string data to the output. Args: data: A string, with or without newlines. \"\"\" if '\\n' in data: self.data.append(data) self.flush() else: self.data.append(data) beancount.utils.misc_utils.TypeComparable \uf0c1 A base class whose equality comparison includes comparing the type of the instance itself. beancount.utils.misc_utils.box(name=None, file=None) \uf0c1 A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Parameters: name \u2013 A string, the name of the box to use. file \u2013 The file object to print to. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def box(name=None, file=None): \"\"\"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Args: name: A string, the name of the box to use. file: The file object to print to. Yields: None. \"\"\" file = file or sys.stdout file.write('\\n') if name: header = ',--------({})--------\\n'.format(name) footer = '`{}\\n'.format('-' * (len(header)-2)) else: header = ',----------------\\n' footer = '`----------------\\n' file.write(header) yield file.write(footer) file.flush() beancount.utils.misc_utils.cmptuple(name, attributes) \uf0c1 Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Parameters: name \u2013 The given name of the class. attributes \u2013 A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. Source code in beancount/utils/misc_utils.py def cmptuple(name, attributes): \"\"\"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Args: name: The given name of the class. attributes: A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. \"\"\" base = collections.namedtuple('_{}'.format(name), attributes) return type(name, (TypeComparable, base,), {}) beancount.utils.misc_utils.compute_unique_clean_ids(strings) \uf0c1 Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Parameters: strings \u2013 A list of strings. Returns: A list of (id, string) pairs. Source code in beancount/utils/misc_utils.py def compute_unique_clean_ids(strings): \"\"\"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Args: strings: A list of strings. Returns: A list of (id, string) pairs. \"\"\" string_set = set(strings) # Try multiple methods until we get one that has no collisions. for regexp, replacement in [(r'[^A-Za-z0-9.-]', '_'), (r'[^A-Za-z0-9_]', ''),]: seen = set() idmap = {} mre = re.compile(regexp) for string in string_set: id_ = mre.sub(replacement, string) if id_ in seen: break # Collision. seen.add(id_) idmap[id_] = string else: break else: return None # Could not find a unique mapping. return idmap beancount.utils.misc_utils.deprecated(message) \uf0c1 A decorator generator to mark functions as deprecated and log a warning. Source code in beancount/utils/misc_utils.py def deprecated(message): \"\"\"A decorator generator to mark functions as deprecated and log a warning.\"\"\" def decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn(\"Call to deprecated function {}: {}\".format(func.__name__, message), category=DeprecationWarning, stacklevel=2) return func(*args, **kwargs) return new_func return decorator beancount.utils.misc_utils.dictmap(mdict, keyfun=None, valfun=None) \uf0c1 Map a dictionary's value. Parameters: mdict \u2013 A dict. key \u2013 A callable to apply to the keys. value \u2013 A callable to apply to the values. Source code in beancount/utils/misc_utils.py def dictmap(mdict, keyfun=None, valfun=None): \"\"\"Map a dictionary's value. Args: mdict: A dict. key: A callable to apply to the keys. value: A callable to apply to the values. \"\"\" if keyfun is None: keyfun = lambda x: x if valfun is None: valfun = lambda x: x return {keyfun(key): valfun(val) for key, val in mdict.items()} beancount.utils.misc_utils.escape_string(string) \uf0c1 Escape quotes and backslashes in payee and narration. Parameters: string \u2013 Any string. Returns. The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def escape_string(string): \"\"\"Escape quotes and backslashes in payee and narration. Args: string: Any string. Returns. The input string, with offending characters replaced. \"\"\" return string.replace('\\\\', r'\\\\')\\ .replace('\"', r'\\\"') beancount.utils.misc_utils.filter_type(elist, types) \uf0c1 Filter the given list to yield only instances of the given types. Parameters: elist \u2013 A sequence of elements. types \u2013 A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. Source code in beancount/utils/misc_utils.py def filter_type(elist, types): \"\"\"Filter the given list to yield only instances of the given types. Args: elist: A sequence of elements. types: A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. \"\"\" for element in elist: if not isinstance(element, types): continue yield element beancount.utils.misc_utils.first_paragraph(docstring) \uf0c1 Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Parameters: docstring \u2013 A doc string. Returns: A string with just the first sentence on a single line. Source code in beancount/utils/misc_utils.py def first_paragraph(docstring): \"\"\"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Args: docstring: A doc string. Returns: A string with just the first sentence on a single line. \"\"\" lines = [] for line in docstring.strip().splitlines(): if not line: break lines.append(line.rstrip()) return ' '.join(lines) beancount.utils.misc_utils.get_screen_height() \uf0c1 Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_height(): \"\"\"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('lines', 0) beancount.utils.misc_utils.get_screen_width() \uf0c1 Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_width(): \"\"\"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('cols', 0) beancount.utils.misc_utils.get_tuple_values(ntuple, predicate, memo=None) \uf0c1 Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Parameters: ntuple \u2013 A tuple or namedtuple. predicate \u2013 A predicate function that returns true if an attribute is to be output. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def get_tuple_values(ntuple, predicate, memo=None): \"\"\"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Args: ntuple: A tuple or namedtuple. predicate: A predicate function that returns true if an attribute is to be output. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return memo.add(id_ntuple) if predicate(ntuple): yield for attribute in ntuple: if predicate(attribute): yield attribute if isinstance(attribute, (list, tuple)): for value in get_tuple_values(attribute, predicate, memo): yield value beancount.utils.misc_utils.groupby(keyfun, elements) \uf0c1 Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Parameters: keyfun \u2013 A callable, used to obtain the group key from each element. elements \u2013 An iterable of the elements to group. Returns: A dict of key to list of sequences. Source code in beancount/utils/misc_utils.py def groupby(keyfun, elements): \"\"\"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Args: keyfun: A callable, used to obtain the group key from each element. elements: An iterable of the elements to group. Returns: A dict of key to list of sequences. \"\"\" # Note: We could allow a custom aggregation function. Another option is # provide another method to reduce the list values of a dict, but that can # be accomplished using a dict comprehension. grouped = defaultdict(list) for element in elements: grouped[keyfun(element)].append(element) return grouped beancount.utils.misc_utils.idify(string) \uf0c1 Replace characters objectionable for a filename with underscores. Parameters: string \u2013 Any string. Returns: The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def idify(string): \"\"\"Replace characters objectionable for a filename with underscores. Args: string: Any string. Returns: The input string, with offending characters replaced. \"\"\" for sfrom, sto in [(r'[ \\(\\)]+', '_'), (r'_*\\._*', '.')]: string = re.sub(sfrom, sto, string) string = string.strip('_') return string beancount.utils.misc_utils.import_curses() \uf0c1 Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Exceptions: ImportError \u2013 If the module could not be imported. Source code in beancount/utils/misc_utils.py def import_curses(): \"\"\"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Raises: ImportError: If the module could not be imported. \"\"\" # Note: There's a recipe for getting terminal size on Windows here, without # curses, I should probably implement that at some point: # https://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window # Also, consider just using 'blessings' instead, which provides this across # multiple platforms. # pylint: disable=import-outside-toplevel import curses return curses beancount.utils.misc_utils.is_sorted(iterable, key= at 0x749446c30cc0>, cmp= at 0x749446c30d60>) \uf0c1 Return true if the sequence is sorted. Parameters: iterable \u2013 An iterable sequence. key \u2013 A function to extract the quantity by which to sort. cmp \u2013 A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. Source code in beancount/utils/misc_utils.py def is_sorted(iterable, key=lambda x: x, cmp=lambda x, y: x <= y): \"\"\"Return true if the sequence is sorted. Args: iterable: An iterable sequence. key: A function to extract the quantity by which to sort. cmp: A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. \"\"\" iterator = map(key, iterable) prev = next(iterator) for element in iterator: if not cmp(prev, element): return False prev = element return True beancount.utils.misc_utils.log_time(operation_name, log_timings, indent=0) \uf0c1 A context manager that times the block and logs it to info level. Parameters: operation_name \u2013 A string, a label for the name of the operation. log_timings \u2013 A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent \u2013 An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def log_time(operation_name, log_timings, indent=0): \"\"\"A context manager that times the block and logs it to info level. Args: operation_name: A string, a label for the name of the operation. log_timings: A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent: An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. \"\"\" time1 = time() yield time1 time2 = time() if log_timings: log_timings(\"Operation: {:48} Time: {}{:6.0f} ms\".format( \"'{}'\".format(operation_name), ' '*indent, (time2 - time1) * 1000)) beancount.utils.misc_utils.longest(seq) \uf0c1 Return the longest of the given subsequences. Parameters: seq \u2013 An iterable sequence of lists. Returns: The longest list from the sequence. Source code in beancount/utils/misc_utils.py def longest(seq): \"\"\"Return the longest of the given subsequences. Args: seq: An iterable sequence of lists. Returns: The longest list from the sequence. \"\"\" longest, length = None, -1 for element in seq: len_element = len(element) if len_element > length: longest, length = element, len_element return longest beancount.utils.misc_utils.map_namedtuple_attributes(attributes, mapper, object_) \uf0c1 Map the value of the named attributes of object by mapper. Parameters: attributes \u2013 A sequence of string, the attribute names to map. mapper \u2013 A callable that accepts the value of a field and returns the new value. object_ \u2013 Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. Source code in beancount/utils/misc_utils.py def map_namedtuple_attributes(attributes, mapper, object_): \"\"\"Map the value of the named attributes of object by mapper. Args: attributes: A sequence of string, the attribute names to map. mapper: A callable that accepts the value of a field and returns the new value. object_: Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. \"\"\" return object_._replace(**{attribute: mapper(getattr(object_, attribute)) for attribute in attributes}) beancount.utils.misc_utils.replace_namedtuple_values(ntuple, predicate, mapper, memo=None) \uf0c1 Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Parameters: ntuple \u2013 A namedtuple instance. predicate \u2013 A predicate function that returns true if an attribute is to be output. mapper \u2013 A callable, that will accept a single argument and return its replacement value. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def replace_namedtuple_values(ntuple, predicate, mapper, memo=None): \"\"\"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Args: ntuple: A namedtuple instance. predicate: A predicate function that returns true if an attribute is to be output. mapper: A callable, that will accept a single argument and return its replacement value. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return None memo.add(id_ntuple) # pylint: disable=unidiomatic-typecheck if not (type(ntuple) is not tuple and isinstance(ntuple, tuple)): return ntuple replacements = {} for attribute_name, attribute in zip(ntuple._fields, ntuple): if predicate(attribute): replacements[attribute_name] = mapper(attribute) elif type(attribute) is not tuple and isinstance(attribute, tuple): replacements[attribute_name] = replace_namedtuple_values( attribute, predicate, mapper, memo) elif type(attribute) in (list, tuple): replacements[attribute_name] = [ replace_namedtuple_values(member, predicate, mapper, memo) for member in attribute] return ntuple._replace(**replacements) beancount.utils.misc_utils.skipiter(iterable, num_skip) \uf0c1 Skip some elements from an iterator. Parameters: iterable \u2013 An iterator. num_skip \u2013 The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. Source code in beancount/utils/misc_utils.py def skipiter(iterable, num_skip): \"\"\"Skip some elements from an iterator. Args: iterable: An iterator. num_skip: The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. \"\"\" assert num_skip > 0 sit = iter(iterable) while 1: try: value = next(sit) except StopIteration: return yield value for _ in range(num_skip-1): try: next(sit) except StopIteration: return beancount.utils.misc_utils.sorted_uniquify(iterable, keyfunc=None, last=False) \uf0c1 Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does not maintain the ordering of the original elements, they are returned sorted (by key) instead. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def sorted_uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does _not_ maintain the ordering of the original elements, they are returned sorted (by key) instead. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x if last: prev_obj = UNSET prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key and prev_obj is not UNSET: yield prev_obj prev_obj = obj prev_key = key if prev_obj is not UNSET: yield prev_obj else: prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key: yield obj prev_key = key beancount.utils.misc_utils.staticvar(varname, initial_value) \uf0c1 Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Parameters: varname \u2013 A string, the name of the variable to define. initial_value \u2013 The value to initialize the variable to. Returns: A function decorator. Source code in beancount/utils/misc_utils.py def staticvar(varname, initial_value): \"\"\"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Args: varname: A string, the name of the variable to define. initial_value: The value to initialize the variable to. Returns: A function decorator. \"\"\" def deco(fun): setattr(fun, varname, initial_value) return fun return deco beancount.utils.misc_utils.swallow(*exception_types) \uf0c1 Catch and ignore certain exceptions. Parameters: exception_types \u2013 A tuple of exception classes to ignore. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def swallow(*exception_types): \"\"\"Catch and ignore certain exceptions. Args: exception_types: A tuple of exception classes to ignore. Yields: None. \"\"\" try: yield except Exception as exc: if not isinstance(exc, exception_types): raise beancount.utils.misc_utils.uniquify(iterable, keyfunc=None, last=False) \uf0c1 Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x seen = set() if last: unique_reversed_list = [] for obj in reversed(iterable): key = keyfunc(obj) if key not in seen: seen.add(key) unique_reversed_list.append(obj) yield from reversed(unique_reversed_list) else: for obj in iterable: key = keyfunc(obj) if key not in seen: seen.add(key) yield obj beancount.utils.net_utils \uf0c1 Network utilities. beancount.utils.net_utils.retrying_urlopen(url, timeout=5, max_retry=5) \uf0c1 Open and download the given URL, retrying if it times out. Parameters: url \u2013 A string, the URL to fetch. timeout \u2013 A timeout after which to stop waiting for a response and return an error. max_retry \u2013 The maximum number of times to retry. Returns: The contents of the fetched URL. Source code in beancount/utils/net_utils.py def retrying_urlopen(url, timeout=5, max_retry=5): \"\"\"Open and download the given URL, retrying if it times out. Args: url: A string, the URL to fetch. timeout: A timeout after which to stop waiting for a response and return an error. max_retry: The maximum number of times to retry. Returns: The contents of the fetched URL. \"\"\" for _ in range(max_retry): logging.debug(\"Reading %s\", url) try: response = request.urlopen(url, timeout=timeout) if response: break except error.URLError: return None if response and response.getcode() != 200: return None return response beancount.utils.pager \uf0c1 Code to write output to a pager. This module contains an object accumulates lines up to a minimum and then decides whether to flush them to the original output directly if under the threshold (no pager) or creates a pager and flushes the lines to it if above the threshold and then forwards all future lines to it. The purpose of this object is to pipe output to a pager only if the number of lines to be printed exceeds a minimum number of lines. The contextmanager is intended to be used to pipe output to a pager and wait on the pager to complete before continuing. Simply write to the file object and upon exit we close the file object. This also silences broken pipe errors triggered by the user exiting the sub-process, and recovers from a failing pager command by just using stdout. beancount.utils.pager.ConditionalPager \uf0c1 A proxy file for a pager that only creates a pager after a minimum number of lines has been printed to it. beancount.utils.pager.ConditionalPager.__enter__(self) special \uf0c1 Initialize the context manager and return this instance as it. Source code in beancount/utils/pager.py def __enter__(self): \"\"\"Initialize the context manager and return this instance as it.\"\"\" # The file and pipe object we're writing to. This gets set after the # number of accumulated lines reaches the threshold. if self.minlines: self.file = None self.pipe = None else: self.file, self.pipe = create_pager(self.command, self.default_file) # Lines accumulated before the threshold. self.accumulated_data = [] self.accumulated_lines = 0 # Return this object to be used as the context manager itself. return self beancount.utils.pager.ConditionalPager.__exit__(self, type, value, unused_traceback) special \uf0c1 Context manager exit. This flushes the output to our output file. Parameters: type \u2013 Optional exception type, as per context managers. value \u2013 Optional exception value, as per context managers. unused_traceback \u2013 Optional trace. Source code in beancount/utils/pager.py def __exit__(self, type, value, unused_traceback): \"\"\"Context manager exit. This flushes the output to our output file. Args: type: Optional exception type, as per context managers. value: Optional exception value, as per context managers. unused_traceback: Optional trace. \"\"\" try: if self.file: # Flush the output file and close it. self.file.flush() else: # Oops... we never reached the threshold. Flush the accumulated # output to the file. self.flush_accumulated(self.default_file) # Wait for the subprocess (if we have one). if self.pipe: self.file.close() self.pipe.wait() # Absorb broken pipes that may occur on flush or close above. except BrokenPipeError: return True # Absorb broken pipes. if isinstance(value, BrokenPipeError): return True elif value: raise beancount.utils.pager.ConditionalPager.__init__(self, command, minlines=None) special \uf0c1 Create a conditional pager. Parameters: command \u2013 A string, the shell command to run as a pager. minlines \u2013 If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). Source code in beancount/utils/pager.py def __init__(self, command, minlines=None): \"\"\"Create a conditional pager. Args: command: A string, the shell command to run as a pager. minlines: If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). \"\"\" self.command = command self.minlines = minlines self.default_file = (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout) beancount.utils.pager.ConditionalPager.flush_accumulated(self, file) \uf0c1 Flush the existing lines to the newly created pager. This also disabled the accumulator. Parameters: file \u2013 A file object to flush the accumulated data to. Source code in beancount/utils/pager.py def flush_accumulated(self, file): \"\"\"Flush the existing lines to the newly created pager. This also disabled the accumulator. Args: file: A file object to flush the accumulated data to. \"\"\" if self.accumulated_data: write = file.write for data in self.accumulated_data: write(data) self.accumulated_data = None self.accumulated_lines = None beancount.utils.pager.ConditionalPager.write(self, data) \uf0c1 Write the data out. Overridden from the file object interface. Parameters: data \u2013 A string, data to write to the output. Source code in beancount/utils/pager.py def write(self, data): \"\"\"Write the data out. Overridden from the file object interface. Args: data: A string, data to write to the output. \"\"\" if self.file is None: # Accumulate the new lines. self.accumulated_lines += data.count('\\n') self.accumulated_data.append(data) # If we've reached the threshold, create a file. if self.accumulated_lines > self.minlines: self.file, self.pipe = create_pager(self.command, self.default_file) self.flush_accumulated(self.file) else: # We've already created a pager subprocess... flush the lines to it. self.file.write(data) # try: # except BrokenPipeError: # # Make sure we don't barf on __exit__(). # self.file = self.pipe = None # raise beancount.utils.pager.create_pager(command, file) \uf0c1 Try to create and return a pager subprocess. Parameters: command \u2013 A string, the shell command to run as a pager. file \u2013 The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. Source code in beancount/utils/pager.py def create_pager(command, file): \"\"\"Try to create and return a pager subprocess. Args: command: A string, the shell command to run as a pager. file: The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. \"\"\" if command is None: command = os.environ.get('PAGER', DEFAULT_PAGER) if not command: command = DEFAULT_PAGER pipe = None # In case of using 'less', make sure the charset is set properly. In theory # you could override this by setting PAGER to \"LESSCHARSET=utf-8 less\" but # this shouldn't affect other programs and is unlikely to cause problems, so # we set it here to make default behavior work for most people (we always # write UTF-8). env = os.environ.copy() env['LESSCHARSET'] = \"utf-8\" try: pipe = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=file, env=env) except OSError as exc: logging.error(\"Invalid pager: {}\".format(exc)) else: stdin_wrapper = io.TextIOWrapper(pipe.stdin, 'utf-8') file = stdin_wrapper return file, pipe beancount.utils.pager.flush_only(fileobj) \uf0c1 A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Parameters: fileobj \u2013 A file object, to remain open after running the context manager. Yields: A context manager that yields this object. Source code in beancount/utils/pager.py @contextlib.contextmanager def flush_only(fileobj): \"\"\"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Args: fileobj: A file object, to remain open after running the context manager. Yields: A context manager that yields this object. \"\"\" try: yield fileobj finally: fileobj.flush() beancount.utils.regexp_utils \uf0c1 Regular expression helpers. beancount.utils.regexp_utils.re_replace_unicode(regexp) \uf0c1 Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Parameters: regexp \u2013 A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. Source code in beancount/utils/regexp_utils.py def re_replace_unicode(regexp): \"\"\"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Args: regexp: A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. \"\"\" for category, rangestr in UNICODE_RANGES.items(): regexp = regexp.replace(r'\\p{' + category + '}', rangestr) return regexp beancount.utils.snoop \uf0c1 Text manipulation utilities. beancount.utils.snoop.Snoop \uf0c1 A snooper callable that just saves the returned values of a function. This is particularly useful for re.match and re.search in conditionals, e.g.:: snoop = Snoop() ... if snoop(re.match(r\"(\\d+)-(\\d+)-(\\d+)\", text)): year, month, date = snoop.value.group(1, 2, 3) Attributes: Name Type Description value The last value snooped from a function call. history If 'maxlen' was specified, the last few values that were snooped. beancount.utils.snoop.Snoop.__call__(self, value) special \uf0c1 Save a value to the snooper. This is meant to wrap a function call. Parameters: value \u2013 The value to push/save. Returns: Value itself. Source code in beancount/utils/snoop.py def __call__(self, value): \"\"\"Save a value to the snooper. This is meant to wrap a function call. Args: value: The value to push/save. Returns: Value itself. \"\"\" self.value = value if self.history is not None: self.history.append(value) return value beancount.utils.snoop.Snoop.__getattr__(self, attr) special \uf0c1 Forward the attribute to the value. Parameters: attr \u2013 A string, the name of the attribute. Returns: The value of the attribute. Source code in beancount/utils/snoop.py def __getattr__(self, attr): \"\"\"Forward the attribute to the value. Args: attr: A string, the name of the attribute. Returns: The value of the attribute. \"\"\" return getattr(self.value, attr) beancount.utils.snoop.Snoop.__init__(self, maxlen=None) special \uf0c1 Create a new snooper. Parameters: maxlen \u2013 If specified, an integer, which enables the saving of that Source code in beancount/utils/snoop.py def __init__(self, maxlen=None): \"\"\"Create a new snooper. Args: maxlen: If specified, an integer, which enables the saving of that number of last values in the history attribute. \"\"\" self.value = None self.history = (collections.deque(maxlen=maxlen) if maxlen else None) beancount.utils.snoop.snoopify(function) \uf0c1 Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. Source code in beancount/utils/snoop.py def snoopify(function): \"\"\"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. \"\"\" @functools.wraps(function) def wrapper(*args, **kw): value = function(*args, **kw) wrapper.value = value return value wrapper.value = None return wrapper beancount.utils.test_utils \uf0c1 Support utilities for testing scripts. beancount.utils.test_utils.RCall ( tuple ) \uf0c1 RCall(args, kwargs, return_value) beancount.utils.test_utils.RCall.__getnewargs__(self) special \uf0c1 Return self as a plain tuple. Used by copy and pickle. Source code in beancount/utils/test_utils.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self) beancount.utils.test_utils.RCall.__new__(_cls, args, kwargs, return_value) special staticmethod \uf0c1 Create new instance of RCall(args, kwargs, return_value) beancount.utils.test_utils.RCall.__repr__(self) special \uf0c1 Return a nicely formatted representation string Source code in beancount/utils/test_utils.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self beancount.utils.test_utils.TestCase ( TestCase ) \uf0c1 beancount.utils.test_utils.TestCase.assertLines(self, text1, text2, message=None) \uf0c1 Compare the lines of text1 and text2, ignoring whitespace. Parameters: text1 \u2013 A string, the expected text. text2 \u2013 A string, the actual text. message \u2013 An optional string message in case the assertion fails. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/utils/test_utils.py def assertLines(self, text1, text2, message=None): \"\"\"Compare the lines of text1 and text2, ignoring whitespace. Args: text1: A string, the expected text. text2: A string, the actual text. message: An optional string message in case the assertion fails. Raises: AssertionError: If the exception fails. \"\"\" clean_text1 = textwrap.dedent(text1.strip()) clean_text2 = textwrap.dedent(text2.strip()) lines1 = [line.strip() for line in clean_text1.splitlines()] lines2 = [line.strip() for line in clean_text2.splitlines()] # Compress all space longer than 4 spaces to exactly 4. # This affords us to be even looser. lines1 = [re.sub(' [ \\t]*', ' ', line) for line in lines1] lines2 = [re.sub(' [ \\t]*', ' ', line) for line in lines2] self.assertEqual(lines1, lines2, message) beancount.utils.test_utils.TestCase.assertOutput(self, expected_text) \uf0c1 Expect text printed to stdout. Parameters: expected_text \u2013 A string, the text that should have been printed to stdout. Exceptions: AssertionError \u2013 If the text differs. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def assertOutput(self, expected_text): \"\"\"Expect text printed to stdout. Args: expected_text: A string, the text that should have been printed to stdout. Raises: AssertionError: If the text differs. \"\"\" with capture() as oss: yield oss self.assertLines(textwrap.dedent(expected_text), oss.getvalue()) beancount.utils.test_utils.call_command(command) \uf0c1 Run the script with a subprocess. Parameters: script_args \u2013 A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). Source code in beancount/utils/test_utils.py def call_command(command): \"\"\"Run the script with a subprocess. Args: script_args: A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). \"\"\" assert isinstance(command, list), command p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return p.returncode, stdout.decode(), stderr.decode() beancount.utils.test_utils.capture(*attributes) \uf0c1 A context manager that captures what's printed to stdout. Parameters: *attributes \u2013 A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. Source code in beancount/utils/test_utils.py def capture(*attributes): \"\"\"A context manager that captures what's printed to stdout. Args: *attributes: A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. \"\"\" if not attributes: attributes = 'stdout' elif len(attributes) == 1: attributes = attributes[0] return patch(sys, attributes, io.StringIO) beancount.utils.test_utils.create_temporary_files(root, contents_map) \uf0c1 Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Parameters: root \u2013 A string, the name of the directory under which to create the files. contents_map \u2013 A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. Source code in beancount/utils/test_utils.py def create_temporary_files(root, contents_map): \"\"\"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Args: root: A string, the name of the directory under which to create the files. contents_map: A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. \"\"\" os.makedirs(root, exist_ok=True) for relative_filename, contents in contents_map.items(): assert not path.isabs(relative_filename) filename = path.join(root, relative_filename) os.makedirs(path.dirname(filename), exist_ok=True) clean_contents = textwrap.dedent(contents.replace('{root}', root)) with open(filename, 'w') as f: f.write(clean_contents) beancount.utils.test_utils.docfile(function, **kwargs) \uf0c1 A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Parameters: function \u2013 A function to decorate. Returns: The decorated function. Source code in beancount/utils/test_utils.py def docfile(function, **kwargs): \"\"\"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Args: function: A function to decorate. Returns: The decorated function. \"\"\" @functools.wraps(function) def new_function(self): allowed = ('buffering', 'encoding', 'newline', 'dir', 'prefix', 'suffix') if any([key not in allowed for key in kwargs]): raise ValueError(\"Invalid kwarg to docfile_extra\") with tempfile.NamedTemporaryFile('w', **kwargs) as file: text = function.__doc__ file.write(textwrap.dedent(text)) file.flush() return function(self, file.name) new_function.__doc__ = None return new_function beancount.utils.test_utils.docfile_extra(**kwargs) \uf0c1 A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile Source code in beancount/utils/test_utils.py def docfile_extra(**kwargs): \"\"\" A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile \"\"\" return functools.partial(docfile, **kwargs) beancount.utils.test_utils.environ(varname, newvalue) \uf0c1 A context manager which pushes varname's value and restores it later. Parameters: varname \u2013 A string, the environ variable name. newvalue \u2013 A string, the desired value. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def environ(varname, newvalue): \"\"\"A context manager which pushes varname's value and restores it later. Args: varname: A string, the environ variable name. newvalue: A string, the desired value. \"\"\" oldvalue = os.environ.get(varname, None) os.environ[varname] = newvalue yield if oldvalue is not None: os.environ[varname] = oldvalue else: del os.environ[varname] beancount.utils.test_utils.find_python_lib() \uf0c1 Return the path to the root of the Python libraries. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_python_lib(): \"\"\"Return the path to the root of the Python libraries. Returns: A string, the root directory. \"\"\" return path.dirname(path.dirname(path.dirname(__file__))) beancount.utils.test_utils.find_repository_root(filename=None) \uf0c1 Return the path to the repository root. Parameters: filename \u2013 A string, the name of a file within the repository. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_repository_root(filename=None): \"\"\"Return the path to the repository root. Args: filename: A string, the name of a file within the repository. Returns: A string, the root directory. \"\"\" if filename is None: filename = __file__ # Support root directory under Bazel. match = re.match(r\"(.*\\.runfiles/beancount)/\", filename) if match: return match.group(1) while not all(path.exists(path.join(filename, sigfile)) for sigfile in ('PKG-INFO', 'COPYING', 'README.rst')): prev_filename = filename filename = path.dirname(filename) if prev_filename == filename: raise ValueError(\"Failed to find the root directory.\") return filename beancount.utils.test_utils.make_failing_importer(*removed_module_names) \uf0c1 Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins. import ', make_failing_importer('setuptools')) def test_... Parameters: removed_module_name \u2013 The name of the module import that should raise an exception. Returns: A decorated test decorator. Source code in beancount/utils/test_utils.py def make_failing_importer(*removed_module_names): \"\"\"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins.__import__', make_failing_importer('setuptools')) def test_... Args: removed_module_name: The name of the module import that should raise an exception. Returns: A decorated test decorator. \"\"\" def failing_import(name, *args, **kwargs): if name in removed_module_names: raise ImportError(\"Could not import {}\".format(name)) return builtins.__import__(name, *args, **kwargs) return failing_import beancount.utils.test_utils.nottest(func) \uf0c1 Make the given function not testable. Source code in beancount/utils/test_utils.py def nottest(func): \"Make the given function not testable.\" func.__test__ = False return func beancount.utils.test_utils.patch(obj, attributes, replacement_type) \uf0c1 A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Parameters: obj \u2013 The object to patch up. attributes \u2013 A string or a sequence of strings, the names of attributes to replace. replacement_type \u2013 A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def patch(obj, attributes, replacement_type): \"\"\"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Args: obj: The object to patch up. attributes: A string or a sequence of strings, the names of attributes to replace. replacement_type: A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. \"\"\" single = isinstance(attributes, str) if single: attributes = [attributes] saved = [] replacements = [] for attribute in attributes: replacement = replacement_type() replacements.append(replacement) saved.append(getattr(obj, attribute)) setattr(obj, attribute, replacement) yield replacements[0] if single else replacements for attribute, saved_attr in zip(attributes, saved): setattr(obj, attribute, saved_attr) beancount.utils.test_utils.record(fun) \uf0c1 Decorates the function to intercept and record all calls and return values. Parameters: fun \u2013 A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. Source code in beancount/utils/test_utils.py def record(fun): \"\"\"Decorates the function to intercept and record all calls and return values. Args: fun: A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. \"\"\" @functools.wraps(fun) def wrapped(*args, **kw): return_value = fun(*args, **kw) wrapped.calls.append(RCall(args, kw, return_value)) return return_value wrapped.calls = [] return wrapped beancount.utils.test_utils.run_with_args(function, args) \uf0c1 Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Parameters: function \u2013 A function object to call with no arguments. argv \u2013 A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. Source code in beancount/utils/test_utils.py def run_with_args(function, args): \"\"\"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Args: function: A function object to call with no arguments. argv: A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. \"\"\" saved_argv = sys.argv saved_handlers = logging.root.handlers try: module = sys.modules[function.__module__] sys.argv = [module.__file__] + args logging.root.handlers = [] return function() finally: sys.argv = saved_argv logging.root.handlers = saved_handlers beancount.utils.test_utils.search_words(words, line) \uf0c1 Search for a sequence of words in a line. Parameters: words \u2013 A list of strings, the words to look for, or a space-separated string. line \u2013 A string, the line to search into. Returns: A MatchObject, or None. Source code in beancount/utils/test_utils.py def search_words(words, line): \"\"\"Search for a sequence of words in a line. Args: words: A list of strings, the words to look for, or a space-separated string. line: A string, the line to search into. Returns: A MatchObject, or None. \"\"\" if isinstance(words, str): words = words.split() return re.search('.*'.join(r'\\b{}\\b'.format(word) for word in words), line) beancount.utils.test_utils.skipIfRaises(*exc_types) \uf0c1 A context manager (or decorator) that skips a test if an exception is raised. Yields: Nothing, for you to execute the function code. Exceptions: SkipTest \u2013 if the test raised the expected exception. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def skipIfRaises(*exc_types): \"\"\"A context manager (or decorator) that skips a test if an exception is raised. Args: exc_type Yields: Nothing, for you to execute the function code. Raises: SkipTest: if the test raised the expected exception. \"\"\" try: yield except exc_types as exception: raise unittest.SkipTest(exception) beancount.utils.test_utils.subprocess_env() \uf0c1 Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def subprocess_env(): \"\"\"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. \"\"\" # Ensure we have locations to invoke our Python executable and our # runnable binaries in the test environment to run subprocesses. binpath = ':'.join([ path.dirname(sys.executable), path.join(find_repository_root(__file__), 'bin'), os.environ.get('PATH', '').strip(':')]).strip(':') return {'PATH': binpath, 'PYTHONPATH': find_python_lib()} beancount.utils.test_utils.tempdir(delete=True, **kw) \uf0c1 A context manager that creates a temporary directory and deletes its contents unconditionally once done. Parameters: delete \u2013 A boolean, true if we want to delete the directory after running. **kw \u2013 Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def tempdir(delete=True, **kw): \"\"\"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Args: delete: A boolean, true if we want to delete the directory after running. **kw: Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. \"\"\" tempdir = tempfile.mkdtemp(prefix=\"beancount-test-tmpdir.\", **kw) try: yield tempdir finally: if delete: shutil.rmtree(tempdir, ignore_errors=True) beancount.utils.text_utils \uf0c1 Text manipulation utilities. beancount.utils.text_utils.entitize_ampersand(filename) \uf0c1 Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Parameters: filename \u2013 A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. Source code in beancount/utils/text_utils.py def entitize_ampersand(filename): \"\"\"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Args: filename: A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. \"\"\" tidy_file = tempfile.NamedTemporaryFile(suffix='.xls', mode='w', delete=False) with open(filename) as infile: contents = infile.read() new_contents = re.sub('&([^;&]{12})', '&\\\\1', contents) tidy_file.write(new_contents) tidy_file.flush() return tidy_file beancount.utils.text_utils.replace_number(match) \uf0c1 Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Parameters: match \u2013 A MatchObject. Returns: A replacement string, consisting only of X'es. Source code in beancount/utils/text_utils.py def replace_number(match): \"\"\"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Args: match: A MatchObject. Returns: A replacement string, consisting only of X'es. \"\"\" return re.sub('[0-9]', 'X', match.group(1)) + match.group(2) beancount.utils.text_utils.replace_numbers(text) \uf0c1 Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Parameters: text \u2013 An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. Source code in beancount/utils/text_utils.py def replace_numbers(text): \"\"\"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Args: text: An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. \"\"\" return re.sub(r'\\b([0-9,]+(?:\\.[0-9]*)?)\\b([ \\t<]+[^0-9,.]|$)', replace_number, text) beancount.utils.version \uf0c1 Implement common options across all programs. beancount.utils.version.ArgumentParser(*args, **kwargs) \uf0c1 Add a standard --version option to an ArgumentParser. Parameters: *args \u2013 Arguments for the parser. *kwargs \u2013 Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. Source code in beancount/utils/version.py def ArgumentParser(*args, **kwargs): \"\"\"Add a standard --version option to an ArgumentParser. Args: *args: Arguments for the parser. *kwargs: Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. \"\"\" parser = argparse.ArgumentParser(*args, **kwargs) parser.add_argument('--version', '-V', action='version', version=compute_version_string( beancount.__version__, _parser.__vc_changeset__, _parser.__vc_timestamp__)) return parser beancount.utils.version.compute_version_string(version, changeset, timestamp) \uf0c1 Compute a version string from the changeset and timestamp baked in the parser. Parameters: version \u2013 A string, the version number. changeset \u2013 A string, a version control string identifying the commit of the version. timestamp \u2013 An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. Source code in beancount/utils/version.py def compute_version_string(version, changeset, timestamp): \"\"\"Compute a version string from the changeset and timestamp baked in the parser. Args: version: A string, the version number. changeset: A string, a version control string identifying the commit of the version. timestamp: An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. \"\"\" # Shorten changeset. if changeset: if re.match('hg:', changeset): changeset = changeset[:15] elif re.match('git:', changeset): changeset = changeset[:12] # Convert timestamp to a date. date = None if timestamp > 0: date = datetime.datetime.utcfromtimestamp(timestamp).date() version = 'Beancount {}'.format(version) if changeset or date: version = '{} ({})'.format( version, '; '.join(map(str, filter(None, [changeset, date])))) return version","title":"beancount.utils"},{"location":"api_reference/beancount.utils.html#beancountutils","text":"Generic utility packages and functions.","title":"beancount.utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key","text":"A version of bisect that accepts a custom key function, like the sorting ones do.","title":"bisect_key"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key.bisect_left_with_key","text":"Find the last element before the given value in a sorted list. Parameters: sequence \u2013 A sorted sequence of elements. value \u2013 The value to search for. key \u2013 An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. Source code in beancount/utils/bisect_key.py def bisect_left_with_key(sequence, value, key=None): \"\"\"Find the last element before the given value in a sorted list. Args: sequence: A sorted sequence of elements. value: The value to search for. key: An optional function used to extract the value from the elements of sequence. Returns: Return the index. May return None. \"\"\" # pylint: disable=invalid-name if key is None: key = lambda x: x # Identity. lo = 0 hi = len(sequence) while lo < hi: mid = (lo + hi) // 2 if key(sequence[mid]) < value: lo = mid + 1 else: hi = mid return lo","title":"bisect_left_with_key()"},{"location":"api_reference/beancount.utils.html#beancount.utils.bisect_key.bisect_right_with_key","text":"Like bisect.bisect_right, but with a key lookup parameter. Parameters: a \u2013 The list to search in. x \u2013 The element to search for. key \u2013 A function, to extract the value from the list. lo \u2013 The smallest index to search. hi \u2013 The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. Source code in beancount/utils/bisect_key.py def bisect_right_with_key(a, x, key, lo=0, hi=None): \"\"\"Like bisect.bisect_right, but with a key lookup parameter. Args: a: The list to search in. x: The element to search for. key: A function, to extract the value from the list. lo: The smallest index to search. hi: The largest index to search. Returns: As in bisect.bisect_right, an element from list 'a'. \"\"\" # pylint: disable=invalid-name if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 if x < key(a[mid]): hi = mid else: lo = mid+1 return lo","title":"bisect_right_with_key()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils","text":"Utilities for reading and writing CSV files.","title":"csv_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.as_rows","text":"Split a string as rows of a CSV file. Parameters: string \u2013 A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. Source code in beancount/utils/csv_utils.py def as_rows(string): \"\"\"Split a string as rows of a CSV file. Args: string: A string to be split, the contents of a CSV file. Returns: Lists of lists of strings. \"\"\" return list(csv.reader(io.StringIO(textwrap.dedent(string))))","title":"as_rows()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_clean_header","text":"Create a new class for the following rows from the header line. This both normalizes the header line and assign Parameters: header_row \u2013 A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. Source code in beancount/utils/csv_utils.py def csv_clean_header(header_row): \"\"\"Create a new class for the following rows from the header line. This both normalizes the header line and assign Args: header_row: A list of strings, the row with header titles. Returns: A list of strings, with possibly modified (cleaned) row titles, of the same lengths. \"\"\" fieldnames = [] for index, column in enumerate(header_row): field = column.lower() field = re.sub(r'\\bp/l\\b', 'pnl', field) field = re.sub('[^a-z0-9]', '_', field) field = field.strip(' _') field = re.sub('__+', '_', field) if not field: field = 'col{}'.format(index) assert field not in fieldnames, field fieldnames.append(field) return fieldnames","title":"csv_clean_header()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_dict_reader","text":"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. Source code in beancount/utils/csv_utils.py def csv_dict_reader(fileobj, **kw): \"\"\"Read a CSV file yielding normalized dictionary fields. This is basically an alternative constructor for csv.DictReader that normalized the field names. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Returns: A csv.DictReader object. \"\"\" reader = csv.DictReader(fileobj, **kw) reader.fieldnames = csv_clean_header(reader.fieldnames) return reader","title":"csv_dict_reader()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_split_sections","text":"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Parameters: rows \u2013 A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. Source code in beancount/utils/csv_utils.py def csv_split_sections(rows): \"\"\"Given rows, split them in at empty lines. This is useful for structured CSV files with multiple sections. Args: rows: A list of rows, which are themselves lists of strings. Returns: A list of sections, which are lists of rows, which are lists of strings. \"\"\" sections = [] current_section = [] for row in rows: if row: current_section.append(row) else: sections.append(current_section) current_section = [] if current_section: sections.append(current_section) return sections","title":"csv_split_sections()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_split_sections_with_titles","text":"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Parameters: rows \u2013 A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). Source code in beancount/utils/csv_utils.py def csv_split_sections_with_titles(rows): \"\"\"Given a list of rows, split their sections. If the sections have single column titles, consume those lines as their names and return a mapping of section names. This is useful for CSV files with multiple sections, where the separator is a title. We use this to separate the multiple tables within the CSV files. Args: rows: A list of rows (list-of-strings). Returns: A list of lists of rows (list-of-strings). \"\"\" sections_map = {} for index, section in enumerate(csv_split_sections(rows)): # Skip too short sections, cannot possibly be a title. if len(section) < 2: continue if len(section[0]) == 1 and len(section[1]) != 1: name = section[0][0] section = section[1:] else: name = 'Section {}'.format(index) sections_map[name] = section return sections_map","title":"csv_split_sections_with_titles()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.csv_tuple_reader","text":"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Parameters: fileobj \u2013 A file object to be read. **kw \u2013 Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. Source code in beancount/utils/csv_utils.py def csv_tuple_reader(fileobj, **kw): \"\"\"Read a CSV file yielding namedtuple instances. The CSV file must have a header line. Args: fileobj: A file object to be read. **kw: Optional arguments forwarded to csv.DictReader. Yields: Nametuple instances, one for each row. \"\"\" reader = csv.reader(fileobj, **kw) ireader = iter(reader) fieldnames = csv_clean_header(next(ireader)) Tuple = collections.namedtuple('Row', fieldnames) for row in ireader: try: yield Tuple(*row) except TypeError: # If there's an error, it's usually from a line that has a 'END OF # LINE' marker at the end, or some comment line. assert len(row) in (0, 1)","title":"csv_tuple_reader()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.iter_sections","text":"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Parameters: fileobj \u2013 A file object to read from. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. Source code in beancount/utils/csv_utils.py def iter_sections(fileobj, separating_predicate=None): \"\"\"For a given file object, yield file-like objects for each of the sections contained therein. A section is defined as a list of lines that don't match the predicate. For example, if you want to split by empty lines, provide a predicate that will be true given an empty line, which will cause a new section to be begun. Args: fileobj: A file object to read from. separating_predicate: A boolean predicate that is true on separating lines. Yields: A list of lines that you can use to iterate. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) lineiter = iter(fileobj) for line in lineiter: if separating_predicate(line): iterator = itertools.chain((line,), iter_until_empty(lineiter, separating_predicate)) yield iterator for _ in iterator: pass","title":"iter_sections()"},{"location":"api_reference/beancount.utils.html#beancount.utils.csv_utils.iter_until_empty","text":"An iterator of lines that will stop at the first empty line. Parameters: iterator \u2013 An iterator of lines. separating_predicate \u2013 A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. Source code in beancount/utils/csv_utils.py def iter_until_empty(iterator, separating_predicate=None): \"\"\"An iterator of lines that will stop at the first empty line. Args: iterator: An iterator of lines. separating_predicate: A boolean predicate that is true on separating lines. Yields: Non-empty lines. EOF when we hit an empty line. \"\"\" if separating_predicate is None: separating_predicate = lambda line: bool(line.strip()) for line in iterator: if not separating_predicate(line): break yield line","title":"iter_until_empty()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils","text":"Parse the date from various formats.","title":"date_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.intimezone","text":"Temporarily reset the value of TZ. This is used for testing. Parameters: tz_value ( str ) \u2013 The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. Source code in beancount/utils/date_utils.py @contextlib.contextmanager def intimezone(tz_value: str): \"\"\"Temporarily reset the value of TZ. This is used for testing. Args: tz_value: The value of TZ to set for the duration of this context. Returns: A contextmanager in the given timezone locale. \"\"\" tz_old = os.environ.get('TZ', None) os.environ['TZ'] = tz_value time.tzset() try: yield finally: if tz_old is None: del os.environ['TZ'] else: os.environ['TZ'] = tz_old time.tzset()","title":"intimezone()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.iter_dates","text":"Yield all the dates between 'start_date' and 'end_date'. Parameters: start_date \u2013 An instance of datetime.date. end_date \u2013 An instance of datetime.date. Yields: Instances of datetime.date. Source code in beancount/utils/date_utils.py def iter_dates(start_date, end_date): \"\"\"Yield all the dates between 'start_date' and 'end_date'. Args: start_date: An instance of datetime.date. end_date: An instance of datetime.date. Yields: Instances of datetime.date. \"\"\" oneday = datetime.timedelta(days=1) date = start_date while date < end_date: yield date date += oneday","title":"iter_dates()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.next_month","text":"Compute the date at the beginning of the following month from the given date. Parameters: date \u2013 A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. Source code in beancount/utils/date_utils.py def next_month(date): \"\"\"Compute the date at the beginning of the following month from the given date. Args: date: A datetime.date instance. Returns: A datetime.date instance, the first day of the month following 'date'. \"\"\" # Compute the date at the beginning of the following month. year = date.year month = date.month + 1 if date.month == 12: year += 1 month = 1 return datetime.date(year, month, 1)","title":"next_month()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.parse_date_liberally","text":"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Parameters: string \u2013 A string to parse. parse_kwargs_dict \u2013 Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. Source code in beancount/utils/date_utils.py def parse_date_liberally(string, parse_kwargs_dict=None): \"\"\"Parse arbitrary strings to dates. This function is intended to support liberal inputs, so that we can use it in accepting user-specified dates on command-line scripts. Args: string: A string to parse. parse_kwargs_dict: Dict of kwargs to pass to dateutil parser. Returns: A datetime.date object. \"\"\" # At the moment, rely on the most excellent dateutil. if parse_kwargs_dict is None: parse_kwargs_dict = {} return dateutil.parser.parse(string, **parse_kwargs_dict).date()","title":"parse_date_liberally()"},{"location":"api_reference/beancount.utils.html#beancount.utils.date_utils.render_ofx_date","text":"Render a datetime to the OFX format. Parameters: dtime \u2013 A datetime.datetime instance. Returns: A string, rendered to milliseconds. Source code in beancount/utils/date_utils.py def render_ofx_date(dtime): \"\"\"Render a datetime to the OFX format. Args: dtime: A datetime.datetime instance. Returns: A string, rendered to milliseconds. \"\"\" return '{}.{:03d}'.format(dtime.strftime('%Y%m%d%H%M%S'), int(dtime.microsecond / 1000))","title":"render_ofx_date()"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict","text":"An instance of collections.defaultdict whose factory accepts a key. Note: This really ought to be an enhancement to Python itself. I should bother adding this in eventually.","title":"defdict"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.DefaultDictWithKey","text":"A version of defaultdict whose factory accepts the key as an argument. Note: collections.defaultdict would be improved by supporting this directly, this is a common occurrence.","title":"DefaultDictWithKey"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault","text":"An immutable dict which returns a default value for missing keys. This differs from a defaultdict in that it does not insert a missing default value when one is materialized (from a missing fetch), and furthermore, the set method is make unavailable to prevent mutation beyond construction.","title":"ImmutableDictWithDefault"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault.__setitem__","text":"Disallow mutating the dict in the usual way. Source code in beancount/utils/defdict.py def __setitem__(self, key, value): \"\"\"Disallow mutating the dict in the usual way.\"\"\" raise NotImplementedError","title":"__setitem__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.defdict.ImmutableDictWithDefault.get","text":"Return the value for key if key is in the dictionary, else default. Source code in beancount/utils/defdict.py def get(self, key, _=None): return self.__getitem__(key)","title":"get()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption","text":"Support for encrypted tests.","title":"encryption"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.is_encrypted_file","text":"Return true if the given filename contains an encrypted file. Parameters: filename \u2013 A path string. Returns: A boolean, true if the file contains an encrypted file. Source code in beancount/utils/encryption.py def is_encrypted_file(filename): \"\"\"Return true if the given filename contains an encrypted file. Args: filename: A path string. Returns: A boolean, true if the file contains an encrypted file. \"\"\" _, ext = path.splitext(filename) if ext == '.gpg': return True if ext == '.asc': with open(filename) as encfile: head = encfile.read(1024) if re.search('--BEGIN PGP MESSAGE--', head): return True return False","title":"is_encrypted_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.is_gpg_installed","text":"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support. Source code in beancount/utils/encryption.py def is_gpg_installed(): \"\"\"Return true if GPG 1.4.x or 2.x are installed, which is what we use and support.\"\"\" try: pipe = subprocess.Popen(['gpg', '--version'], shell=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = pipe.communicate() version_text = out.decode('utf8') return pipe.returncode == 0 and re.match(r'gpg \\(GnuPG\\) (1\\.4|2)\\.', version_text) except OSError: return False","title":"is_gpg_installed()"},{"location":"api_reference/beancount.utils.html#beancount.utils.encryption.read_encrypted_file","text":"Decrypt and read an encrypted file without temporary storage. Parameters: filename \u2013 A string, the path to the encrypted file. Returns: A string, the contents of the file. Exceptions: OSError \u2013 If we could not properly decrypt the file. Source code in beancount/utils/encryption.py def read_encrypted_file(filename): \"\"\"Decrypt and read an encrypted file without temporary storage. Args: filename: A string, the path to the encrypted file. Returns: A string, the contents of the file. Raises: OSError: If we could not properly decrypt the file. \"\"\" command = ['gpg', '--batch', '--decrypt', path.realpath(filename)] pipe = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) contents, errors = pipe.communicate() if pipe.returncode != 0: raise OSError(\"Could not decrypt file ({}): {}\".format(pipe.returncode, errors.decode('utf8'))) return contents.decode('utf-8')","title":"read_encrypted_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_type","text":"Code that can guess a MIME type for a filename. This attempts to identify the mime-type of a file suing The built-in mimetypes library, then python-magic (if available), and finally some custom mappers for datatypes used in financial downloads, such as Quicken files.","title":"file_type"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_type.guess_file_type","text":"Attempt to guess the type of the input file. Parameters: filename \u2013 A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. Source code in beancount/utils/file_type.py def guess_file_type(filename): \"\"\"Attempt to guess the type of the input file. Args: filename: A string, the name of the file to guess the type for. Returns: A suitable mimetype string, or None if we could not guess. \"\"\" # Try the standard mimetypes association. filetype, _ = mimetypes.guess_type(filename, False) if filetype: return filetype # Try out some extra types that only we know about. for regexp, mimetype in EXTRA_FILE_TYPES: if regexp.match(filename): return mimetype # Try out libmagic, if it is installed. if magic: filetype = magic.from_file(filename, mime=True) if isinstance(filetype, bytes): filetype = filetype.decode('utf8') return filetype else: raise ValueError((\"Could not identify the type of file '{}'; \" \"try installing python-magic\").format(filename))","title":"guess_file_type()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils","text":"File utilities.","title":"file_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.chdir","text":"Temporarily chdir to the given directory. Parameters: directory \u2013 The directory to switch do. Returns: A context manager which restores the cwd after running. Source code in beancount/utils/file_utils.py @contextlib.contextmanager def chdir(directory): \"\"\"Temporarily chdir to the given directory. Args: directory: The directory to switch do. Returns: A context manager which restores the cwd after running. \"\"\" cwd = os.getcwd() os.chdir(directory) try: yield cwd finally: os.chdir(cwd)","title":"chdir()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.find_files","text":"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Parameters: fords \u2013 A list of strings, file or directory names. ignore_dirs \u2013 A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. Source code in beancount/utils/file_utils.py def find_files(fords, ignore_dirs=('.hg', '.svn', '.git'), ignore_files=('.DS_Store',)): \"\"\"Enumerate the files under the given directories, stably. Invalid file or directory names will be logged to the error log. Args: fords: A list of strings, file or directory names. ignore_dirs: A list of strings, filenames or directories to be ignored. Yields: Strings, full filenames from the given roots. \"\"\" if isinstance(fords, str): fords = [fords] assert isinstance(fords, (list, tuple)) for ford in fords: if path.isdir(ford): for root, dirs, filenames in os.walk(ford): dirs[:] = sorted(dirname for dirname in dirs if dirname not in ignore_dirs) for filename in sorted(filenames): if filename in ignore_files: continue yield path.join(root, filename) elif path.isfile(ford) or path.islink(ford): yield ford elif not path.exists(ford): logging.error(\"File or directory '{}' does not exist.\".format(ford))","title":"find_files()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.guess_file_format","text":"Guess the file format from the filename. Parameters: filename \u2013 A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. Source code in beancount/utils/file_utils.py def guess_file_format(filename, default=None): \"\"\"Guess the file format from the filename. Args: filename: A string, the name of the file. This can be None. Returns: A string, the extension of the format, without a leading period. \"\"\" if filename: if filename.endswith('.txt') or filename.endswith('.text'): format = 'text' elif filename.endswith('.csv'): format = 'csv' elif filename.endswith('.html') or filename.endswith('.xhtml'): format = 'html' else: format = default else: format = default return format","title":"guess_file_format()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.path_greedy_split","text":"Split a path, returning the longest possible extension. Parameters: filename \u2013 A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). Source code in beancount/utils/file_utils.py def path_greedy_split(filename): \"\"\"Split a path, returning the longest possible extension. Args: filename: A string, the filename to split. Returns: A pair of basename, extension (which includes the leading period). \"\"\" basename = path.basename(filename) index = basename.find('.') if index == -1: extension = None else: extension = basename[index:] basename = basename[:index] return (path.join(path.dirname(filename), basename), extension)","title":"path_greedy_split()"},{"location":"api_reference/beancount.utils.html#beancount.utils.file_utils.touch_file","text":"Touch a file and wait until its timestamp has been changed. Parameters: filename \u2013 A string path, the name of the file to touch. otherfiles \u2013 A list of other files to ensure the timestamp is beyond of. Source code in beancount/utils/file_utils.py def touch_file(filename, *otherfiles): \"\"\"Touch a file and wait until its timestamp has been changed. Args: filename: A string path, the name of the file to touch. otherfiles: A list of other files to ensure the timestamp is beyond of. \"\"\" # Note: You could set os.stat_float_times() but then the main function would # have to set that up as well. It doesn't help so much, however, since # filesystems tend to have low resolutions, e.g. one second. orig_mtime_ns = max(os.stat(minfile).st_mtime_ns for minfile in (filename,) + otherfiles) delay_secs = 0.05 while True: with open(filename, 'a'): os.utime(filename) time.sleep(delay_secs) new_stat = os.stat(filename) if new_stat.st_mtime_ns > orig_mtime_ns: break","title":"touch_file()"},{"location":"api_reference/beancount.utils.html#beancount.utils.import_utils","text":"Utilities for importing symbols programmatically.","title":"import_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.import_utils.import_symbol","text":"Import a symbol in an arbitrary module. Parameters: dotted_name \u2013 A dotted path to a symbol. Returns: The object referenced by the given name. Exceptions: ImportError \u2013 If the module not not be imported. AttributeError \u2013 If the symbol could not be found in the module. Source code in beancount/utils/import_utils.py def import_symbol(dotted_name): \"\"\"Import a symbol in an arbitrary module. Args: dotted_name: A dotted path to a symbol. Returns: The object referenced by the given name. Raises: ImportError: If the module not not be imported. AttributeError: If the symbol could not be found in the module. \"\"\" comps = dotted_name.split('.') module_name = '.'.join(comps[:-1]) symbol_name = comps[-1] module = importlib.import_module(module_name) return getattr(module, symbol_name)","title":"import_symbol()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants","text":"Functions to register auxiliary functions on a class' methods to check for invariants. This is intended to be used in a test, whereby your test will setup a class to automatically run invariant verification functions before and after each function call, to ensure some extra sanity checks that wouldn't be used in non-tests. Example: Instrument the Inventory class with the check_inventory_invariants() function. def setUp(module): instrument_invariants(Inventory, check_inventory_invariants, check_inventory_invariants) def tearDown(module): uninstrument_invariants(Inventory)","title":"invariants"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.instrument_invariants","text":"Instrument the class 'klass' with pre/post invariant checker functions. Parameters: klass \u2013 A class object, whose methods to be instrumented. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants pre-call. Source code in beancount/utils/invariants.py def instrument_invariants(klass, prefun, postfun): \"\"\"Instrument the class 'klass' with pre/post invariant checker functions. Args: klass: A class object, whose methods to be instrumented. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants pre-call. \"\"\" instrumented = {} for attrname, object_ in klass.__dict__.items(): if attrname.startswith('_'): continue if not isinstance(object_, types.FunctionType): continue instrumented[attrname] = object_ setattr(klass, attrname, invariant_check(object_, prefun, postfun)) klass.__instrumented = instrumented","title":"instrument_invariants()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.invariant_check","text":"Decorate a method with the pre/post invariant checkers. Parameters: method \u2013 An unbound method to instrument. prefun \u2013 A function that checks invariants pre-call. postfun \u2013 A function that checks invariants post-call. Returns: An unbound method, decorated. Source code in beancount/utils/invariants.py def invariant_check(method, prefun, postfun): \"\"\"Decorate a method with the pre/post invariant checkers. Args: method: An unbound method to instrument. prefun: A function that checks invariants pre-call. postfun: A function that checks invariants post-call. Returns: An unbound method, decorated. \"\"\" reentrant = [] def new_method(self, *args, **kw): reentrant.append(None) if len(reentrant) == 1: prefun(self) result = method(self, *args, **kw) if len(reentrant) == 1: postfun(self) reentrant.pop() return result return new_method","title":"invariant_check()"},{"location":"api_reference/beancount.utils.html#beancount.utils.invariants.uninstrument_invariants","text":"Undo the instrumentation for invariants. Parameters: klass \u2013 A class object, whose methods to be uninstrumented. Source code in beancount/utils/invariants.py def uninstrument_invariants(klass): \"\"\"Undo the instrumentation for invariants. Args: klass: A class object, whose methods to be uninstrumented. \"\"\" instrumented = getattr(klass, '__instrumented', None) if instrumented: for attrname, object_ in instrumented.items(): setattr(klass, attrname, object_) del klass.__instrumented","title":"uninstrument_invariants()"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo","text":"Memoization utilities.","title":"memo"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo.memoize_recent_fileobj","text":"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Parameters: function \u2013 A callable object. cache_filename \u2013 A string, the path to the database file to cache to. expiration \u2013 The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. Source code in beancount/utils/memo.py def memoize_recent_fileobj(function, cache_filename, expiration=None): \"\"\"Memoize recent calls to the given function which returns a file object. The results of the cache expire after some time. Args: function: A callable object. cache_filename: A string, the path to the database file to cache to. expiration: The time during which the results will be kept valid. Use 'None' to never expire the cache (this is the default). Returns: A memoized version of the function. \"\"\" urlcache = shelve.open(cache_filename, 'c') urlcache.lock = threading.Lock() # Note: 'shelve' is not thread-safe. @functools.wraps(function) def memoized(*args, **kw): # Encode the arguments, including a date string in order to invalidate # results over some time. md5 = hashlib.md5() md5.update(str(args).encode('utf-8')) md5.update(str(sorted(kw.items())).encode('utf-8')) hash_ = md5.hexdigest() time_now = now() try: with urlcache.lock: time_orig, contents = urlcache[hash_] if expiration is not None and (time_now - time_orig) > expiration: raise KeyError except KeyError: fileobj = function(*args, **kw) if fileobj: contents = fileobj.read() with urlcache.lock: urlcache[hash_] = (time_now, contents) else: contents = None return io.BytesIO(contents) if contents else None return memoized","title":"memoize_recent_fileobj()"},{"location":"api_reference/beancount.utils.html#beancount.utils.memo.now","text":"Indirection on datetime.datetime.now() for testing. Source code in beancount/utils/memo.py def now(): \"Indirection on datetime.datetime.now() for testing.\" return datetime.datetime.now()","title":"now()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils","text":"Generic utility packages and functions.","title":"misc_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy","text":"A file object that will delegate writing full lines to another logging function. This may be used for writing data to a logging level without having to worry about lines.","title":"LineFileProxy"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.__init__","text":"Construct a new line delegator file object proxy. Parameters: line_writer \u2013 A callable function, used to write to the delegated output. prefix \u2013 An optional string, the prefix to insert before every line. write_newlines \u2013 A boolean, true if we should output the newline characters. Source code in beancount/utils/misc_utils.py def __init__(self, line_writer, prefix=None, write_newlines=False): \"\"\"Construct a new line delegator file object proxy. Args: line_writer: A callable function, used to write to the delegated output. prefix: An optional string, the prefix to insert before every line. write_newlines: A boolean, true if we should output the newline characters. \"\"\" self.line_writer = line_writer self.prefix = prefix self.write_newlines = write_newlines self.data = []","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.close","text":"Close the line delegator. Source code in beancount/utils/misc_utils.py def close(self): \"\"\"Close the line delegator.\"\"\" self.flush()","title":"close()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.flush","text":"Flush the data to the line writer. Source code in beancount/utils/misc_utils.py def flush(self): \"\"\"Flush the data to the line writer.\"\"\" data = ''.join(self.data) if data: lines = data.splitlines() self.data = [lines.pop(-1)] if data[-1] != '\\n' else [] for line in lines: if self.prefix: line = self.prefix + line if self.write_newlines: line += '\\n' self.line_writer(line)","title":"flush()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.LineFileProxy.write","text":"Write some string data to the output. Parameters: data \u2013 A string, with or without newlines. Source code in beancount/utils/misc_utils.py def write(self, data): \"\"\"Write some string data to the output. Args: data: A string, with or without newlines. \"\"\" if '\\n' in data: self.data.append(data) self.flush() else: self.data.append(data)","title":"write()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.TypeComparable","text":"A base class whose equality comparison includes comparing the type of the instance itself.","title":"TypeComparable"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.box","text":"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Parameters: name \u2013 A string, the name of the box to use. file \u2013 The file object to print to. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def box(name=None, file=None): \"\"\"A context manager that prints out a box around a block. This is useful for printing out stuff from tests in a way that is readable. Args: name: A string, the name of the box to use. file: The file object to print to. Yields: None. \"\"\" file = file or sys.stdout file.write('\\n') if name: header = ',--------({})--------\\n'.format(name) footer = '`{}\\n'.format('-' * (len(header)-2)) else: header = ',----------------\\n' footer = '`----------------\\n' file.write(header) yield file.write(footer) file.flush()","title":"box()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.cmptuple","text":"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Parameters: name \u2013 The given name of the class. attributes \u2013 A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. Source code in beancount/utils/misc_utils.py def cmptuple(name, attributes): \"\"\"Manufacture a comparable namedtuple class, similar to collections.namedtuple. A comparable named tuple is a tuple which compares to False if contents are equal but the data types are different. We define this to supplement collections.namedtuple because by default a namedtuple disregards the type and we want to make precise comparisons for tests. Args: name: The given name of the class. attributes: A string or tuple of strings, with the names of the attributes. Returns: A new namedtuple-derived type that compares False with other tuples with same contents. \"\"\" base = collections.namedtuple('_{}'.format(name), attributes) return type(name, (TypeComparable, base,), {})","title":"cmptuple()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.compute_unique_clean_ids","text":"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Parameters: strings \u2013 A list of strings. Returns: A list of (id, string) pairs. Source code in beancount/utils/misc_utils.py def compute_unique_clean_ids(strings): \"\"\"Given a sequence of strings, reduce them to corresponding ids without any funny characters and insure that the list of ids is unique. Yields pairs of (id, string) for the result. Args: strings: A list of strings. Returns: A list of (id, string) pairs. \"\"\" string_set = set(strings) # Try multiple methods until we get one that has no collisions. for regexp, replacement in [(r'[^A-Za-z0-9.-]', '_'), (r'[^A-Za-z0-9_]', ''),]: seen = set() idmap = {} mre = re.compile(regexp) for string in string_set: id_ = mre.sub(replacement, string) if id_ in seen: break # Collision. seen.add(id_) idmap[id_] = string else: break else: return None # Could not find a unique mapping. return idmap","title":"compute_unique_clean_ids()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.deprecated","text":"A decorator generator to mark functions as deprecated and log a warning. Source code in beancount/utils/misc_utils.py def deprecated(message): \"\"\"A decorator generator to mark functions as deprecated and log a warning.\"\"\" def decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn(\"Call to deprecated function {}: {}\".format(func.__name__, message), category=DeprecationWarning, stacklevel=2) return func(*args, **kwargs) return new_func return decorator","title":"deprecated()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.dictmap","text":"Map a dictionary's value. Parameters: mdict \u2013 A dict. key \u2013 A callable to apply to the keys. value \u2013 A callable to apply to the values. Source code in beancount/utils/misc_utils.py def dictmap(mdict, keyfun=None, valfun=None): \"\"\"Map a dictionary's value. Args: mdict: A dict. key: A callable to apply to the keys. value: A callable to apply to the values. \"\"\" if keyfun is None: keyfun = lambda x: x if valfun is None: valfun = lambda x: x return {keyfun(key): valfun(val) for key, val in mdict.items()}","title":"dictmap()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.escape_string","text":"Escape quotes and backslashes in payee and narration. Parameters: string \u2013 Any string. Returns. The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def escape_string(string): \"\"\"Escape quotes and backslashes in payee and narration. Args: string: Any string. Returns. The input string, with offending characters replaced. \"\"\" return string.replace('\\\\', r'\\\\')\\ .replace('\"', r'\\\"')","title":"escape_string()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.filter_type","text":"Filter the given list to yield only instances of the given types. Parameters: elist \u2013 A sequence of elements. types \u2013 A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. Source code in beancount/utils/misc_utils.py def filter_type(elist, types): \"\"\"Filter the given list to yield only instances of the given types. Args: elist: A sequence of elements. types: A sequence of types to include in the output list. Yields: Each element, if it is an instance of 'types'. \"\"\" for element in elist: if not isinstance(element, types): continue yield element","title":"filter_type()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.first_paragraph","text":"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Parameters: docstring \u2013 A doc string. Returns: A string with just the first sentence on a single line. Source code in beancount/utils/misc_utils.py def first_paragraph(docstring): \"\"\"Return the first sentence of a docstring. The sentence has to be delimited by an empty line. Args: docstring: A doc string. Returns: A string with just the first sentence on a single line. \"\"\" lines = [] for line in docstring.strip().splitlines(): if not line: break lines.append(line.rstrip()) return ' '.join(lines)","title":"first_paragraph()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_screen_height","text":"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_height(): \"\"\"Return the height of the terminal that runs this program. Returns: An integer, the number of characters the screen is high. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('lines', 0)","title":"get_screen_height()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_screen_width","text":"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. Source code in beancount/utils/misc_utils.py def get_screen_width(): \"\"\"Return the width of the terminal that runs this program. Returns: An integer, the number of characters the screen is wide. Return 0 if the terminal cannot be initialized. \"\"\" return _get_screen_value('cols', 0)","title":"get_screen_width()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.get_tuple_values","text":"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Parameters: ntuple \u2013 A tuple or namedtuple. predicate \u2013 A predicate function that returns true if an attribute is to be output. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def get_tuple_values(ntuple, predicate, memo=None): \"\"\"Return all members referred to by this namedtuple instance that satisfy the given predicate. This function also works recursively on its members which are lists or tuples, and so it can be used for Transaction instances. Args: ntuple: A tuple or namedtuple. predicate: A predicate function that returns true if an attribute is to be output. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return memo.add(id_ntuple) if predicate(ntuple): yield for attribute in ntuple: if predicate(attribute): yield attribute if isinstance(attribute, (list, tuple)): for value in get_tuple_values(attribute, predicate, memo): yield value","title":"get_tuple_values()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.groupby","text":"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Parameters: keyfun \u2013 A callable, used to obtain the group key from each element. elements \u2013 An iterable of the elements to group. Returns: A dict of key to list of sequences. Source code in beancount/utils/misc_utils.py def groupby(keyfun, elements): \"\"\"Group the elements as a dict of lists, where the key is computed using the function 'keyfun'. Args: keyfun: A callable, used to obtain the group key from each element. elements: An iterable of the elements to group. Returns: A dict of key to list of sequences. \"\"\" # Note: We could allow a custom aggregation function. Another option is # provide another method to reduce the list values of a dict, but that can # be accomplished using a dict comprehension. grouped = defaultdict(list) for element in elements: grouped[keyfun(element)].append(element) return grouped","title":"groupby()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.idify","text":"Replace characters objectionable for a filename with underscores. Parameters: string \u2013 Any string. Returns: The input string, with offending characters replaced. Source code in beancount/utils/misc_utils.py def idify(string): \"\"\"Replace characters objectionable for a filename with underscores. Args: string: Any string. Returns: The input string, with offending characters replaced. \"\"\" for sfrom, sto in [(r'[ \\(\\)]+', '_'), (r'_*\\._*', '.')]: string = re.sub(sfrom, sto, string) string = string.strip('_') return string","title":"idify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.import_curses","text":"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Exceptions: ImportError \u2013 If the module could not be imported. Source code in beancount/utils/misc_utils.py def import_curses(): \"\"\"Try to import the 'curses' module. (This is used here in order to override for tests.) Returns: The curses module, if it was possible to import it. Raises: ImportError: If the module could not be imported. \"\"\" # Note: There's a recipe for getting terminal size on Windows here, without # curses, I should probably implement that at some point: # https://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window # Also, consider just using 'blessings' instead, which provides this across # multiple platforms. # pylint: disable=import-outside-toplevel import curses return curses","title":"import_curses()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.is_sorted","text":"Return true if the sequence is sorted. Parameters: iterable \u2013 An iterable sequence. key \u2013 A function to extract the quantity by which to sort. cmp \u2013 A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. Source code in beancount/utils/misc_utils.py def is_sorted(iterable, key=lambda x: x, cmp=lambda x, y: x <= y): \"\"\"Return true if the sequence is sorted. Args: iterable: An iterable sequence. key: A function to extract the quantity by which to sort. cmp: A function that compares two elements of a sequence. Returns: A boolean, true if the sequence is sorted. \"\"\" iterator = map(key, iterable) prev = next(iterator) for element in iterator: if not cmp(prev, element): return False prev = element return True","title":"is_sorted()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.log_time","text":"A context manager that times the block and logs it to info level. Parameters: operation_name \u2013 A string, a label for the name of the operation. log_timings \u2013 A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent \u2013 An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def log_time(operation_name, log_timings, indent=0): \"\"\"A context manager that times the block and logs it to info level. Args: operation_name: A string, a label for the name of the operation. log_timings: A function to write log messages to. If left to None, no timings are written (this becomes a no-op). indent: An integer, the indentation level for the format of the timing line. This is useful if you're logging timing to a hierarchy of operations. Yields: The start time of the operation. \"\"\" time1 = time() yield time1 time2 = time() if log_timings: log_timings(\"Operation: {:48} Time: {}{:6.0f} ms\".format( \"'{}'\".format(operation_name), ' '*indent, (time2 - time1) * 1000))","title":"log_time()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.longest","text":"Return the longest of the given subsequences. Parameters: seq \u2013 An iterable sequence of lists. Returns: The longest list from the sequence. Source code in beancount/utils/misc_utils.py def longest(seq): \"\"\"Return the longest of the given subsequences. Args: seq: An iterable sequence of lists. Returns: The longest list from the sequence. \"\"\" longest, length = None, -1 for element in seq: len_element = len(element) if len_element > length: longest, length = element, len_element return longest","title":"longest()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.map_namedtuple_attributes","text":"Map the value of the named attributes of object by mapper. Parameters: attributes \u2013 A sequence of string, the attribute names to map. mapper \u2013 A callable that accepts the value of a field and returns the new value. object_ \u2013 Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. Source code in beancount/utils/misc_utils.py def map_namedtuple_attributes(attributes, mapper, object_): \"\"\"Map the value of the named attributes of object by mapper. Args: attributes: A sequence of string, the attribute names to map. mapper: A callable that accepts the value of a field and returns the new value. object_: Some namedtuple object with attributes on it. Returns: A new instance of the same namedtuple with the named fields mapped by mapper. \"\"\" return object_._replace(**{attribute: mapper(getattr(object_, attribute)) for attribute in attributes})","title":"map_namedtuple_attributes()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.replace_namedtuple_values","text":"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Parameters: ntuple \u2013 A namedtuple instance. predicate \u2013 A predicate function that returns true if an attribute is to be output. mapper \u2013 A callable, that will accept a single argument and return its replacement value. memo \u2013 An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. Source code in beancount/utils/misc_utils.py def replace_namedtuple_values(ntuple, predicate, mapper, memo=None): \"\"\"Recurse through all the members of namedtuples and lists, and for members that match the given predicate, run them through the given mapper. Args: ntuple: A namedtuple instance. predicate: A predicate function that returns true if an attribute is to be output. mapper: A callable, that will accept a single argument and return its replacement value. memo: An optional memoizing dictionary. If a tuple has already been seen, the recursion will be avoided. Yields: Attributes of the tuple and its sub-elements if the predicate is true. \"\"\" if memo is None: memo = set() id_ntuple = id(ntuple) if id_ntuple in memo: return None memo.add(id_ntuple) # pylint: disable=unidiomatic-typecheck if not (type(ntuple) is not tuple and isinstance(ntuple, tuple)): return ntuple replacements = {} for attribute_name, attribute in zip(ntuple._fields, ntuple): if predicate(attribute): replacements[attribute_name] = mapper(attribute) elif type(attribute) is not tuple and isinstance(attribute, tuple): replacements[attribute_name] = replace_namedtuple_values( attribute, predicate, mapper, memo) elif type(attribute) in (list, tuple): replacements[attribute_name] = [ replace_namedtuple_values(member, predicate, mapper, memo) for member in attribute] return ntuple._replace(**replacements)","title":"replace_namedtuple_values()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.skipiter","text":"Skip some elements from an iterator. Parameters: iterable \u2013 An iterator. num_skip \u2013 The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. Source code in beancount/utils/misc_utils.py def skipiter(iterable, num_skip): \"\"\"Skip some elements from an iterator. Args: iterable: An iterator. num_skip: The number of elements in the period. Yields: Elements from the iterable, with num_skip elements skipped. For example, skipiter(range(10), 3) yields [0, 3, 6, 9]. \"\"\" assert num_skip > 0 sit = iter(iterable) while 1: try: value = next(sit) except StopIteration: return yield value for _ in range(num_skip-1): try: next(sit) except StopIteration: return","title":"skipiter()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.sorted_uniquify","text":"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does not maintain the ordering of the original elements, they are returned sorted (by key) instead. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def sorted_uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, sort and remove duplicates of the given key. Keep either the first or the last (by key) element of a sequence of key-identical elements. This does _not_ maintain the ordering of the original elements, they are returned sorted (by key) instead. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x if last: prev_obj = UNSET prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key and prev_obj is not UNSET: yield prev_obj prev_obj = obj prev_key = key if prev_obj is not UNSET: yield prev_obj else: prev_key = UNSET for obj in sorted(iterable, key=keyfunc): key = keyfunc(obj) if key != prev_key: yield obj prev_key = key","title":"sorted_uniquify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.staticvar","text":"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Parameters: varname \u2013 A string, the name of the variable to define. initial_value \u2013 The value to initialize the variable to. Returns: A function decorator. Source code in beancount/utils/misc_utils.py def staticvar(varname, initial_value): \"\"\"Returns a decorator that defines a Python function attribute. This is used to simulate a static function variable in Python. Args: varname: A string, the name of the variable to define. initial_value: The value to initialize the variable to. Returns: A function decorator. \"\"\" def deco(fun): setattr(fun, varname, initial_value) return fun return deco","title":"staticvar()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.swallow","text":"Catch and ignore certain exceptions. Parameters: exception_types \u2013 A tuple of exception classes to ignore. Yields: None. Source code in beancount/utils/misc_utils.py @contextlib.contextmanager def swallow(*exception_types): \"\"\"Catch and ignore certain exceptions. Args: exception_types: A tuple of exception classes to ignore. Yields: None. \"\"\" try: yield except Exception as exc: if not isinstance(exc, exception_types): raise","title":"swallow()"},{"location":"api_reference/beancount.utils.html#beancount.utils.misc_utils.uniquify","text":"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Parameters: iterable \u2013 An iterable sequence. keyfunc \u2013 A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last \u2013 A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. Source code in beancount/utils/misc_utils.py def uniquify(iterable, keyfunc=None, last=False): \"\"\"Given a sequence of elements, remove duplicates of the given key. Keep either the first or the last element of a sequence of key-identical elements. Order is maintained as much as possible. This does maintain the ordering of the original elements, they are returned in the same order as the original elements. Args: iterable: An iterable sequence. keyfunc: A function that extracts from the elements the sort key to use and uniquify on. If left unspecified, the identify function is used and the uniquification occurs on the elements themselves. last: A boolean, True if we should keep the last item of the same keys. Otherwise keep the first. Yields: Elements from the iterable. \"\"\" if keyfunc is None: keyfunc = lambda x: x seen = set() if last: unique_reversed_list = [] for obj in reversed(iterable): key = keyfunc(obj) if key not in seen: seen.add(key) unique_reversed_list.append(obj) yield from reversed(unique_reversed_list) else: for obj in iterable: key = keyfunc(obj) if key not in seen: seen.add(key) yield obj","title":"uniquify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.net_utils","text":"Network utilities.","title":"net_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.net_utils.retrying_urlopen","text":"Open and download the given URL, retrying if it times out. Parameters: url \u2013 A string, the URL to fetch. timeout \u2013 A timeout after which to stop waiting for a response and return an error. max_retry \u2013 The maximum number of times to retry. Returns: The contents of the fetched URL. Source code in beancount/utils/net_utils.py def retrying_urlopen(url, timeout=5, max_retry=5): \"\"\"Open and download the given URL, retrying if it times out. Args: url: A string, the URL to fetch. timeout: A timeout after which to stop waiting for a response and return an error. max_retry: The maximum number of times to retry. Returns: The contents of the fetched URL. \"\"\" for _ in range(max_retry): logging.debug(\"Reading %s\", url) try: response = request.urlopen(url, timeout=timeout) if response: break except error.URLError: return None if response and response.getcode() != 200: return None return response","title":"retrying_urlopen()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager","text":"Code to write output to a pager. This module contains an object accumulates lines up to a minimum and then decides whether to flush them to the original output directly if under the threshold (no pager) or creates a pager and flushes the lines to it if above the threshold and then forwards all future lines to it. The purpose of this object is to pipe output to a pager only if the number of lines to be printed exceeds a minimum number of lines. The contextmanager is intended to be used to pipe output to a pager and wait on the pager to complete before continuing. Simply write to the file object and upon exit we close the file object. This also silences broken pipe errors triggered by the user exiting the sub-process, and recovers from a failing pager command by just using stdout.","title":"pager"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager","text":"A proxy file for a pager that only creates a pager after a minimum number of lines has been printed to it.","title":"ConditionalPager"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__enter__","text":"Initialize the context manager and return this instance as it. Source code in beancount/utils/pager.py def __enter__(self): \"\"\"Initialize the context manager and return this instance as it.\"\"\" # The file and pipe object we're writing to. This gets set after the # number of accumulated lines reaches the threshold. if self.minlines: self.file = None self.pipe = None else: self.file, self.pipe = create_pager(self.command, self.default_file) # Lines accumulated before the threshold. self.accumulated_data = [] self.accumulated_lines = 0 # Return this object to be used as the context manager itself. return self","title":"__enter__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__exit__","text":"Context manager exit. This flushes the output to our output file. Parameters: type \u2013 Optional exception type, as per context managers. value \u2013 Optional exception value, as per context managers. unused_traceback \u2013 Optional trace. Source code in beancount/utils/pager.py def __exit__(self, type, value, unused_traceback): \"\"\"Context manager exit. This flushes the output to our output file. Args: type: Optional exception type, as per context managers. value: Optional exception value, as per context managers. unused_traceback: Optional trace. \"\"\" try: if self.file: # Flush the output file and close it. self.file.flush() else: # Oops... we never reached the threshold. Flush the accumulated # output to the file. self.flush_accumulated(self.default_file) # Wait for the subprocess (if we have one). if self.pipe: self.file.close() self.pipe.wait() # Absorb broken pipes that may occur on flush or close above. except BrokenPipeError: return True # Absorb broken pipes. if isinstance(value, BrokenPipeError): return True elif value: raise","title":"__exit__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.__init__","text":"Create a conditional pager. Parameters: command \u2013 A string, the shell command to run as a pager. minlines \u2013 If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). Source code in beancount/utils/pager.py def __init__(self, command, minlines=None): \"\"\"Create a conditional pager. Args: command: A string, the shell command to run as a pager. minlines: If set, the number of lines under which you should not bother starting a pager. This avoids kicking off a pager if the screen is high enough to render the contents. If the value is unset, always starts a pager (which is fine behavior too). \"\"\" self.command = command self.minlines = minlines self.default_file = (codecs.getwriter(\"utf-8\")(sys.stdout.buffer) if hasattr(sys.stdout, 'buffer') else sys.stdout)","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.flush_accumulated","text":"Flush the existing lines to the newly created pager. This also disabled the accumulator. Parameters: file \u2013 A file object to flush the accumulated data to. Source code in beancount/utils/pager.py def flush_accumulated(self, file): \"\"\"Flush the existing lines to the newly created pager. This also disabled the accumulator. Args: file: A file object to flush the accumulated data to. \"\"\" if self.accumulated_data: write = file.write for data in self.accumulated_data: write(data) self.accumulated_data = None self.accumulated_lines = None","title":"flush_accumulated()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.ConditionalPager.write","text":"Write the data out. Overridden from the file object interface. Parameters: data \u2013 A string, data to write to the output. Source code in beancount/utils/pager.py def write(self, data): \"\"\"Write the data out. Overridden from the file object interface. Args: data: A string, data to write to the output. \"\"\" if self.file is None: # Accumulate the new lines. self.accumulated_lines += data.count('\\n') self.accumulated_data.append(data) # If we've reached the threshold, create a file. if self.accumulated_lines > self.minlines: self.file, self.pipe = create_pager(self.command, self.default_file) self.flush_accumulated(self.file) else: # We've already created a pager subprocess... flush the lines to it. self.file.write(data) # try: # except BrokenPipeError: # # Make sure we don't barf on __exit__(). # self.file = self.pipe = None # raise","title":"write()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.create_pager","text":"Try to create and return a pager subprocess. Parameters: command \u2013 A string, the shell command to run as a pager. file \u2013 The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. Source code in beancount/utils/pager.py def create_pager(command, file): \"\"\"Try to create and return a pager subprocess. Args: command: A string, the shell command to run as a pager. file: The file object for the pager write to. This is also used as a default if we failed to create the pager subprocess. Returns: A pair of (file, pipe), a file object and an optional subprocess.Popen instance to wait on. The pipe instance may be set to None if we failed to create a subprocess. \"\"\" if command is None: command = os.environ.get('PAGER', DEFAULT_PAGER) if not command: command = DEFAULT_PAGER pipe = None # In case of using 'less', make sure the charset is set properly. In theory # you could override this by setting PAGER to \"LESSCHARSET=utf-8 less\" but # this shouldn't affect other programs and is unlikely to cause problems, so # we set it here to make default behavior work for most people (we always # write UTF-8). env = os.environ.copy() env['LESSCHARSET'] = \"utf-8\" try: pipe = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=file, env=env) except OSError as exc: logging.error(\"Invalid pager: {}\".format(exc)) else: stdin_wrapper = io.TextIOWrapper(pipe.stdin, 'utf-8') file = stdin_wrapper return file, pipe","title":"create_pager()"},{"location":"api_reference/beancount.utils.html#beancount.utils.pager.flush_only","text":"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Parameters: fileobj \u2013 A file object, to remain open after running the context manager. Yields: A context manager that yields this object. Source code in beancount/utils/pager.py @contextlib.contextmanager def flush_only(fileobj): \"\"\"A contextmanager around a file object that does not close the file. This is used to return a context manager on a file object but not close it. We flush it instead. This is useful in order to provide an alternative to a pager class as above. Args: fileobj: A file object, to remain open after running the context manager. Yields: A context manager that yields this object. \"\"\" try: yield fileobj finally: fileobj.flush()","title":"flush_only()"},{"location":"api_reference/beancount.utils.html#beancount.utils.regexp_utils","text":"Regular expression helpers.","title":"regexp_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.regexp_utils.re_replace_unicode","text":"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Parameters: regexp \u2013 A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. Source code in beancount/utils/regexp_utils.py def re_replace_unicode(regexp): \"\"\"Substitute Unicode Properties in regular expressions with their corresponding expanded ranges. Args: regexp: A regular expression string. Returns: Return the regular expression with Unicode Properties substituted. \"\"\" for category, rangestr in UNICODE_RANGES.items(): regexp = regexp.replace(r'\\p{' + category + '}', rangestr) return regexp","title":"re_replace_unicode()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop","text":"Text manipulation utilities.","title":"snoop"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop","text":"A snooper callable that just saves the returned values of a function. This is particularly useful for re.match and re.search in conditionals, e.g.:: snoop = Snoop() ... if snoop(re.match(r\"(\\d+)-(\\d+)-(\\d+)\", text)): year, month, date = snoop.value.group(1, 2, 3) Attributes: Name Type Description value The last value snooped from a function call. history If 'maxlen' was specified, the last few values that were snooped.","title":"Snoop"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__call__","text":"Save a value to the snooper. This is meant to wrap a function call. Parameters: value \u2013 The value to push/save. Returns: Value itself. Source code in beancount/utils/snoop.py def __call__(self, value): \"\"\"Save a value to the snooper. This is meant to wrap a function call. Args: value: The value to push/save. Returns: Value itself. \"\"\" self.value = value if self.history is not None: self.history.append(value) return value","title":"__call__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__getattr__","text":"Forward the attribute to the value. Parameters: attr \u2013 A string, the name of the attribute. Returns: The value of the attribute. Source code in beancount/utils/snoop.py def __getattr__(self, attr): \"\"\"Forward the attribute to the value. Args: attr: A string, the name of the attribute. Returns: The value of the attribute. \"\"\" return getattr(self.value, attr)","title":"__getattr__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.Snoop.__init__","text":"Create a new snooper. Parameters: maxlen \u2013 If specified, an integer, which enables the saving of that Source code in beancount/utils/snoop.py def __init__(self, maxlen=None): \"\"\"Create a new snooper. Args: maxlen: If specified, an integer, which enables the saving of that number of last values in the history attribute. \"\"\" self.value = None self.history = (collections.deque(maxlen=maxlen) if maxlen else None)","title":"__init__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.snoop.snoopify","text":"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. Source code in beancount/utils/snoop.py def snoopify(function): \"\"\"Decorate a function as snoopable. This is meant to reassign existing functions to a snoopable version of them. For example, if you wanted 're.match' to be automatically snoopable, just decorate it like this: re.match = snoopify(re.match) and then you can just call 're.match' in a conditional and then access 're.match.value' to get to the last returned value. \"\"\" @functools.wraps(function) def wrapper(*args, **kw): value = function(*args, **kw) wrapper.value = value return value wrapper.value = None return wrapper","title":"snoopify()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils","text":"Support utilities for testing scripts.","title":"test_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall","text":"RCall(args, kwargs, return_value)","title":"RCall"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__getnewargs__","text":"Return self as a plain tuple. Used by copy and pickle. Source code in beancount/utils/test_utils.py def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return _tuple(self)","title":"__getnewargs__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__new__","text":"Create new instance of RCall(args, kwargs, return_value)","title":"__new__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.RCall.__repr__","text":"Return a nicely formatted representation string Source code in beancount/utils/test_utils.py def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self","title":"__repr__()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase","text":"","title":"TestCase"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase.assertLines","text":"Compare the lines of text1 and text2, ignoring whitespace. Parameters: text1 \u2013 A string, the expected text. text2 \u2013 A string, the actual text. message \u2013 An optional string message in case the assertion fails. Exceptions: AssertionError \u2013 If the exception fails. Source code in beancount/utils/test_utils.py def assertLines(self, text1, text2, message=None): \"\"\"Compare the lines of text1 and text2, ignoring whitespace. Args: text1: A string, the expected text. text2: A string, the actual text. message: An optional string message in case the assertion fails. Raises: AssertionError: If the exception fails. \"\"\" clean_text1 = textwrap.dedent(text1.strip()) clean_text2 = textwrap.dedent(text2.strip()) lines1 = [line.strip() for line in clean_text1.splitlines()] lines2 = [line.strip() for line in clean_text2.splitlines()] # Compress all space longer than 4 spaces to exactly 4. # This affords us to be even looser. lines1 = [re.sub(' [ \\t]*', ' ', line) for line in lines1] lines2 = [re.sub(' [ \\t]*', ' ', line) for line in lines2] self.assertEqual(lines1, lines2, message)","title":"assertLines()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.TestCase.assertOutput","text":"Expect text printed to stdout. Parameters: expected_text \u2013 A string, the text that should have been printed to stdout. Exceptions: AssertionError \u2013 If the text differs. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def assertOutput(self, expected_text): \"\"\"Expect text printed to stdout. Args: expected_text: A string, the text that should have been printed to stdout. Raises: AssertionError: If the text differs. \"\"\" with capture() as oss: yield oss self.assertLines(textwrap.dedent(expected_text), oss.getvalue())","title":"assertOutput()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.call_command","text":"Run the script with a subprocess. Parameters: script_args \u2013 A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). Source code in beancount/utils/test_utils.py def call_command(command): \"\"\"Run the script with a subprocess. Args: script_args: A list of strings, the arguments to the subprocess, including the script name. Returns: A triplet of (return code integer, stdout ext, stderr text). \"\"\" assert isinstance(command, list), command p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return p.returncode, stdout.decode(), stderr.decode()","title":"call_command()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.capture","text":"A context manager that captures what's printed to stdout. Parameters: *attributes \u2013 A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. Source code in beancount/utils/test_utils.py def capture(*attributes): \"\"\"A context manager that captures what's printed to stdout. Args: *attributes: A tuple of strings, the name of the sys attributes to override with StringIO instances. Yields: A StringIO string accumulator. \"\"\" if not attributes: attributes = 'stdout' elif len(attributes) == 1: attributes = attributes[0] return patch(sys, attributes, io.StringIO)","title":"capture()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.create_temporary_files","text":"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Parameters: root \u2013 A string, the name of the directory under which to create the files. contents_map \u2013 A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. Source code in beancount/utils/test_utils.py def create_temporary_files(root, contents_map): \"\"\"Create a number of temporary files under 'root'. This routine is used to initialize the contents of multiple files under a temporary directory. Args: root: A string, the name of the directory under which to create the files. contents_map: A dict of relative filenames to their contents. The content strings will be automatically dedented for convenience. In addition, the string 'ROOT' in the contents will be automatically replaced by the root directory name. \"\"\" os.makedirs(root, exist_ok=True) for relative_filename, contents in contents_map.items(): assert not path.isabs(relative_filename) filename = path.join(root, relative_filename) os.makedirs(path.dirname(filename), exist_ok=True) clean_contents = textwrap.dedent(contents.replace('{root}', root)) with open(filename, 'w') as f: f.write(clean_contents)","title":"create_temporary_files()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.docfile","text":"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Parameters: function \u2013 A function to decorate. Returns: The decorated function. Source code in beancount/utils/test_utils.py def docfile(function, **kwargs): \"\"\"A decorator that write the function's docstring to a temporary file and calls the decorated function with the temporary filename. This is useful for writing tests. Args: function: A function to decorate. Returns: The decorated function. \"\"\" @functools.wraps(function) def new_function(self): allowed = ('buffering', 'encoding', 'newline', 'dir', 'prefix', 'suffix') if any([key not in allowed for key in kwargs]): raise ValueError(\"Invalid kwarg to docfile_extra\") with tempfile.NamedTemporaryFile('w', **kwargs) as file: text = function.__doc__ file.write(textwrap.dedent(text)) file.flush() return function(self, file.name) new_function.__doc__ = None return new_function","title":"docfile()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.docfile_extra","text":"A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile Source code in beancount/utils/test_utils.py def docfile_extra(**kwargs): \"\"\" A decorator identical to @docfile, but it also takes kwargs for the temporary file, Kwargs: e.g. buffering, encoding, newline, dir, prefix, and suffix. Returns: docfile \"\"\" return functools.partial(docfile, **kwargs)","title":"docfile_extra()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.environ","text":"A context manager which pushes varname's value and restores it later. Parameters: varname \u2013 A string, the environ variable name. newvalue \u2013 A string, the desired value. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def environ(varname, newvalue): \"\"\"A context manager which pushes varname's value and restores it later. Args: varname: A string, the environ variable name. newvalue: A string, the desired value. \"\"\" oldvalue = os.environ.get(varname, None) os.environ[varname] = newvalue yield if oldvalue is not None: os.environ[varname] = oldvalue else: del os.environ[varname]","title":"environ()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.find_python_lib","text":"Return the path to the root of the Python libraries. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_python_lib(): \"\"\"Return the path to the root of the Python libraries. Returns: A string, the root directory. \"\"\" return path.dirname(path.dirname(path.dirname(__file__)))","title":"find_python_lib()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.find_repository_root","text":"Return the path to the repository root. Parameters: filename \u2013 A string, the name of a file within the repository. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def find_repository_root(filename=None): \"\"\"Return the path to the repository root. Args: filename: A string, the name of a file within the repository. Returns: A string, the root directory. \"\"\" if filename is None: filename = __file__ # Support root directory under Bazel. match = re.match(r\"(.*\\.runfiles/beancount)/\", filename) if match: return match.group(1) while not all(path.exists(path.join(filename, sigfile)) for sigfile in ('PKG-INFO', 'COPYING', 'README.rst')): prev_filename = filename filename = path.dirname(filename) if prev_filename == filename: raise ValueError(\"Failed to find the root directory.\") return filename","title":"find_repository_root()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.make_failing_importer","text":"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins. import ', make_failing_importer('setuptools')) def test_... Parameters: removed_module_name \u2013 The name of the module import that should raise an exception. Returns: A decorated test decorator. Source code in beancount/utils/test_utils.py def make_failing_importer(*removed_module_names): \"\"\"Make an importer that raise an ImportError for some modules. Use it like this: @mock.patch('builtins.__import__', make_failing_importer('setuptools')) def test_... Args: removed_module_name: The name of the module import that should raise an exception. Returns: A decorated test decorator. \"\"\" def failing_import(name, *args, **kwargs): if name in removed_module_names: raise ImportError(\"Could not import {}\".format(name)) return builtins.__import__(name, *args, **kwargs) return failing_import","title":"make_failing_importer()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.nottest","text":"Make the given function not testable. Source code in beancount/utils/test_utils.py def nottest(func): \"Make the given function not testable.\" func.__test__ = False return func","title":"nottest()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.patch","text":"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Parameters: obj \u2013 The object to patch up. attributes \u2013 A string or a sequence of strings, the names of attributes to replace. replacement_type \u2013 A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def patch(obj, attributes, replacement_type): \"\"\"A context manager that temporarily patches an object's attributes. All attributes in 'attributes' are saved and replaced by new instances of type 'replacement_type'. Args: obj: The object to patch up. attributes: A string or a sequence of strings, the names of attributes to replace. replacement_type: A callable to build replacement objects. Yields: An instance of a list of sequences of 'replacement_type'. \"\"\" single = isinstance(attributes, str) if single: attributes = [attributes] saved = [] replacements = [] for attribute in attributes: replacement = replacement_type() replacements.append(replacement) saved.append(getattr(obj, attribute)) setattr(obj, attribute, replacement) yield replacements[0] if single else replacements for attribute, saved_attr in zip(attributes, saved): setattr(obj, attribute, saved_attr)","title":"patch()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.record","text":"Decorates the function to intercept and record all calls and return values. Parameters: fun \u2013 A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. Source code in beancount/utils/test_utils.py def record(fun): \"\"\"Decorates the function to intercept and record all calls and return values. Args: fun: A callable to be decorated. Returns: A wrapper function with a .calls attribute, a list of RCall instances. \"\"\" @functools.wraps(fun) def wrapped(*args, **kw): return_value = fun(*args, **kw) wrapped.calls.append(RCall(args, kw, return_value)) return return_value wrapped.calls = [] return wrapped","title":"record()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.run_with_args","text":"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Parameters: function \u2013 A function object to call with no arguments. argv \u2013 A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. Source code in beancount/utils/test_utils.py def run_with_args(function, args): \"\"\"Run the given function with sys.argv set to argv. The first argument is automatically inferred to be where the function object was defined. sys.argv is restored after the function is called. Args: function: A function object to call with no arguments. argv: A list of arguments, excluding the script name, to be temporarily set on sys.argv. Returns: The return value of the function run. \"\"\" saved_argv = sys.argv saved_handlers = logging.root.handlers try: module = sys.modules[function.__module__] sys.argv = [module.__file__] + args logging.root.handlers = [] return function() finally: sys.argv = saved_argv logging.root.handlers = saved_handlers","title":"run_with_args()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.search_words","text":"Search for a sequence of words in a line. Parameters: words \u2013 A list of strings, the words to look for, or a space-separated string. line \u2013 A string, the line to search into. Returns: A MatchObject, or None. Source code in beancount/utils/test_utils.py def search_words(words, line): \"\"\"Search for a sequence of words in a line. Args: words: A list of strings, the words to look for, or a space-separated string. line: A string, the line to search into. Returns: A MatchObject, or None. \"\"\" if isinstance(words, str): words = words.split() return re.search('.*'.join(r'\\b{}\\b'.format(word) for word in words), line)","title":"search_words()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.skipIfRaises","text":"A context manager (or decorator) that skips a test if an exception is raised. Yields: Nothing, for you to execute the function code. Exceptions: SkipTest \u2013 if the test raised the expected exception. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def skipIfRaises(*exc_types): \"\"\"A context manager (or decorator) that skips a test if an exception is raised. Args: exc_type Yields: Nothing, for you to execute the function code. Raises: SkipTest: if the test raised the expected exception. \"\"\" try: yield except exc_types as exception: raise unittest.SkipTest(exception)","title":"skipIfRaises()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.subprocess_env","text":"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. Source code in beancount/utils/test_utils.py def subprocess_env(): \"\"\"Return a dict to use as environment for running subprocesses. Returns: A string, the root directory. \"\"\" # Ensure we have locations to invoke our Python executable and our # runnable binaries in the test environment to run subprocesses. binpath = ':'.join([ path.dirname(sys.executable), path.join(find_repository_root(__file__), 'bin'), os.environ.get('PATH', '').strip(':')]).strip(':') return {'PATH': binpath, 'PYTHONPATH': find_python_lib()}","title":"subprocess_env()"},{"location":"api_reference/beancount.utils.html#beancount.utils.test_utils.tempdir","text":"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Parameters: delete \u2013 A boolean, true if we want to delete the directory after running. **kw \u2013 Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. Source code in beancount/utils/test_utils.py @contextlib.contextmanager def tempdir(delete=True, **kw): \"\"\"A context manager that creates a temporary directory and deletes its contents unconditionally once done. Args: delete: A boolean, true if we want to delete the directory after running. **kw: Keyword arguments for mkdtemp. Yields: A string, the name of the temporary directory created. \"\"\" tempdir = tempfile.mkdtemp(prefix=\"beancount-test-tmpdir.\", **kw) try: yield tempdir finally: if delete: shutil.rmtree(tempdir, ignore_errors=True)","title":"tempdir()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils","text":"Text manipulation utilities.","title":"text_utils"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.entitize_ampersand","text":"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Parameters: filename \u2013 A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. Source code in beancount/utils/text_utils.py def entitize_ampersand(filename): \"\"\"Convert unescaped ampersand characters (&) to XML entities. This is used to fix code that has been programmed by bad developers who didn't think about escaping the entities in their strings. file does not contain Args: filename: A string, the name of the file to convert. Returns: A self-destructing NamedTemporaryFile object that has been flushed and which you may read to obtain the fixed contents. \"\"\" tidy_file = tempfile.NamedTemporaryFile(suffix='.xls', mode='w', delete=False) with open(filename) as infile: contents = infile.read() new_contents = re.sub('&([^;&]{12})', '&\\\\1', contents) tidy_file.write(new_contents) tidy_file.flush() return tidy_file","title":"entitize_ampersand()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.replace_number","text":"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Parameters: match \u2013 A MatchObject. Returns: A replacement string, consisting only of X'es. Source code in beancount/utils/text_utils.py def replace_number(match): \"\"\"Replace a single number matched from text into X'es. 'match' is a MatchObject from a regular expressions match. (Use this with re.sub()). Args: match: A MatchObject. Returns: A replacement string, consisting only of X'es. \"\"\" return re.sub('[0-9]', 'X', match.group(1)) + match.group(2)","title":"replace_number()"},{"location":"api_reference/beancount.utils.html#beancount.utils.text_utils.replace_numbers","text":"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Parameters: text \u2013 An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. Source code in beancount/utils/text_utils.py def replace_numbers(text): \"\"\"Replace all numbers found within text. Note that this is a heuristic used to filter out private numbers from web pages in incognito mode and thus may not be perfect. It should let through numbers which are part of URLs. Args: text: An input string object. Returns: A string, with relevant numbers hopefully replaced with X'es. \"\"\" return re.sub(r'\\b([0-9,]+(?:\\.[0-9]*)?)\\b([ \\t<]+[^0-9,.]|$)', replace_number, text)","title":"replace_numbers()"},{"location":"api_reference/beancount.utils.html#beancount.utils.version","text":"Implement common options across all programs.","title":"version"},{"location":"api_reference/beancount.utils.html#beancount.utils.version.ArgumentParser","text":"Add a standard --version option to an ArgumentParser. Parameters: *args \u2013 Arguments for the parser. *kwargs \u2013 Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. Source code in beancount/utils/version.py def ArgumentParser(*args, **kwargs): \"\"\"Add a standard --version option to an ArgumentParser. Args: *args: Arguments for the parser. *kwargs: Keyword arguments for the parser. Returns: An instance of ArgumentParser, with our default options set. \"\"\" parser = argparse.ArgumentParser(*args, **kwargs) parser.add_argument('--version', '-V', action='version', version=compute_version_string( beancount.__version__, _parser.__vc_changeset__, _parser.__vc_timestamp__)) return parser","title":"ArgumentParser()"},{"location":"api_reference/beancount.utils.html#beancount.utils.version.compute_version_string","text":"Compute a version string from the changeset and timestamp baked in the parser. Parameters: version \u2013 A string, the version number. changeset \u2013 A string, a version control string identifying the commit of the version. timestamp \u2013 An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. Source code in beancount/utils/version.py def compute_version_string(version, changeset, timestamp): \"\"\"Compute a version string from the changeset and timestamp baked in the parser. Args: version: A string, the version number. changeset: A string, a version control string identifying the commit of the version. timestamp: An integer, the UNIX epoch timestamp of the changeset. Returns: A human-readable string for the version. \"\"\" # Shorten changeset. if changeset: if re.match('hg:', changeset): changeset = changeset[:15] elif re.match('git:', changeset): changeset = changeset[:12] # Convert timestamp to a date. date = None if timestamp > 0: date = datetime.datetime.utcfromtimestamp(timestamp).date() version = 'Beancount {}'.format(version) if changeset or date: version = '{} ({})'.format( version, '; '.join(map(str, filter(None, [changeset, date])))) return version","title":"compute_version_string()"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index d780e7fa..26c33ccd 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,242 +2,242 @@ https://beancount.github.io/docs/index.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/a_comparison_of_beancount_and_ledger_hledger.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/a_proposal_for_an_improvement_on_inventory_booking.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/balance_assertions_in_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_cheat_sheet.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_design_doc.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_history_and_credits.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_language_syntax.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_options_reference.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_query_language.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_scripting_plugins.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_v3.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beancount_v3_dependencies.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/beangulp.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/calculating_portolio_returns.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/command_line_accounting_cookbook.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/command_line_accounting_in_context.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/exporting_your_portfolio.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/external_contributions.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/fetching_prices_in_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/fund_accounting_with_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/getting_started_with_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/health_care_expenses.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/how_inventories_work.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/how_we_share_expenses.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/importing_external_data.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/installing_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/installing_beancount_v3.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/ledgerhub_design_doc.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/precision_tolerances.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/rounding_precision_in_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/running_beancount_and_generating_reports.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/settlement_dates_in_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/sharing_expenses_with_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/stock_vesting_in_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/the_double_entry_counting_method.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/tracking_medical_claims.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/trading_with_beancount.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/tutorial_example.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/index.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.core.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.loader.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.ops.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.parser.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.plugins.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.scripts.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.tools.html - 2024-08-01 + 2024-09-01 daily https://beancount.github.io/docs/api_reference/beancount.utils.html - 2024-08-01 + 2024-09-01 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index e8c3ce03a01c2d64772d217ad353730c33c3c1fb..dae415dcd8edfaef841d549b9c9006983b7f637d 100644 GIT binary patch delta 750 zcmVJ~!|=R(Sgn@}2nMYS-ShJIuh;t@%a`rlCXr`|3+WvAdD+J} z{%o4#@wj3aVbCM5kh+FVFF&|>Z*119r{(r;AxD0}d^H^#k7k?S!t$COX!(D~6ymCL zu}`~|!^WcF#sv$fRUZexY4QWN&ufkY+U?`|@%#Py>3;nnUEbSnx|v>w4qAe@t<}z- zH_c7U4Axx9{)tg5gTtuFF$RULx^`4LSmnWXK&qe5#Sdj99FetGEt2pnH%Q9?29V*% z@LV{^71CMl5clFCg%4<_z1)8RrI~@EI5^7?KE6=t^jA-ksM1k!4^R)Y1$JFf(pn92 z?|5?V9Jq$Sy2#bAi=%f*ccAzjqCQo_jpH1b1TByh9;k-=ErIyT#qQN$vmqxo51kr) z>RhPD#Sh;qLY4eCg2@5Fy15r;_*x*%%A zOnve&jL4`gr7Fc2F=a4BIF&9-;FKLEBcCzRx5WpygDN7 zD?mQV6rr!k%x<`(?rVQGv^wh2se^=}_ZhpE5d0f*`I>uPSd*4|792(b7=Owv1Ubi){t;LTyr0)%B?H!_ta8bjbNF0z z>QW~h(d^X$_%iFM1w8g32{G-wlzW*$oYhs@4iI~^rQ?$}`V!Z delta 750 zcmVXRd&I$-@c0!JI+jV>7fI< zNJdEhvD%M6uz5N4esKh1hv9kouv#w{5DZ!u+UMo(U$6I1%a`rlCXr`|3+WvAdD+D{ z{A`+W99Qfj^m^bGQrocU)XW4XOs$dO+#UrooxquJ)Su)JmmTK<1Ag}7>6 z?9y)Ku(4>kalyiA)y3X#n*6}+^O|FicKf(~{C>ZFx?ev?m-n`tZl=?rg+0OB)@tX^ zo93ov25YWl|HP=3!G6%>7=yySx^`4LSmnXCK&qS1#Sdj59FVnFdnDmku9uc0^dQ5L z;kj^>E2Oj9As)m-3Lnr;d%1rDN;3mRad4I)e0-tO>93w7QKh5e4xk=p3+&pUq_rC4 z&hg~jIdBbub&;!K7e{ZCZb9)mM187;8;3bA3HCrzc%T~cw*=xV7rR%3&4!%VJhW=? zsdb?q7e9Wh2vzdm03u(4aZrPa+vJDBF^%>nc|H3sQbg}i@Oy=z^#b zGxf>CFe0O}l&Tb8#FW7h;Z(XTfm3#vjC{sKllSbi`xK8Ifr7~KY-Mu0G}X$&q4y9? zH^nG9163*ReG(_X@$XS?R|DTdoS->sieT%4mkEv+T%!q;1S=*5=kU4c z)TK^1qB*D|@MYFh3wZ265@OnUDfcpiIIF9)?ICt(OUEZ|4l`b@btcb)ymmd8Pi?E4 zJ;an{te6;1x*vS$>zR6RA4|q{>=S+>{^(|qpi*>?+Lphm`2{=